Skip to content

Commit

Permalink
feat(world): add callFrom entry point (#1364)
Browse files Browse the repository at this point in the history
  • Loading branch information
alvrs committed Sep 1, 2023
1 parent bbe4def commit 1ca35e9
Show file tree
Hide file tree
Showing 45 changed files with 2,083 additions and 36 deletions.
50 changes: 50 additions & 0 deletions .changeset/nervous-walls-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
"@latticexyz/world": minor
---

The `World` has a new `callFrom` entry point which allows systems to be called on behalf of other addresses if those addresses have registered a delegation.
If there is a delegation, the call is forwarded to the system with `delegator` as `msgSender`.

```solidity
interface IBaseWorld {
function callFrom(
address delegator,
bytes32 resourceSelector,
bytes memory funcSelectorAndArgs
) external payable virtual returns (bytes memory);
}
```

A delegation can be registered via the `World`'s `registerDelegation` function.
If `delegatee` is `address(0)`, the delegation is considered to be a "fallback" delegation and is used in `callFrom` if there is no delegation is found for the specific caller.
Otherwise the delegation is registered for the specific `delegatee`.

```solidity
interface IBaseWorld {
function registerDelegation(
address delegatee,
bytes32 delegationControl,
bytes memory initFuncSelectorAndArgs
) external;
}
```

The `delegationControl` refers to the resource selector of a `DelegationControl` system that must have been registered beforehand.
As part of registering the delegation, the `DelegationControl` system is called with the provided `initFuncSelectorAndArgs`.
This can be used to initialize data in the given `DelegationControl` system.

The `DelegationControl` system must implement the `IDelegationControl` interface:

```solidity
interface IDelegationControl {
function verify(address delegator, bytes32 systemId, bytes calldata funcSelectorAndArgs) external returns (bool);
}
```

When `callFrom` is called, the `World` checks if a delegation is registered for the given caller, and if so calls the delegation control's `verify` function with the same same arguments as `callFrom`.
If the call to `verify` is successful and returns `true`, the delegation is valid and the call is forwarded to the system with `delegator` as `msgSender`.

Note: if `UNLIMITED_DELEGATION` (from `@latticexyz/world/src/constants.sol`) is passed as `delegationControl`, the external call to the delegation control contract is skipped and the delegation is considered valid.

For examples of `DelegationControl` systems, check out the `CallboundDelegationControl` or `TimeboundDelegationControl` systems in the `std-delegations` module.
See `StandardDelegations.t.sol` for usage examples.
95 changes: 95 additions & 0 deletions e2e/packages/contracts/types/ethers-contracts/IWorld.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type {
export interface IWorldInterface extends utils.Interface {
functions: {
"call(bytes32,bytes)": FunctionFragment;
"callFrom(address,bytes32,bytes)": FunctionFragment;
"deleteRecord(bytes32,bytes32[],bytes32)": FunctionFragment;
"emitEphemeralRecord(bytes32,bytes32[],bytes,bytes32)": FunctionFragment;
"getField(bytes32,bytes32[],uint8,bytes32)": FunctionFragment;
Expand All @@ -47,6 +48,7 @@ export interface IWorldInterface extends utils.Interface {
"push(uint32)": FunctionFragment;
"pushRange(uint32,uint32)": FunctionFragment;
"pushToField(bytes32,bytes32[],uint8,bytes,bytes32)": FunctionFragment;
"registerDelegation(address,bytes32,bytes)": FunctionFragment;
"registerFunctionSelector(bytes32,string,string)": FunctionFragment;
"registerNamespace(bytes16)": FunctionFragment;
"registerRootFunctionSelector(bytes32,bytes4,bytes4)": FunctionFragment;
Expand All @@ -66,6 +68,7 @@ export interface IWorldInterface extends utils.Interface {
getFunction(
nameOrSignatureOrTopic:
| "call"
| "callFrom"
| "deleteRecord"
| "emitEphemeralRecord"
| "getField"
Expand All @@ -82,6 +85,7 @@ export interface IWorldInterface extends utils.Interface {
| "push"
| "pushRange"
| "pushToField"
| "registerDelegation"
| "registerFunctionSelector"
| "registerNamespace"
| "registerRootFunctionSelector"
Expand All @@ -102,6 +106,14 @@ export interface IWorldInterface extends utils.Interface {
functionFragment: "call",
values: [PromiseOrValue<BytesLike>, PromiseOrValue<BytesLike>]
): string;
encodeFunctionData(
functionFragment: "callFrom",
values: [
PromiseOrValue<string>,
PromiseOrValue<BytesLike>,
PromiseOrValue<BytesLike>
]
): string;
encodeFunctionData(
functionFragment: "deleteRecord",
values: [
Expand Down Expand Up @@ -205,6 +217,14 @@ export interface IWorldInterface extends utils.Interface {
PromiseOrValue<BytesLike>
]
): string;
encodeFunctionData(
functionFragment: "registerDelegation",
values: [
PromiseOrValue<string>,
PromiseOrValue<BytesLike>,
PromiseOrValue<BytesLike>
]
): string;
encodeFunctionData(
functionFragment: "registerFunctionSelector",
values: [
Expand Down Expand Up @@ -299,6 +319,7 @@ export interface IWorldInterface extends utils.Interface {
): string;

decodeFunctionResult(functionFragment: "call", data: BytesLike): Result;
decodeFunctionResult(functionFragment: "callFrom", data: BytesLike): Result;
decodeFunctionResult(
functionFragment: "deleteRecord",
data: BytesLike
Expand Down Expand Up @@ -348,6 +369,10 @@ export interface IWorldInterface extends utils.Interface {
functionFragment: "pushToField",
data: BytesLike
): Result;
decodeFunctionResult(
functionFragment: "registerDelegation",
data: BytesLike
): Result;
decodeFunctionResult(
functionFragment: "registerFunctionSelector",
data: BytesLike
Expand Down Expand Up @@ -496,6 +521,13 @@ export interface IWorld extends BaseContract {
overrides?: PayableOverrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>;

callFrom(
delegator: PromiseOrValue<string>,
resourceSelector: PromiseOrValue<BytesLike>,
funcSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: PayableOverrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>;

deleteRecord(
table: PromiseOrValue<BytesLike>,
key: PromiseOrValue<BytesLike>[],
Expand Down Expand Up @@ -605,6 +637,13 @@ export interface IWorld extends BaseContract {
overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>;

registerDelegation(
delegatee: PromiseOrValue<string>,
delegationControlId: PromiseOrValue<BytesLike>,
initFuncSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>;

registerFunctionSelector(
resourceSelector: PromiseOrValue<BytesLike>,
systemFunctionName: PromiseOrValue<string>,
Expand Down Expand Up @@ -708,6 +747,13 @@ export interface IWorld extends BaseContract {
overrides?: PayableOverrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>;

callFrom(
delegator: PromiseOrValue<string>,
resourceSelector: PromiseOrValue<BytesLike>,
funcSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: PayableOverrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>;

deleteRecord(
table: PromiseOrValue<BytesLike>,
key: PromiseOrValue<BytesLike>[],
Expand Down Expand Up @@ -817,6 +863,13 @@ export interface IWorld extends BaseContract {
overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>;

registerDelegation(
delegatee: PromiseOrValue<string>,
delegationControlId: PromiseOrValue<BytesLike>,
initFuncSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<ContractTransaction>;

registerFunctionSelector(
resourceSelector: PromiseOrValue<BytesLike>,
systemFunctionName: PromiseOrValue<string>,
Expand Down Expand Up @@ -920,6 +973,13 @@ export interface IWorld extends BaseContract {
overrides?: CallOverrides
): Promise<string>;

callFrom(
delegator: PromiseOrValue<string>,
resourceSelector: PromiseOrValue<BytesLike>,
funcSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: CallOverrides
): Promise<string>;

deleteRecord(
table: PromiseOrValue<BytesLike>,
key: PromiseOrValue<BytesLike>[],
Expand Down Expand Up @@ -1027,6 +1087,13 @@ export interface IWorld extends BaseContract {
overrides?: CallOverrides
): Promise<void>;

registerDelegation(
delegatee: PromiseOrValue<string>,
delegationControlId: PromiseOrValue<BytesLike>,
initFuncSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: CallOverrides
): Promise<void>;

registerFunctionSelector(
resourceSelector: PromiseOrValue<BytesLike>,
systemFunctionName: PromiseOrValue<string>,
Expand Down Expand Up @@ -1177,6 +1244,13 @@ export interface IWorld extends BaseContract {
overrides?: PayableOverrides & { from?: PromiseOrValue<string> }
): Promise<BigNumber>;

callFrom(
delegator: PromiseOrValue<string>,
resourceSelector: PromiseOrValue<BytesLike>,
funcSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: PayableOverrides & { from?: PromiseOrValue<string> }
): Promise<BigNumber>;

deleteRecord(
table: PromiseOrValue<BytesLike>,
key: PromiseOrValue<BytesLike>[],
Expand Down Expand Up @@ -1286,6 +1360,13 @@ export interface IWorld extends BaseContract {
overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<BigNumber>;

registerDelegation(
delegatee: PromiseOrValue<string>,
delegationControlId: PromiseOrValue<BytesLike>,
initFuncSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<BigNumber>;

registerFunctionSelector(
resourceSelector: PromiseOrValue<BytesLike>,
systemFunctionName: PromiseOrValue<string>,
Expand Down Expand Up @@ -1390,6 +1471,13 @@ export interface IWorld extends BaseContract {
overrides?: PayableOverrides & { from?: PromiseOrValue<string> }
): Promise<PopulatedTransaction>;

callFrom(
delegator: PromiseOrValue<string>,
resourceSelector: PromiseOrValue<BytesLike>,
funcSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: PayableOverrides & { from?: PromiseOrValue<string> }
): Promise<PopulatedTransaction>;

deleteRecord(
table: PromiseOrValue<BytesLike>,
key: PromiseOrValue<BytesLike>[],
Expand Down Expand Up @@ -1499,6 +1587,13 @@ export interface IWorld extends BaseContract {
overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<PopulatedTransaction>;

registerDelegation(
delegatee: PromiseOrValue<string>,
delegationControlId: PromiseOrValue<BytesLike>,
initFuncSelectorAndArgs: PromiseOrValue<BytesLike>,
overrides?: Overrides & { from?: PromiseOrValue<string> }
): Promise<PopulatedTransaction>;

registerFunctionSelector(
resourceSelector: PromiseOrValue<BytesLike>,
systemFunctionName: PromiseOrValue<string>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ const _abi = [
name: "AccessDenied",
type: "error",
},
{
inputs: [
{
internalType: "address",
name: "delegator",
type: "address",
},
{
internalType: "address",
name: "delegatee",
type: "address",
},
],
name: "DelegationNotFound",
type: "error",
},
{
inputs: [
{
Expand Down Expand Up @@ -379,6 +395,35 @@ const _abi = [
stateMutability: "payable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "delegator",
type: "address",
},
{
internalType: "bytes32",
name: "resourceSelector",
type: "bytes32",
},
{
internalType: "bytes",
name: "funcSelectorAndArgs",
type: "bytes",
},
],
name: "callFrom",
outputs: [
{
internalType: "bytes",
name: "",
type: "bytes",
},
],
stateMutability: "payable",
type: "function",
},
{
inputs: [
{
Expand Down Expand Up @@ -767,6 +812,29 @@ const _abi = [
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "delegatee",
type: "address",
},
{
internalType: "bytes32",
name: "delegationControlId",
type: "bytes32",
},
{
internalType: "bytes",
name: "initFuncSelectorAndArgs",
type: "bytes",
},
],
name: "registerDelegation",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
Expand Down
Loading

0 comments on commit 1ca35e9

Please sign in to comment.