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): register delegation with signature #2480

Merged
merged 76 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 68 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
9638e39
wip
yonadaaa Mar 20, 2024
0dd31e2
feat: use v, r, s
yonadaaa Mar 20, 2024
991f921
fix: test command
yonadaaa Mar 20, 2024
cfed6b6
refactor: add helper
yonadaaa Mar 20, 2024
7e01049
chore: artifacts
yonadaaa Mar 20, 2024
3184d08
feat: signature nonces
yonadaaa Mar 20, 2024
4700302
chore: docs
yonadaaa Mar 20, 2024
9da694d
refactor: arguments on error
yonadaaa Mar 20, 2024
5fd4630
refactor: encodePacked
yonadaaa Mar 20, 2024
0294888
Merge remote-tracking branch 'origin/main' into yonadaaa/register-wit…
yonadaaa Mar 20, 2024
4b88f2f
wip: example of signature
yonadaaa Mar 20, 2024
4ae2c64
refactor: use eth signed message
yonadaaa Mar 20, 2024
0509e52
feat: test burner signing message in react minimal
yonadaaa Mar 20, 2024
1776b99
refactor: do not import oz
yonadaaa Mar 20, 2024
2ef40ab
Merge remote-tracking branch 'origin/main' into yonadaaa/register-wit…
yonadaaa Mar 21, 2024
73c3a3b
wip
yonadaaa Mar 21, 2024
4046da5
fix: declare nonce table with v2 config
yonadaaa Mar 21, 2024
a005254
test: add e2e test
yonadaaa Mar 21, 2024
feaa25c
remove examples
yonadaaa Mar 21, 2024
2bfda18
feat: better e2e test
yonadaaa Mar 21, 2024
a2f962a
rename test
yonadaaa Mar 21, 2024
a9f5516
chore: gas report
yonadaaa Mar 21, 2024
97c0026
refactor: rename files
yonadaaa Mar 21, 2024
b57b413
refactor: copy whole ecdsa library
yonadaaa Mar 21, 2024
65a306e
chore: gas report
yonadaaa Mar 21, 2024
6ddd301
refactor: createDelegation
yonadaaa Mar 21, 2024
4d0dfed
fix: chainid
yonadaaa Mar 21, 2024
300f17e
chore: gas report
yonadaaa Mar 21, 2024
8efbeb9
refactor: data export
yonadaaa Mar 21, 2024
c983a1f
refactor: remove app name and version
yonadaaa Mar 21, 2024
1d89e8e
feat: use proper world address
yonadaaa Mar 21, 2024
72d429f
chore: artifacts
yonadaaa Mar 21, 2024
6401c1f
docs: natspec
yonadaaa Mar 21, 2024
83c05ca
refactor: signature is calldata
yonadaaa Mar 21, 2024
568f0db
chore: artifacts
yonadaaa Mar 21, 2024
1959f65
Merge remote-tracking branch 'origin/main' into yonadaaa/register-wit…
yonadaaa Mar 22, 2024
9cb3e6e
refactor: test describe
yonadaaa Mar 22, 2024
6da29bd
refactor: read delegation data
yonadaaa Mar 22, 2024
9f0153d
refactor: private key constant
yonadaaa Mar 22, 2024
b0b9704
refactor: move function to DelegationSystem
yonadaaa Mar 25, 2024
0aa80dd
refactor: move to world-modules
yonadaaa Mar 25, 2024
9b5209e
chore: docs
yonadaaa Mar 25, 2024
b8c5cee
chore: remove unused interface
yonadaaa Mar 25, 2024
6328cee
docs: comment
yonadaaa Mar 25, 2024
36bddd0
fix: export
yonadaaa Mar 25, 2024
f03f67d
fix: e2e test
yonadaaa Mar 25, 2024
53ff98e
chore: worlds.json
yonadaaa Mar 25, 2024
c446925
refactor: use writeContract
yonadaaa Mar 25, 2024
f100973
chore: worlds.json
yonadaaa Mar 25, 2024
026f61d
fix: keysintable
yonadaaa Mar 25, 2024
eac661e
chore: gas report
yonadaaa Mar 25, 2024
52d6e8a
docs: comment
yonadaaa Mar 25, 2024
2422a87
refactor: rename to delegationWithSignature
yonadaaa Mar 26, 2024
36b1b30
test: assertEq address
yonadaaa Mar 26, 2024
b141f62
test: tidy test naming
yonadaaa Mar 26, 2024
b479e03
refactor: remove .sol from name
yonadaaa Mar 26, 2024
89f8832
refactor: use recover instead of tryRecover
yonadaaa Mar 26, 2024
d700848
test: rename test file
yonadaaa Mar 26, 2024
d0effce
test: check that cannot use empty signature
yonadaaa Mar 26, 2024
f4b5a3b
chore: delete unused interfaces
yonadaaa Mar 26, 2024
9285af2
refactor: remove registerDelegationWithSignature helper
yonadaaa Mar 26, 2024
184e8ad
test: use delegationControlId variable
yonadaaa Mar 26, 2024
3719a10
refactor: rename type export
yonadaaa Mar 26, 2024
41f5d10
chore: comment
yonadaaa Mar 26, 2024
da64e83
Merge remote-tracking branch 'origin/main' into yonadaaa/register-wit…
yonadaaa Mar 26, 2024
b8027ac
chore: changeset
yonadaaa Mar 26, 2024
9153b4c
refactor: add delegator to message
yonadaaa Mar 26, 2024
607ad03
chore: update changeset
yonadaaa Mar 26, 2024
50f4c60
fix: revert with non root install
yonadaaa Mar 26, 2024
b219c2d
refactor: invalid signature
yonadaaa Mar 26, 2024
3f7baf6
refactor: rename file
yonadaaa Mar 26, 2024
09f6acf
refactor: rename to unstable_
yonadaaa Mar 26, 2024
8916deb
fix: exclude system from mud config
yonadaaa Mar 26, 2024
993b468
Update .changeset/cold-apes-play.md
alvrs Mar 26, 2024
ccaf5ee
Merge remote-tracking branch 'origin/main' into yonadaaa/register-wit…
yonadaaa Mar 26, 2024
25ed862
Merge remote-tracking branch 'origin/main' into yonadaaa/register-wit…
yonadaaa Mar 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/cold-apes-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@latticexyz/cli": patch
"@latticexyz/world-modules": patch
"@latticexyz/world": patch
---

