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-modules): add callWithSignature #2592

Merged
merged 14 commits into from
Apr 3, 2024
2 changes: 1 addition & 1 deletion e2e/packages/contracts/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default defineWorld({
},
modules: [
{
name: "Unstable_DelegationWithSignatureModule",
name: "Unstable_CallWithSignatureModule",
root: true,
args: [],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,46 @@ import { Page } from "@playwright/test";
import { GetContractReturnType, PublicClient, WalletClient } from "viem";
import { AbiParametersToPrimitiveTypes, ExtractAbiFunction, ExtractAbiFunctionNames } from "abitype";

const DelegationAbi = [
const Abi = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason we can't import this from world-modules?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I didn't think to try it 🤯

{
type: "function",
name: "registerDelegationWithSignature",
name: "callWithSignature",
inputs: [
{
name: "delegatee",
name: "delegator",
type: "address",
internalType: "address",
},
{
name: "delegationControlId",
name: "systemId",
type: "bytes32",
internalType: "ResourceId",
},
{
name: "initCallData",
name: "callData",
type: "bytes",
internalType: "bytes",
},
{
name: "delegator",
type: "address",
internalType: "address",
},
{
name: "signature",
type: "bytes",
internalType: "bytes",
},
],
outputs: [],
stateMutability: "nonpayable",
stateMutability: "payable",
},
] as const;

type DelegationAbi = typeof DelegationAbi;
type DelegationAbi = typeof Abi;

type WorldContract = GetContractReturnType<DelegationAbi, PublicClient, WalletClient>;

type WriteMethodName = ExtractAbiFunctionNames<DelegationAbi>;
type WriteMethod<TMethod extends WriteMethodName> = ExtractAbiFunction<DelegationAbi, TMethod>;
type WriteArgs<TMethod extends WriteMethodName> = AbiParametersToPrimitiveTypes<WriteMethod<TMethod>["inputs"]>;

export function callRegisterDelegationWithSignature(page: Page, args?: WriteArgs<"registerDelegationWithSignature">) {
export function callWithSignature(page: Page, args?: WriteArgs<"callWithSignature">) {
return page.evaluate(
([_args]) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -60,47 +55,40 @@ export function callRegisterDelegationWithSignature(page: Page, args?: WriteArgs
abi: [
{
type: "function",
name: "registerDelegationWithSignature",
name: "callWithSignature",
inputs: [
{
name: "delegatee",
name: "delegator",
type: "address",
internalType: "address",
},
{
name: "delegationControlId",
name: "systemId",
type: "bytes32",
internalType: "ResourceId",
},
{
name: "initCallData",
name: "callData",
type: "bytes",
internalType: "bytes",
},
{
name: "delegator",
type: "address",
internalType: "address",
},
{
name: "signature",
type: "bytes",
internalType: "bytes",
},
],
outputs: [],
stateMutability: "nonpayable",
stateMutability: "payable",
},
],
functionName: "registerDelegationWithSignature",
functionName: "callWithSignature",
args: _args,
})
.then((tx) => window["waitForTransaction"](tx))
.catch((error) => {
console.error(error);
throw new Error(
[`Error executing registerDelegationWithSignature with args:`, JSON.stringify(_args), error].join("\n\n"),
);
throw new Error([`Error executing callWithSignature with args:`, JSON.stringify(_args), error].join("\n\n"));
});
},
[args],
Expand Down
38 changes: 20 additions & 18 deletions e2e/packages/sync-test/registerDelegationWithSignature.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@ import { deployContracts, startViteServer, startBrowserAndPage, openClientWithRo
import { rpcHttpUrl } from "./setup/constants";
import { waitForInitialSync } from "./data/waitForInitialSync";
import { createBurnerAccount, resourceToHex, transportObserver } from "@latticexyz/common";
import { http, createWalletClient, ClientConfig } from "viem";
import { http, createWalletClient, ClientConfig, encodeFunctionData } from "viem";
import { mudFoundry } from "@latticexyz/common/chains";
import { encodeEntity } from "@latticexyz/store-sync/recs";
import { callPageFunction } from "./data/callPageFunction";
import worldConfig from "@latticexyz/world/mud.config";
import { worldToV1 } from "@latticexyz/world/config/v2";
import { delegationWithSignatureTypes } from "@latticexyz/world/internal";
import { callWithSignatureTypes } from "@latticexyz/world/internal";
import { getWorld } from "./data/getWorld";
import { callRegisterDelegationWithSignature } from "./data/callRegisterDelegationWithSignature";
import { callWithSignature } from "./data/callWithSignature";
import IWorldAbi from "../contracts/out/IWorld.sol/IWorld.abi.json";

const DELEGATOR_PRIVATE_KEY = "0x67bbd1575ecc79b3247c7d7b87a5bc533ccb6a63955a9fefdfaf75853f7cd543";

const worldConfigV1 = worldToV1(worldConfig);

describe("registerDelegationWithSignature", async () => {
describe("callWithSignature", async () => {
const asyncErrorHandler = createAsyncErrorHandler();
let webserver: ViteDevServer;
let browser: Browser;
Expand Down Expand Up @@ -60,38 +61,39 @@ describe("registerDelegationWithSignature", async () => {
});

const worldContract = await getWorld(page);
const systemId = resourceToHex({ type: "system", namespace: "", name: "Registration" });

// Declare delegation parameters
const delegatee = "0x7203e7ADfDF38519e1ff4f8Da7DCdC969371f377";
const delegationControlId = resourceToHex({ type: "system", namespace: "", name: "unlimited" });
const initCallData = "0x";

const callData = encodeFunctionData({
abi: IWorldAbi,
functionName: "registerDelegation",
args: [delegatee, delegationControlId, initCallData],
});

const nonce = 0n;

// Sign registration message
// Sign registration call message
const signature = await delegatorWalletClient.signTypedData({
domain: {
chainId: delegatorWalletClient.chain.id,
verifyingContract: worldContract.address,
},
types: delegationWithSignatureTypes,
primaryType: "Delegation",
types: callWithSignatureTypes,
primaryType: "Call",
message: {
delegatee,
delegationControlId,
initCallData,
delegator: delegator.address,
systemId,
callData,
nonce,
},
});

// Register the delegation
await callRegisterDelegationWithSignature(page, [
delegatee,
delegationControlId,
initCallData,
delegator.address,
signature,
]);
// Register a delegation
await callWithSignature(page, [delegator.address, systemId, callData, signature]);

// Expect delegation to have been created
const value = await callPageFunction(page, "getComponentValue", [
Expand Down
12 changes: 6 additions & 6 deletions packages/cli/src/utils/defaultModuleContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import KeysWithValueModuleData from "@latticexyz/world-modules/out/KeysWithValue
import KeysInTableModuleData from "@latticexyz/world-modules/out/KeysInTableModule.sol/KeysInTableModule.json" assert { type: "json" };
import UniqueEntityModuleData from "@latticexyz/world-modules/out/UniqueEntityModule.sol/UniqueEntityModule.json" assert { type: "json" };
// eslint-disable-next-line max-len
import Unstable_DelegationWithSignatureModuleData from "@latticexyz/world-modules/out/Unstable_DelegationWithSignatureModule.sol/Unstable_DelegationWithSignatureModule.json" assert { type: "json" };
import Unstable_CallWithSignatureModuleData from "@latticexyz/world-modules/out/Unstable_CallWithSignatureModule.sol/Unstable_CallWithSignatureModule.json" assert { type: "json" };
import { Abi, Hex, size } from "viem";
import { findPlaceholders } from "./findPlaceholders";

Expand Down Expand Up @@ -30,10 +30,10 @@ export const defaultModuleContracts = [
deployedBytecodeSize: size(UniqueEntityModuleData.deployedBytecode.object as Hex),
},
{
name: "Unstable_DelegationWithSignatureModule",
abi: Unstable_DelegationWithSignatureModuleData.abi as Abi,
bytecode: Unstable_DelegationWithSignatureModuleData.bytecode.object as Hex,
placeholders: findPlaceholders(Unstable_DelegationWithSignatureModuleData.bytecode.linkReferences),
deployedBytecodeSize: size(Unstable_DelegationWithSignatureModuleData.deployedBytecode.object as Hex),
name: "Unstable_CallWithSignatureModule",
abi: Unstable_CallWithSignatureModuleData.abi as Abi,
bytecode: Unstable_CallWithSignatureModuleData.bytecode.object as Hex,
placeholders: findPlaceholders(Unstable_CallWithSignatureModuleData.bytecode.linkReferences),
deployedBytecodeSize: size(Unstable_CallWithSignatureModuleData.deployedBytecode.object as Hex),
},
];
8 changes: 4 additions & 4 deletions packages/world-modules/gas-report.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[
{
"file": "test/DelegationWithSignatureModule.t.sol",
"file": "test/CallWithSignatureModule.t.sol",
"test": "testInstallRoot",
"name": "install delegation module",
"gasUsed": 689194
"gasUsed": 687912
},
{
"file": "test/DelegationWithSignatureModule.t.sol",
"file": "test/CallWithSignatureModule.t.sol",
"test": "testRegisterDelegationWithSignature",
"name": "register an unlimited delegation with signature",
"gasUsed": 117588
"gasUsed": 133222
},
{
"file": "test/ERC20.t.sol",
Expand Down
4 changes: 2 additions & 2 deletions packages/world-modules/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export default defineWorld({
* REGISTER DELEGATION WITH SIGNATURE MODULE
*
************************************************************************/
UserDelegationNonces: {
CallWithSignatureNonces: {
schema: { delegator: "address", nonce: "uint256" },
key: ["delegator"],
codegen: {
Expand All @@ -291,6 +291,6 @@ export default defineWorld({
"PuppetFactorySystem",
"ERC20System",
"ERC721System",
"Unstable_DelegationWithSignatureSystem",
"Unstable_CallWithSignatureSystem",
],
});
2 changes: 1 addition & 1 deletion packages/world-modules/src/index.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ import { Owners } from "./modules/erc721-puppet/tables/Owners.sol";
import { TokenApproval } from "./modules/erc721-puppet/tables/TokenApproval.sol";
import { OperatorApproval } from "./modules/erc721-puppet/tables/OperatorApproval.sol";
import { ERC721Registry } from "./modules/erc721-puppet/tables/ERC721Registry.sol";
import { UserDelegationNonces } from "./modules/delegation/tables/UserDelegationNonces.sol";
import { CallWithSignatureNonces } from "./modules/delegation/tables/CallWithSignatureNonces.sol";

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,25 @@ import { IBaseWorld } from "@latticexyz/world/src/codegen/interfaces/IBaseWorld.
import { Module } from "@latticexyz/world/src/Module.sol";
import { revertWithBytes } from "@latticexyz/world/src/revertWithBytes.sol";

import { UserDelegationNonces } from "./tables/UserDelegationNonces.sol";
import { Unstable_DelegationWithSignatureSystem } from "./Unstable_DelegationWithSignatureSystem.sol";
import { CallWithSignatureNonces } from "./tables/CallWithSignatureNonces.sol";
import { Unstable_CallWithSignatureSystem } from "./Unstable_CallWithSignatureSystem.sol";

import { DELEGATION_SYSTEM_ID } from "./constants.sol";

contract Unstable_DelegationWithSignatureModule is Module {
Unstable_DelegationWithSignatureSystem private immutable delegationWithSignatureSystem =
new Unstable_DelegationWithSignatureSystem();
contract Unstable_CallWithSignatureModule is Module {
Unstable_CallWithSignatureSystem private immutable callWithSignatureSystem = new Unstable_CallWithSignatureSystem();

function installRoot(bytes memory encodedArgs) public {
requireNotInstalled(__self, encodedArgs);

IBaseWorld world = IBaseWorld(_world());

// Register table
UserDelegationNonces._register();
CallWithSignatureNonces._register();

// Register system
(bool success, bytes memory data) = address(world).delegatecall(
abi.encodeCall(world.registerSystem, (DELEGATION_SYSTEM_ID, delegationWithSignatureSystem, true))
abi.encodeCall(world.registerSystem, (DELEGATION_SYSTEM_ID, callWithSignatureSystem, true))
);
if (!success) revertWithBytes(data);

Expand All @@ -35,8 +34,8 @@ contract Unstable_DelegationWithSignatureModule is Module {
world.registerRootFunctionSelector,
(
DELEGATION_SYSTEM_ID,
"registerDelegationWithSignature(address,bytes32,bytes,address,bytes)",
"registerDelegationWithSignature(address,bytes32,bytes,address,bytes)"
"callWithSignature(address,bytes32,bytes,bytes)",
"callWithSignature(address,bytes32,bytes,bytes)"
)
)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;

import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
import { System } from "@latticexyz/world/src/System.sol";
import { SystemCall } from "@latticexyz/world/src/SystemCall.sol";
import { createDelegation } from "@latticexyz/world/src/modules/init/implementations/createDelegation.sol";

import { CallWithSignatureNonces } from "./tables/CallWithSignatureNonces.sol";
import { getSignedMessageHash } from "./getSignedMessageHash.sol";
import { ECDSA } from "./ECDSA.sol";

contract Unstable_CallWithSignatureSystem is System {
/**
* @dev Mismatched signature.
*/
error InvalidSignature(address signer);

/**
* @notice Calls a system with a given system ID using the given signature.
* @param delegator The address on whose behalf the system is called.
* @param systemId The ID of the system to be called.
* @param callData The ABI data for the system call.
* @param signature The EIP712 signature.
* @return Return data from the system call.
*/
function callWithSignature(
address delegator,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if we should call this signer in this context. Delegations are similar but kind of a different concept to this path of calling on behalf of someone else

Suggested change
address delegator,
address signer,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree delegator is confusing in this context. signer is also what OpenZeppelin calls it in the SignatureChecker so i'm good with that

ResourceId systemId,
bytes memory callData,
bytes memory signature
) external payable returns (bytes memory) {
uint256 nonce = CallWithSignatureNonces.get(delegator);
bytes32 hash = getSignedMessageHash(delegator, systemId, callData, nonce, _world());

// If the message was not signed by the delegator or is invalid, revert
address signer = ECDSA.recover(hash, signature);
if (signer != delegator) {
revert InvalidSignature(signer);
}

CallWithSignatureNonces.set(delegator, nonce + 1);

return SystemCall.callWithHooksOrRevert(delegator, systemId, callData, _msgValue());
}
}
Loading
Loading