Skip to content

Commit

Permalink
wip: getInvoker abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Apr 26, 2024
1 parent cd1f9a4 commit 6b83cc8
Show file tree
Hide file tree
Showing 14 changed files with 434 additions and 52 deletions.
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "test/invoker"]
path = test/invoker
url = git@github.com:wevm/invoker.git
[submodule "test/invokers"]
path = test/invokers
url = git@github.com:anton-rs/3074-invokers.git
10 changes: 2 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
{
"workspaces": [
"examples/*",
"environments/*",
"site",
"src",
"test"
],
"workspaces": ["examples/*", "environments/*", "site", "src", "test"],
"private": true,
"type": "module",
"scripts": {
Expand All @@ -21,7 +15,7 @@
"changeset:version": "changeset version && bun install --lockfile-only && bun scripts/updateVersion.ts",
"clean": "rimraf src/_esm src/_cjs src/_types",
"contracts:build": "forge build --config-path ./test/foundry.toml && bun run contracts:build:invoker && bun ./scripts/generateTypedArtifacts.ts",
"contracts:build:invoker": "cd test/invoker && ./bin/forge build",
"contracts:build:invoker": "cd test/invokers && ./bin/forge build",
"docs:dev": "cd site && bun run dev",
"docs:build": "cd site && bun run build",
"docs:preview": "cd site && bun run preview",
Expand Down
2 changes: 1 addition & 1 deletion scripts/generateTypedArtifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const writer = generated.writer()

const paths = await globby([
join(import.meta.dir, '../test/contracts/out/**/*.json'),
join(import.meta.dir, '../test/invoker/out/**/*.json'),
join(import.meta.dir, '../test/invokers/out/**/*.json'),
])

const fileNames = []
Expand Down
106 changes: 106 additions & 0 deletions src/experimental/eip3074/constants/abis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
export const invokerAbi = [
{
type: 'function',
name: 'execute',
inputs: [
{
name: 'execData',
type: 'bytes',
internalType: 'bytes',
},
{
name: 'authority',
type: 'address',
internalType: 'address',
},
{
name: 'signature',
type: 'tuple',
internalType: 'struct Auth.Signature',
components: [
{
name: 'yParity',
type: 'uint8',
internalType: 'uint8',
},
{
name: 'r',
type: 'bytes32',
internalType: 'bytes32',
},
{
name: 's',
type: 'bytes32',
internalType: 'bytes32',
},
],
},
],
outputs: [],
stateMutability: 'payable',
},
{
type: 'function',
name: 'getDigest',
inputs: [
{
name: 'execData',
type: 'bytes',
internalType: 'bytes',
},
{
name: 'nonce',
type: 'uint256',
internalType: 'uint256',
},
],
outputs: [
{
name: 'digest',
type: 'bytes32',
internalType: 'bytes32',
},
],
stateMutability: 'view',
},
{
type: 'function',
name: 'nonce',
inputs: [
{
name: '',
type: 'address',
internalType: 'address',
},
],
outputs: [
{
name: '',
type: 'uint256',
internalType: 'uint256',
},
],
stateMutability: 'view',
},
{
type: 'error',
name: 'BadAuth',
inputs: [],
},
{
type: 'error',
name: 'InvalidNonce',
inputs: [
{
name: 'authority',
type: 'address',
internalType: 'address',
},
{
name: 'attempted',
type: 'uint256',
internalType: 'uint256',
},
],
},
] as const
65 changes: 27 additions & 38 deletions src/experimental/eip3074/e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,51 @@
import { expect, test } from 'vitest'
import { Invoker } from '../../../test/contracts/generated.js'
import { BatchInvoker } from '../../../test/contracts/generated.js'
import { anvil3074 } from '../../../test/src/anvil.js'
import { accounts } from '../../../test/src/constants.js'
import { deploy } from '../../../test/src/utils.js'
import { privateKeyToAccount } from '../../accounts/index.js'
import { getContract } from '../../actions/getContract.js'
import { getTransactionReceipt, mine } from '../../actions/index.js'
import { concat, encodePacked, parseEther } from '../../utils/index.js'
import { hexToSignature } from '../../utils/signature/hexToSignature.js'
import { signAuthMessage } from './actions/signAuthMessage.js'
import { parseEther } from '../../utils/index.js'
import { batchInvokerCoder } from './invokers/coders/batchInvokerCoder.js'
import { type InvokerArgs, getInvoker } from './invokers/getInvoker.js'

const client = anvil3074.getClient({ account: true })
const authorityClient = anvil3074.getClient({
account: privateKeyToAccount(accounts[1].privateKey),
})

test('execute', async () => {
const { contractAddress: invokerAddress } = await deploy(client, {
abi: Invoker.abi,
bytecode: Invoker.bytecode.object,
abi: BatchInvoker.abi,
bytecode: BatchInvoker.bytecode.object,
})

const contract = getContract({
abi: Invoker.abi,
// Initialize an invoker with it's contract address & `args` coder.
const invoker = getInvoker({
address: invokerAddress!,
client,
coder: batchInvokerCoder(),
})

const calls = concat([
encodePacked(
['uint8', 'address', 'uint256', 'uint256', 'bytes'],
[2, accounts[2].address, parseEther('1'), 0n, '0x'],
),
encodePacked(
['uint8', 'address', 'uint256', 'uint256', 'bytes'],
[2, accounts[2].address, parseEther('2'), 0n, '0x'],
),
encodePacked(
['uint8', 'address', 'uint256', 'uint256', 'bytes'],
[2, accounts[2].address, parseEther('3'), 0n, '0x'],
),
])
// Construct `args` as defined by shape of `batchInvokerCoder`.
const args = [
{ to: accounts[2].address, value: parseEther('1') },
{ to: accounts[2].address, value: parseEther('2') },
{ to: accounts[2].address, value: parseEther('3') },
] as const satisfies InvokerArgs<typeof invoker>

const commit = await contract.read.getCommit([
calls,
authorityClient.account.address,
])
// Authority to execute calls on behalf of.
const authority = privateKeyToAccount(accounts[1].privateKey)

const signature = await signAuthMessage(authorityClient, {
commit,
invokerAddress: invokerAddress!,
// Sign the auth message of `args`.
const signature = await invoker.sign({
authority,
args,
})

const hash = await contract.write.execute([
calls,
authorityClient.account.address,
hexToSignature(signature!),
])
// Execute `args` with the signature.
const hash = await invoker.execute({
authority,
args,
signature,
})

await mine(client, { blocks: 1 })

Expand Down
44 changes: 44 additions & 0 deletions src/experimental/eip3074/invokers/coders/batchInvokerCoder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect, test } from 'vitest'
import { BatchInvoker } from '../../../../../test/contracts/generated.js'
import { anvil3074 } from '../../../../../test/src/anvil.js'
import { deploy } from '../../../../../test/src/utils.js'
import { parseEther } from '../../../../utils/index.js'
import { batchInvokerCoder } from './batchInvokerCoder.js'

const client = anvil3074.getClient()

test('default', async () => {
const { contractAddress: invokerAddress } = await deploy(client, {
abi: BatchInvoker.abi,
bytecode: BatchInvoker.bytecode.object,
})

const coder = batchInvokerCoder()
expect(
await coder.toExecData(
[
{
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
data: '0xdeadbeef',
value: parseEther('1'),
},
{ to: '0xd2135CfB216b74109775236E36d4b433F1DF507B' },
{
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
value: parseEther('1'),
},
{
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
data: '0xdeadbeef',
},
],
{
authority: '0x0000000000000000000000000000000000000000',
client,
invokerAddress: invokerAddress!,
},
),
).toMatchInlineSnapshot(
`"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000015c02d2135cfb216b74109775236e36d4b433f1df507b0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000004deadbeef02d2135cfb216b74109775236e36d4b433f1df507b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d2135cfb216b74109775236e36d4b433f1df507b0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000000002d2135cfb216b74109775236e36d4b433f1df507b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004deadbeef00000000"`,
)
})
46 changes: 46 additions & 0 deletions src/experimental/eip3074/invokers/coders/batchInvokerCoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Address } from 'abitype'
import { readContract } from '../../../../actions/public/readContract.js'
import type { Hex } from '../../../../types/misc.js'
import { encodeAbiParameters } from '../../../../utils/abi/encodeAbiParameters.js'
import { encodePacked } from '../../../../utils/abi/encodePacked.js'
import { concat } from '../../../../utils/data/concat.js'
import { size } from '../../../../utils/data/size.js'
import { invokerAbi } from '../../constants/abis.js'
import { defineInvokerCoder } from './defineInvokerCoder.js'

type Calls = readonly {
to: Address
data?: Hex | undefined
value?: bigint | undefined
}[]

export type BatchInvokerArgs = Calls

export function batchInvokerCoder() {
return defineInvokerCoder({
async toExecData(
args: BatchInvokerArgs,
{ authority, client, invokerAddress },
) {
const nonce = await readContract(client, {
abi: invokerAbi,
address: invokerAddress,
functionName: 'nonce',
args: [authority],
})

const calls = concat(
args.map(({ data = '0x', to, value = 0n }) =>
encodePacked(
['uint8', 'address', 'uint256', 'uint256', 'bytes'],
[2, to, value, BigInt(size(data)), data],
),
),
)
return encodeAbiParameters(
[{ type: 'uint256' }, { type: 'bytes' }],
[nonce, calls],
)
},
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from 'vitest'
import { anvil3074 } from '../../../../../test/src/anvil.js'
import { encodePacked } from '../../../../utils/index.js'
import { defineInvokerCoder } from './defineInvokerCoder.js'

const client = anvil3074.getClient()

test('default', () => {
const coder = defineInvokerCoder({
async toExecData(args: string) {
return encodePacked(['string'], [args])
},
})
expect(
coder.toExecData('hello world', {
authority: '0x0000000000000000000000000000000000000000',
client,
invokerAddress: '0x0000000000000000000000000000000000000000',
}),
).toMatchInlineSnapshot(`"0x68656c6c6f20776f726c64"`)
})
20 changes: 20 additions & 0 deletions src/experimental/eip3074/invokers/coders/defineInvokerCoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Address } from 'abitype'
import type { Client } from '../../../../clients/createClient.js'
import type { Hex } from '../../../../types/misc.js'

export type InvokerCoder<args = unknown> = {
toExecData: (
args: args,
options: { authority: Address; client: Client; invokerAddress: Hex },
) => Promise<Hex>
}

export type DefineInvokerCoderParameters<args = unknown> = InvokerCoder<args>

export type DefineInvokerCoderReturnType<args = unknown> = InvokerCoder<args>

export function defineInvokerCoder<args>(
parameters: DefineInvokerCoderParameters<args>,
): DefineInvokerCoderReturnType<args> {
return parameters
}
22 changes: 22 additions & 0 deletions src/experimental/eip3074/invokers/getInvoker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { expect, test } from 'vitest'
import { anvil3074 } from '../../../../test/src/anvil.js'
import { batchInvokerCoder } from './coders/batchInvokerCoder.js'
import { getInvoker } from './getInvoker.js'

const client = anvil3074.getClient({ account: true })

test('default', async () => {
// Initialize an invoker with it's contract address & `args` coder.
const invoker = getInvoker({
address: '0x0D44f617435088c947F00B31160f64b074e412B4',
client,
coder: batchInvokerCoder(),
})
expect(invoker).toMatchInlineSnapshot(`
{
"address": "0x0D44f617435088c947F00B31160f64b074e412B4",
"execute": [Function],
"sign": [Function],
}
`)
})
Loading

0 comments on commit 6b83cc8

Please sign in to comment.