Added a new module, `DelegationWithSignatureModule`, which allows registering delegations with a signature.
alvrs marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion e2e/packages/client-vanilla/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { setup } from "./mud/setup";
import { decodeEntity } from "@latticexyz/store-sync/recs";

const {
network: { components, latestBlock$, worldContract, waitForTransaction },
network: { components, latestBlock$, walletClient, worldContract, waitForTransaction },
} = await setup();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const _window = window as any;
_window.walletClient = walletClient;
_window.worldContract = worldContract;
_window.waitForTransaction = waitForTransaction;

Expand Down
7 changes: 7 additions & 0 deletions e2e/packages/contracts/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,11 @@ export default defineWorld({
key: [],
},
},
modules: [
{
name: "DelegationWithSignatureModule",
root: true,
args: [],
},
],
});
108 changes: 108 additions & 0 deletions e2e/packages/sync-test/data/callRegisterDelegationWithSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Page } from "@playwright/test";
import { GetContractReturnType, PublicClient, WalletClient } from "viem";
import { AbiParametersToPrimitiveTypes, ExtractAbiFunction, ExtractAbiFunctionNames } from "abitype";

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

type DelegationAbi = typeof DelegationAbi;

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">) {
return page.evaluate(
([_args]) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const walletClient = (window as any).walletClient as WalletClient;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const worldContract = (window as any).worldContract as WorldContract;

return walletClient
.writeContract({
address: worldContract.address,
abi: [
{
type: "function",
name: "registerDelegationWithSignature",
inputs: [
{
name: "delegatee",
type: "address",
internalType: "address",
},
{
name: "delegationControlId",
type: "bytes32",
internalType: "ResourceId",
},
{
name: "initCallData",
type: "bytes",
internalType: "bytes",
},
{
name: "delegator",
type: "address",
internalType: "address",
},
{
name: "signature",
type: "bytes",
internalType: "bytes",
},
],
outputs: [],
stateMutability: "nonpayable",
},
],
functionName: "registerDelegationWithSignature",
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"),
);
});
},
[args],
);
}
15 changes: 15 additions & 0 deletions e2e/packages/sync-test/data/getWorld.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added this utility to get the world address (as it is part of the signature)

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Page } from "@playwright/test";
import IWorldAbi from "../../contracts/out/IWorld.sol/IWorld.abi.json";
import { GetContractReturnType, PublicClient, WalletClient } from "viem";

