-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
1,502 additions
and
64 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import { AbiFunction, P256, Secp256k1, Value } from 'ox' | ||
import { privateKeyToAccount } from 'viem/accounts' | ||
import { getBalance, readContract, setBalance } from 'viem/actions' | ||
import { | ||
type SignAuthorizationParameters, | ||
signAuthorization, | ||
} from 'viem/experimental' | ||
import { describe, expect, test } from 'vitest' | ||
|
||
import { porto } from '../../../test/src/config.js' | ||
import * as DelegatedAccount from './delegatedAccount.js' | ||
import { delegationAbi } from './generated.js' | ||
import * as Key from './key.js' | ||
|
||
const state = porto._internal.store.getState() | ||
const client = state.client.extend(() => ({ mode: 'anvil' })) | ||
|
||
async function setup( | ||
parameters: { | ||
delegate?: SignAuthorizationParameters['delegate'] | undefined | ||
} = {}, | ||
) { | ||
const { delegate } = parameters | ||
|
||
const privateKey = Secp256k1.randomPrivateKey() | ||
const eoa = privateKeyToAccount(privateKey) | ||
|
||
await setBalance(client, { | ||
address: eoa.address, | ||
value: Value.fromEther('2'), | ||
}) | ||
|
||
const authorization = await signAuthorization(client, { | ||
account: eoa, | ||
contractAddress: state.delegation, | ||
delegate, | ||
}) | ||
|
||
return { authorization, eoa } | ||
} | ||
|
||
describe('authorize', () => { | ||
test('sender = EOA, keysToAuthorize = [P256], executor = EOA', async () => { | ||
const { authorization, eoa } = await setup() | ||
|
||
const key = Key.fromP256({ | ||
privateKey: P256.randomPrivateKey(), | ||
role: 'admin', | ||
}) | ||
|
||
const account = DelegatedAccount.from({ | ||
address: eoa.address, | ||
keys: [key], | ||
}) | ||
|
||
await DelegatedAccount.execute(client, { | ||
account, | ||
authorizationList: [authorization], | ||
executor: eoa, | ||
calls: [ | ||
{ | ||
data: AbiFunction.encodeData( | ||
AbiFunction.fromAbi(delegationAbi, 'authorize'), | ||
[Key.serialize(key)], | ||
), | ||
to: account.address, | ||
}, | ||
], | ||
}) | ||
|
||
const serializedKey = await readContract(client, { | ||
abi: delegationAbi, | ||
address: account.address, | ||
functionName: 'keyAt', | ||
args: [0n], | ||
}) | ||
|
||
expect(Key.deserialize(serializedKey)).toEqual({ ...key, sign: undefined }) | ||
}) | ||
}) | ||
|
||
describe('execute', () => { | ||
test.skip('sender = EOA, executor = JSON-RPC', async () => { | ||
const { authorization, eoa } = await setup({ delegate: true }) | ||
|
||
const alice = privateKeyToAccount(Secp256k1.randomPrivateKey()) | ||
const bob = privateKeyToAccount(Secp256k1.randomPrivateKey()) | ||
|
||
const balances_before = await Promise.all([ | ||
getBalance(client, { address: eoa.address }), | ||
getBalance(client, { address: alice.address }), | ||
getBalance(client, { address: bob.address }), | ||
]) | ||
|
||
expect(balances_before[0]).toEqual(Value.fromEther('2')) | ||
expect(balances_before[1]).toEqual(Value.fromEther('0')) | ||
expect(balances_before[2]).toEqual(Value.fromEther('0')) | ||
|
||
await DelegatedAccount.execute(client, { | ||
account: eoa, | ||
authorizationList: [authorization], | ||
executor: null, | ||
calls: [ | ||
{ to: alice.address, value: Value.fromEther('1') }, | ||
{ to: bob.address, value: Value.fromEther('0.5') }, | ||
], | ||
}) | ||
|
||
const balances_after = await Promise.all([ | ||
getBalance(client, { address: eoa.address }), | ||
getBalance(client, { address: alice.address }), | ||
getBalance(client, { address: bob.address }), | ||
]) | ||
|
||
expect(balances_after[0]).not.toBeGreaterThan( | ||
balances_before[0] - Value.fromEther('1'), | ||
) | ||
expect(balances_after[1]).toEqual(Value.fromEther('1')) | ||
expect(balances_after[2]).toEqual(Value.fromEther('0.5')) | ||
}) | ||
|
||
test('sender = EOA, executor = EOA', async () => { | ||
const { authorization, eoa } = await setup() | ||
|
||
const alice = privateKeyToAccount(Secp256k1.randomPrivateKey()) | ||
const bob = privateKeyToAccount(Secp256k1.randomPrivateKey()) | ||
|
||
const balances_before = await Promise.all([ | ||
getBalance(client, { address: eoa.address }), | ||
getBalance(client, { address: alice.address }), | ||
getBalance(client, { address: bob.address }), | ||
]) | ||
|
||
expect(balances_before[0]).toEqual(Value.fromEther('2')) | ||
expect(balances_before[1]).toEqual(Value.fromEther('0')) | ||
expect(balances_before[2]).toEqual(Value.fromEther('0')) | ||
|
||
await DelegatedAccount.execute(client, { | ||
account: eoa, | ||
authorizationList: [authorization], | ||
calls: [ | ||
{ to: alice.address, value: Value.fromEther('1') }, | ||
{ to: bob.address, value: Value.fromEther('0.5') }, | ||
], | ||
}) | ||
|
||
const balances_after = await Promise.all([ | ||
getBalance(client, { address: eoa.address }), | ||
getBalance(client, { address: alice.address }), | ||
getBalance(client, { address: bob.address }), | ||
]) | ||
|
||
expect(balances_after[0]).not.toBeGreaterThan( | ||
balances_before[0] - Value.fromEther('1'), | ||
) | ||
expect(balances_after[1]).toEqual(Value.fromEther('1')) | ||
expect(balances_after[2]).toEqual(Value.fromEther('0.5')) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import type * as Address from 'ox/Address' | ||
import * as Hex from 'ox/Hex' | ||
import type { Account, Chain, Client, Transport } from 'viem' | ||
import { | ||
type ExecuteParameters, | ||
type ExecuteReturnType, | ||
execute as execute_viem, | ||
} from 'viem/experimental/erc7821' | ||
|
||
import { readContract } from 'viem/actions' | ||
|
||
import { delegationAbi } from './generated.js' | ||
import type * as Key from './key.js' | ||
|
||
/** A delegated account. */ | ||
export type DelegatedAccount = { | ||
address: Address.Address | ||
keys: readonly Key.Key[] | ||
label?: string | undefined | ||
} | ||
|
||
/** | ||
* Executes a set of calls on a delegated account. | ||
* | ||
* @param client - Client. | ||
* @param parameters - Execution parameters. | ||
* @returns Transaction hash. | ||
*/ | ||
export async function execute< | ||
const calls extends readonly unknown[], | ||
chain extends Chain | undefined, | ||
>( | ||
client: Client<Transport, chain>, | ||
parameters: execute.Parameters<calls, chain>, | ||
): Promise<execute.ReturnType> { | ||
const { account, executor, ...rest } = parameters | ||
try { | ||
return await execute_viem(client, { | ||
...rest, | ||
address: account.address, | ||
account: typeof executor === 'undefined' ? account : executor, | ||
} as ExecuteParameters) | ||
} catch (error) { | ||
// biome-ignore lint/complexity/noUselessCatch: TODO: Handle contract errors | ||
throw error | ||
} | ||
} | ||
|
||
export declare namespace execute { | ||
export type Parameters< | ||
calls extends readonly unknown[] = readonly unknown[], | ||
chain extends Chain | undefined = Chain | undefined, | ||
> = Omit< | ||
ExecuteParameters<calls, chain>, | ||
'account' | 'address' | 'opData' | ||
> & { | ||
/** | ||
* The delegated account to execute the calls on. | ||
* | ||
* - `DelegatedAccount`: account that was instantiated with `Delegation.create` or `Delegation.from`. | ||
* - `Account`: Viem account that has delegated to Porto. | ||
*/ | ||
account: DelegatedAccount | Account | ||
/** | ||
* The executor of the execute transaction. | ||
* | ||
* - `Account`: execution will be attempted with the specified account. | ||
* - `null`: the transaction will be filled by the JSON-RPC server. | ||
* - `undefined`: execution will be attempted with the `account` value. | ||
*/ | ||
executor?: Account | undefined | null | ||
/** | ||
* The index of the key to use on the delegated account to sign the execution. | ||
*/ | ||
keyIndex?: number | undefined | ||
} | ||
|
||
export type ReturnType = ExecuteReturnType | ||
} | ||
|
||
/** | ||
* Instantiates a delegated account. | ||
* | ||
* @param account - Account to instantiate. | ||
* @returns An instantiated delegated account. | ||
*/ | ||
export function from(account: DelegatedAccount): DelegatedAccount { | ||
return account | ||
} |
Oops, something went wrong.