Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions packages/composable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,23 @@ const signedRegisterCalldata = poller.encodeRegisterWithSignature(schedule, dead

// Submit this calldata to pollerAddress from schedule.funder.
const revokeCalldata = poller.encodeRevoke(scheduleId)

// After registration succeeds, read the funder's shared action nonce again before signing revocation.
const revokeNonce = await poller.getNonce(schedule.funder)
const revokeDeadline = Math.floor(Date.now() / 1000) + 15 * 60
const revokeTypedData = poller.getRevokeTypedData({
chainId,
id: scheduleId,
funder: schedule.funder,
nonce: revokeNonce,
deadline: revokeDeadline,
})
const revokeSignature = await signer.signTypedData(
revokeTypedData.domain,
revokeTypedData.types,
revokeTypedData.message,
)
const signedRevokeCalldata = poller.encodeRevokeWithSignature(scheduleId, revokeDeadline, revokeSignature)
```

## Usage
Expand Down
41 changes: 41 additions & 0 deletions packages/composable/src/ComposableCowPoller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BigIntish, getGlobalAdapter, Provider } from '@cowprotocol/sdk-common'
import { ComposableCowPollerAbi } from './abis/ComposableCowPollerAbi'
import type {
ComposableCowPollerRegisterTypedDataParams,
ComposableCowPollerRevokeTypedDataParams,
ComposableCowPollerSchedule,
ComposableCowPollerScheduleKey,
ComposableCowPollerTypedData,
Expand All @@ -20,6 +21,14 @@ const REGISTER_TYPES = {
{ name: 'deadline', type: 'uint256' },
],
}
const REVOKE_TYPES = {
Revoke: [
{ name: 'id', type: 'bytes32' },
{ name: 'funder', type: 'address' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
}

type RegisterMessage = {
readonly handler: string
Expand All @@ -31,6 +40,13 @@ type RegisterMessage = {
readonly deadline: BigIntish
}

type RevokeMessage = {
readonly id: string
readonly funder: string
readonly nonce: BigIntish
readonly deadline: BigIntish
}

/** Utilities for interacting with a ComposableCowPoller deployment. */
export class ComposableCowPoller {
private composableCowAddress?: { adapter: ReturnType<typeof getGlobalAdapter>; address: string }
Expand Down Expand Up @@ -99,6 +115,22 @@ export class ComposableCowPoller {
}
}

/** Builds the EIP-712 payload authorized by revokeWithSignature. */
public getRevokeTypedData({
chainId,
id,
funder,
nonce,
deadline,
}: ComposableCowPollerRevokeTypedDataParams): ComposableCowPollerTypedData<'Revoke', RevokeMessage> {
return {
domain: this.getEip712Domain(chainId),
types: REVOKE_TYPES,
primaryType: 'Revoke',
message: { id, funder, nonce, deadline },
}
}

/** Returns the app-data-independent schedule ID. */
public getScheduleId(schedule: ComposableCowPollerScheduleKey): string {
const encoded = getGlobalAdapter().utils.encodeAbi(SCHEDULE_ID_ABI, [
Expand Down Expand Up @@ -138,4 +170,13 @@ export class ComposableCowPoller {
public encodeRevoke(id: string): string {
return getGlobalAdapter().utils.encodeFunction(ComposableCowPollerAbi, 'revoke', [id]) as string
}

/** Encodes Poller.revokeWithSignature. */
public encodeRevokeWithSignature(id: string, deadline: BigIntish, signature: string): string {
return getGlobalAdapter().utils.encodeFunction(ComposableCowPollerAbi, 'revokeWithSignature', [
id,
deadline,
signature,
]) as string
}
}
8 changes: 8 additions & 0 deletions packages/composable/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ export type ComposableCowPollerRegisterTypedDataParams = {
readonly deadline: BigIntish
}

export type ComposableCowPollerRevokeTypedDataParams = {
readonly chainId: number
readonly id: string
readonly funder: string
readonly nonce: BigIntish
readonly deadline: BigIntish
}

export enum ProofLocation {
// The location of the proofs is private to the caller.
PRIVATE = 0,
Expand Down
44 changes: 44 additions & 0 deletions packages/composable/tests/ComposableCowPoller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const NONCE = 7n
const DEADLINE = 2_000_000_000n
const SIGNATURE = '0x123456'
const REGISTER_DIGEST = '0x19c2f3157fd433af46f24ac718b47fb3b8a9b456d2a2f2b6c31cc07820bec2d1'
const REVOKE_DIGEST = '0x904595cae3f7402646e3fba3b2683afaee0b71fd0eaf835ce0bb9c2bd7a3c9fd'

describe('ComposableCowPoller ABI', () => {
test('keeps the ABI internal', () => {
Expand Down Expand Up @@ -138,6 +139,15 @@ describe('ComposableCowPoller', () => {
expect(() =>
instance.getRegisterTypedData({ chainId: CHAIN_ID, schedule: SCHEDULE, nonce: NONCE, deadline: DEADLINE }),
).toThrow('pollerAddress is required')
expect(() =>
instance.getRevokeTypedData({
chainId: CHAIN_ID,
id: SCHEDULE_ID,
funder: SCHEDULE.funder,
nonce: NONCE,
deadline: DEADLINE,
}),
).toThrow('pollerAddress is required')
})

test('builds the register digest across adapters', () => {
Expand Down Expand Up @@ -178,4 +188,38 @@ describe('ComposableCowPoller', () => {
expect(adapters.viemAdapter.utils.toBigIntish(deadline)).toEqual(DEADLINE)
expect(signature).toEqual(SIGNATURE)
})

test('builds the revoke digest across adapters', () => {
for (const adapter of Object.values(adapters)) {
setGlobalAdapter(adapter)
const typedData = poller.getRevokeTypedData({
chainId: CHAIN_ID,
id: SCHEDULE_ID,
funder: SCHEDULE.funder,
nonce: NONCE,
deadline: DEADLINE,
})

expect(adapter.utils.hashTypedData(typedData.domain, typedData.types, typedData.message)).toEqual(REVOKE_DIGEST)
}
})

test('encodes signed revocation calldata across adapters', () => {
const encodedCalls = []

for (const adapter of Object.values(adapters)) {
setGlobalAdapter(adapter)
encodedCalls.push(poller.encodeRevokeWithSignature(SCHEDULE_ID, DEADLINE, SIGNATURE))
}

expect(new Set(encodedCalls).size).toEqual(1)

const call = adapters.viemAdapter.utils.decodeFunctionData(
ComposableCowPollerAbi,
'revokeWithSignature',
encodedCalls[0]!,
)

expect(Array.from(call)).toEqual([SCHEDULE_ID, BigInt(DEADLINE), SIGNATURE])
})
})
Loading