type WorldAbi = typeof IWorldAbi;

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

export function getWorld(page: Page) {
return page.evaluate(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const worldContract = (window as any).worldContract as WorldContract;
return worldContract;
}, []);
}
110 changes: 110 additions & 0 deletions e2e/packages/sync-test/registerDelegationWithSignature.test.ts
yonadaaa marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import type { ViteDevServer } from "vite";
import { Browser, Page } from "@playwright/test";
import { createAsyncErrorHandler } from "./asyncErrors";
import { deployContracts, startViteServer, startBrowserAndPage, openClientWithRootAccount } from "./setup";
import { rpcHttpUrl } from "./setup/constants";
import { waitForInitialSync } from "./data/waitForInitialSync";
import { createBurnerAccount, resourceToHex, transportObserver } from "@latticexyz/common";
import { http, createWalletClient, ClientConfig } 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 { getWorld } from "./data/getWorld";
import { callRegisterDelegationWithSignature } from "./data/callRegisterDelegationWithSignature";

const DELEGATOR_PRIVATE_KEY = "0x67bbd1575ecc79b3247c7d7b87a5bc533ccb6a63955a9fefdfaf75853f7cd543";

const worldConfigV1 = worldToV1(worldConfig);

describe("registerDelegationWithSignature", async () => {
const asyncErrorHandler = createAsyncErrorHandler();
let webserver: ViteDevServer;
let browser: Browser;
let page: Page;

beforeEach(async () => {
asyncErrorHandler.resetErrors();

await deployContracts(rpcHttpUrl);

// Start client and browser
webserver = await startViteServer();
const browserAndPage = await startBrowserAndPage(asyncErrorHandler.reportError);
browser = browserAndPage.browser;
page = browserAndPage.page;
});

afterEach(async () => {
await browser.close();
await webserver.close();
});

it("can generate a signature and register a delegation", async () => {
await openClientWithRootAccount(page);
await waitForInitialSync(page);

// Set up client
const clientOptions = {
chain: mudFoundry,
transport: transportObserver(http(mudFoundry.rpcUrls.default.http[0] ?? undefined)),
} as const satisfies ClientConfig;

const delegator = createBurnerAccount(DELEGATOR_PRIVATE_KEY);
const delegatorWalletClient = createWalletClient({
...clientOptions,
account: delegator,
});

const worldContract = await getWorld(page);

// Declare delegation parameters
const delegatee = "0x7203e7ADfDF38519e1ff4f8Da7DCdC969371f377";
Copy link
Member

Choose a reason for hiding this comment

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

where is this address coming from? is it account.address?

Copy link
Contributor Author

@yonadaaa yonadaaa Mar 26, 2024

Choose a reason for hiding this comment

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

It's just a made up address! it doesn't matter who the delegatee is because we don't do anything as them

I'll make this more clear

Copy link
Contributor Author

@yonadaaa yonadaaa Mar 26, 2024

Choose a reason for hiding this comment

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

This example is confusing because there are three accounts:

  • delegator, which is generated during the test and signs the message
  • delegatee, which is just generated during the test to fill in the delegatee value
  • the page account, which actually registers the delegation onchain but is otherwise not involved

Copy link
Member

Choose a reason for hiding this comment

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

ohh i see

const delegationControlId = resourceToHex({ type: "system", namespace: "", name: "unlimited" });
const initCallData = "0x";
const nonce = 0n;

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

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

// Expect delegation to have been created
const value = await callPageFunction(page, "getComponentValue", [
"UserDelegationControl",
encodeEntity(worldConfigV1.tables.UserDelegationControl.keySchema, {
delegator: delegator.address,
delegatee,
}),
]);

expect(value).toMatchObject({
__staticData: delegationControlId,
delegationControlId,
});
});
});
8 changes: 8 additions & 0 deletions packages/cli/src/utils/defaultModuleContracts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import KeysWithValueModuleData from "@latticexyz/world-modules/out/KeysWithValueModule.sol/KeysWithValueModule.json" assert { type: "json" };
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" };
import DelegationWithSignatureModuleData from "@latticexyz/world-modules/out/DelegationWithSignatureModule.sol/DelegationWithSignatureModule.json" assert { type: "json" };
import { Abi, Hex, size } from "viem";
import { findPlaceholders } from "./findPlaceholders";

