diff --git a/docs/pages/world/account-delegation.mdx b/docs/pages/world/account-delegation.mdx
index cd1a3bb1d8..6f19787ef0 100644
--- a/docs/pages/world/account-delegation.mdx
+++ b/docs/pages/world/account-delegation.mdx
@@ -1,3 +1,4 @@
+import { CollapseCode } from "../../components/CollapseCode";
import { Callout } from "nextra/components";
# Account delegation
@@ -33,6 +34,72 @@ With MUD delegation, you keep your existing account, but (temporarily) approve o
The most common type of delegation is when a delegator address allows a specific delegatee address to act on its behalf.
+### Delegation types
+
+Delegation can either be unlimited or limited by various factors (time, calldata, etc.).
+To create a limited delegation you need a contract that implements the [`DelegationControl`](https://github.com/latticexyz/mud/blob/main/packages/world/src/DelegationControl.sol) interface to decide whether or not to approve a specific call.
+The [`StandardDelegationModule`](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/std-delegations/StandardDelegationsModule.sol) includes three limited delegation control contracts. To use them, the owner of the root namespace needs to install this module.
+
+- [`CallboundDelegationControl`](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/std-delegations/CallboundDelegationControl.sol)
+- [`SystemboundDelegationControl`](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/std-delegations/SystemboundDelegationControl.sol)
+- [`TimeboundDelegationControl`](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/std-delegations/TimeboundDelegationControl.sol)
+
+Alternatively, you can write your own `DelegationControl` contract with custom logic.
+
+
+
+```solidity filename="DeployDelegation.s.sol" copy showLineNumbers {19-20}
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.21;
+
+import { Script } from "forge-std/Script.sol";
+import { console } from "forge-std/console.sol";
+
+import { IWorld } from "../src/codegen/world/IWorld.sol";
+
+import { StandardDelegationsModule } from "@latticexyz/world-modules/src/modules/std-delegations/StandardDelegationsModule.sol";
+
+contract DeployDelegation is Script {
+ function run() external {
+ uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
+ address worldAddress = vm.envAddress("WORLD_ADDRESS");
+
+ vm.startBroadcast(deployerPrivateKey);
+ IWorld world = IWorld(worldAddress);
+
+ StandardDelegationsModule standardDelegationsModule = new StandardDelegationsModule();
+ world.installRootModule(standardDelegationsModule, new bytes(0));
+
+ vm.stopBroadcast();
+ }
+}
+```
+
+
+
+
+
+Explanation
+
+Other than boilerplate, this script does two things.
+
+```solidity
+StandardDelegationsModule standardDelegationsModule =
+ new StandardDelegationsModule();
+```
+
+Deploy a new [`StandardDelegationsModule`](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/std-delegations/StandardDelegationsModule.sol) contract.
+[MUD modules](/world/modules) are onchain installation scripts, which can be used by multiple `World`s.
+
+```solidity
+world.installRootModule(standardDelegationsModule, new bytes(0));
+```
+
+Install the module.
+`StandardDelegationsModule` can only be installed as a root module (using `installRootModule`, not `installModule`), which can only be done by the owner of the root namespace.
+
+
+
### Creating a user delegation
First, the delegator has to call [`registerDelegation`](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol#L251-L284).
@@ -49,6 +116,132 @@ This function takes three parameters:
- `initCallData`, call data for a function that is called on the `delegationControlId` to inform it of the new delegation.
This [call data](https://docs.soliditylang.org/en/latest/abi-spec.html) includes both the function selector of the function to call and arguments to pass to the function (the result of `abi.encodeCall`).
+
+
+```solidity filename="TestDelegation.s.sol" copy showLineNumbers {7-10,35-39}
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.21;
+
+import { Script } from "forge-std/Script.sol";
+import { console } from "forge-std/console.sol";
+
+import { SystemboundDelegationControl } from "@latticexyz/world-modules/src/modules/std-delegations/SystemboundDelegationControl.sol";
+import { SYSTEMBOUND_DELEGATION } from "@latticexyz/world-modules/src/modules/std-delegations/StandardDelegationsModule.sol";
+import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
+
+import { IWorld } from "../src/codegen/world/IWorld.sol";
+
+contract TestDelegation is Script {
+ using WorldResourceIdInstance for ResourceId;
+
+ function run() external {
+ // Load the configuration
+ uint256 userPrivateKey = vm.envUint("PRIVATE_KEY");
+ uint256 userPrivateKey2 = vm.envUint("PRIVATE_KEY_2");
+ address userAddress = vm.envAddress("USER_ADDRESS");
+ address userAddress2 = vm.envAddress("USER_ADDRESS_2");
+ address worldAddress = vm.envAddress("WORLD_ADDRESS");
+
+ IWorld world = IWorld(worldAddress);
+ ResourceId systemId = WorldResourceIdLib.encode({
+ typeId: RESOURCE_SYSTEM,
+ namespace: "",
+ name: "IncrementSystem"
+ });
+
+ // Run as the first address
+ vm.startBroadcast(userPrivateKey);
+
+ world.registerDelegation(
+ userAddress2,
+ SYSTEMBOUND_DELEGATION,
+ abi.encodeCall(SystemboundDelegationControl.initDelegation, (userAddress2, systemId, 2))
+ );
+
+ vm.stopBroadcast();
+
+ // Run as the second address
+ vm.startBroadcast(userPrivateKey2);
+
+ bytes memory returnData = world.callFrom(userAddress, systemId, abi.encodeWithSignature("increment()"));
+ console.logBytes(returnData);
+
+ vm.stopBroadcast();
+ }
+}
+```
+
+
+
+
+
+Explanation
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.21;
+
+import { Script } from "forge-std/Script.sol";
+import { console } from "forge-std/console.sol";
+
+import { SystemboundDelegationControl } from "@latticexyz/world-modules/src/modules/std-delegations/SystemboundDelegationControl.sol";
+import { SYSTEMBOUND_DELEGATION } from "@latticexyz/world-modules/src/modules/std-delegations/StandardDelegationsModule.sol";
+```
+
+Import the contract definitions for the delegation system we use, [`SystemboundDelegationControl`](https://github.com/latticexyz/mud/blob/main/packages/world-modules/src/modules/std-delegations/SystemboundDelegationControl.sol), and the `System` identifier for that delegation type.
+
+```solidity
+import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
+```
+
+We need to create the resource identifier for the target `System`, the one where `USER_ADDRESS` allows `USER_ADDRESS_2` to perform actions on its behalf.
+
+```solidity
+import { IWorld } from "../src/codegen/world/IWorld.sol";
+
+contract TestDelegation is Script {
+ using WorldResourceIdInstance for ResourceId;
+
+ function run() external {
+
+ // Load the configuration
+ uint256 userPrivateKey = vm.envUint("PRIVATE_KEY");
+ uint256 userPrivateKey2 = vm.envUint("PRIVATE_KEY_2");
+ address userAddress = vm.envAddress("USER_ADDRESS");
+ address userAddress2 = vm.envAddress("USER_ADDRESS_2");
+ address worldAddress = vm.envAddress("WORLD_ADDRESS");
+
+ IWorld world = IWorld(worldAddress);
+ ResourceId systemId = WorldResourceIdLib.encode({
+ typeId: RESOURCE_SYSTEM,
+ namespace: "",
+ name: "IncrementSystem"
+ });
+```
+
+The system where `increment`, the function to which we are delegating access, is located in `IncrementSystem` at the root namespace.
+
+```solidity
+
+ // Run as the first address
+ vm.startBroadcast(userPrivateKey);
+
+ world.registerDelegation(
+ userAddress2,
+ SYSTEMBOUND_DELEGATION,
+ abi.encodeCall(SystemboundDelegationControl.initDelegation, (userAddress2, systemId, 2))
+ );
+```
+
+This is how you register a delegation.
+The exact parameters depend on the delegation `System` we are using, so we call `registerDelegation` with some parameters, and then provide the exact call to the `System`, in this case `initDelegation(userAddress2, systemId, 2)`.
+
+The remaining code uses a user delegation, so you can see it in the next section.
+
+
+
### Using a user delegation
The delegatee can use [`callFrom`](https://github.com/latticexyz/mud/blob/main/packages/world/src/World.sol#L353-L394).
@@ -61,6 +254,94 @@ This function takes three parameters:
Note that between a specific delegator and a specific delegatee there can only be one user delegation at a time.
This means that if you need in a `World` to implement multiple delegation algorithms you might need to create a dispatcher delegation that calls different verification functions based on the `systemId` and `callData` it receives.
+
+
+```solidity copy filename="TestDelegation.s.sol" showLineNumbers {26-30,46-47}
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.21;
+
+import { Script } from "forge-std/Script.sol";
+import { console } from "forge-std/console.sol";
+
+import { SystemboundDelegationControl } from "@latticexyz/world-modules/src/modules/std-delegations/SystemboundDelegationControl.sol";
+import { SYSTEMBOUND_DELEGATION } from "@latticexyz/world-modules/src/modules/std-delegations/StandardDelegationsModule.sol";
+import { ResourceId, WorldResourceIdLib, WorldResourceIdInstance } from "@latticexyz/world/src/WorldResourceId.sol";
+import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
+
+import { IWorld } from "../src/codegen/world/IWorld.sol";
+
+contract TestDelegation is Script {
+ using WorldResourceIdInstance for ResourceId;
+
+ function run() external {
+ // Load the configuration
+ uint256 userPrivateKey = vm.envUint("PRIVATE_KEY");
+ uint256 userPrivateKey2 = vm.envUint("PRIVATE_KEY_2");
+ address userAddress = vm.envAddress("USER_ADDRESS");
+ address userAddress2 = vm.envAddress("USER_ADDRESS_2");
+ address worldAddress = vm.envAddress("WORLD_ADDRESS");
+
+ IWorld world = IWorld(worldAddress);
+ ResourceId systemId = WorldResourceIdLib.encode({
+ typeId: RESOURCE_SYSTEM,
+ namespace: "",
+ name: "IncrementSystem"
+ });
+
+ // Run as the first address
+ vm.startBroadcast(userPrivateKey);
+
+ world.registerDelegation(
+ userAddress2,
+ SYSTEMBOUND_DELEGATION,
+ abi.encodeCall(SystemboundDelegationControl.initDelegation, (userAddress2, systemId, 2))
+ );
+
+ vm.stopBroadcast();
+
+ // Run as the second address
+ vm.startBroadcast(userPrivateKey2);
+
+ bytes memory returnData = world.callFrom(userAddress, systemId, abi.encodeWithSignature("increment()"));
+ console.logBytes(returnData);
+
+ vm.stopBroadcast();
+ }
+}
+```
+
+
+
+
+
+Explanation
+
+This code explains how to use a user delegation.
+The beginning of the script, which registers a delegation, is in the previous section.
+
+```solidity
+ vm.stopBroadcast();
+
+ // Run as the second address
+ vm.startBroadcast(userPrivateKey2);
+
+ bytes memory returnData = world.callFrom(userAddress, systemId,
+ abi.encodeWithSignature("increment()"));
+ console.logBytes(returnData);
+```
+
+To actually use a delegation you use `world.callFrom` with the address of the user on whose behalf you are performing the action, the resource identifier of the `System` on which you perform the action, and the calldata to send that system (which includes the signature of the function name and parameters, followed by parameter values if any).
+
+The output is returned as `bytes memory`.
+
+```solidity
+ vm.stopBroadcast();
+ }
+}
+```
+
+
+
### Removing a user delegation
You can use [`unregisterDelegation`](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol#L286-L294) to remove a delegation.
@@ -68,6 +349,33 @@ You can use [`unregisterDelegation`](https://github.com/latticexyz/mud/blob/main
Because of `unregisterDelegation` delegations cannot be used for a delegator to commit to allow something to be done in the future.
If you need a _commitment_, create a table with a `System` that lets the address that commits write the commitment and have an action that other addresses can call only if the proper commitment is there - without giving the committed address an option to delete the entry.
+
+
+```solidity filename="RemoveDelegation.s.sol" copy showLineNumbers {17}
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.8.21;
+
+import { Script } from "forge-std/Script.sol";
+import { console } from "forge-std/console.sol";
+import { IWorld } from "../src/codegen/world/IWorld.sol";
+
+contract RemoveDelegation is Script {
+ function run() external {
+ // Load the configuration
+ uint256 userPrivateKey = vm.envUint("PRIVATE_KEY");
+ address worldAddress = vm.envAddress("WORLD_ADDRESS");
+ address userAddress2 = vm.envAddress("USER_ADDRESS_2");
+
+ IWorld world = IWorld(worldAddress);
+ vm.startBroadcast(userPrivateKey);
+ world.unregisterDelegation(userAddress2);
+ vm.stopBroadcast();
+ }
+}
+```
+
+
+
## Namespace delegation
The owner of a namespace can use [`registerNamespaceDelegation`](https://github.com/latticexyz/mud/blob/main/packages/world/src/modules/init/implementations/WorldRegistrationSystem.sol#L296-L335) to register a delegation that applies to all callers of systems in this namespace.