Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(world): add callFrom entry point #1364

Merged
merged 15 commits into from
Sep 1, 2023
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