Expand All @@ -27,4 +28,11 @@ export const defaultModuleContracts = [
placeholders: findPlaceholders(UniqueEntityModuleData.bytecode.linkReferences),
deployedBytecodeSize: size(UniqueEntityModuleData.deployedBytecode.object as Hex),
},
{
name: "DelegationWithSignatureModule",
abi: DelegationWithSignatureModuleData.abi as Abi,
bytecode: DelegationWithSignatureModuleData.bytecode.object as Hex,
placeholders: findPlaceholders(DelegationWithSignatureModuleData.bytecode.linkReferences),
deployedBytecodeSize: size(DelegationWithSignatureModuleData.deployedBytecode.object as Hex),
},
];
18 changes: 15 additions & 3 deletions packages/world-modules/gas-report.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
[
{
"file": "test/DelegationWithSignatureModule.t.sol",
"test": "testInstallRoot",
"name": "install delegation module",
"gasUsed": 689194
},
{
"file": "test/DelegationWithSignatureModule.t.sol",
"test": "testRegisterDelegationWithSignature",
"name": "register an unlimited delegation with signature",
"gasUsed": 117588
},
{
"file": "test/ERC20.t.sol",
"test": "testApprove",
Expand Down Expand Up @@ -267,7 +279,7 @@
"file": "test/StandardDelegationsModule.t.sol",
"test": "testCallFromCallboundDelegation",
"name": "register a callbound delegation",
"gasUsed": 139044
"gasUsed": 138994
},
{
"file": "test/StandardDelegationsModule.t.sol",
Expand All @@ -279,7 +291,7 @@
"file": "test/StandardDelegationsModule.t.sol",
"test": "testCallFromSystemDelegation",
"name": "register a systembound delegation",
"gasUsed": 136166
"gasUsed": 136116
},
{
"file": "test/StandardDelegationsModule.t.sol",
Expand All @@ -291,7 +303,7 @@
"file": "test/StandardDelegationsModule.t.sol",
"test": "testCallFromTimeboundDelegation",
"name": "register a timebound delegation",
"gasUsed": 132709
"gasUsed": 132659
},
{
"file": "test/StandardDelegationsModule.t.sol",
Expand Down
12 changes: 12 additions & 0 deletions packages/world-modules/mud.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,18 @@ export default defineWorld({
tableIdArgument: true,
},
},
/************************************************************************
*
* REGISTER DELEGATION WITH SIGNATURE MODULE
*
************************************************************************/
UserDelegationNonces: {
schema: { delegator: "address", nonce: "uint256" },
key: ["delegator"],
codegen: {
outputDirectory: "modules/delegation/tables",
},
},
},
excludeSystems: ["UniqueEntitySystem", "PuppetFactorySystem", "ERC20System", "ERC721System"],
});
1 change: 1 addition & 0 deletions packages/world-modules/src/index.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +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";
4 changes: 3 additions & 1 deletion packages/world-modules/src/interfaces/IBaseWorld.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ pragma solidity >=0.8.24;
import { IStore } from "@latticexyz/store/src/IStore.sol";
import { IWorldKernel } from "@latticexyz/world/src/IWorldKernel.sol";

import { IDelegationWithSignatureSystem } from "./IDelegationWithSignatureSystem.sol";

/**
* @title IBaseWorld
* @author MUD (https://mud.dev) by Lattice (https://lattice.xyz)
* @notice This interface integrates all systems and associated function selectors
* that are dynamically registered in the World during deployment.
* @dev This is an autogenerated file; do not edit manually.
*/
interface IBaseWorld is IStore, IWorldKernel {}
interface IBaseWorld is IStore, IWorldKernel, IDelegationWithSignatureSystem {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;

/* Autogenerated file. Do not edit manually. */

import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";

/**
* @title IDelegationWithSignatureSystem
* @author MUD (https://mud.dev) by Lattice (https://lattice.xyz)
* @dev This interface is automatically generated from the corresponding system contract. Do not edit manually.
*/
interface IDelegationWithSignatureSystem {
error InvalidSigner(address delegator, address delegatee);

function registerDelegationWithSignature(
address delegatee,
ResourceId delegationControlId,
bytes memory initCallData,
address delegator,
bytes memory signature
) external;
}
Loading
Loading