-
Notifications
You must be signed in to change notification settings - Fork 746
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: draft SNIP-9 implementation (#1111)
- Loading branch information
Showing
12 changed files
with
38,418 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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,119 @@ | ||
import { getStarkKey, utils } from '@scure/starknet'; | ||
import { Provider, Account, cairo } from '../src'; | ||
import { SNIP9_V1_INTERFACE_ID } from '../src/types/outsideExecution'; | ||
import { OutsideExecution } from '../src/utils/outsideExecution'; | ||
import { randomAddress } from '../src/utils/stark'; | ||
import { | ||
compiledArgentAccount, | ||
compiledArgentAccountCasm, | ||
getTestAccount, | ||
getTestProvider, | ||
} from './config/fixtures'; | ||
|
||
const { uint256 } = cairo; | ||
|
||
describe('Account and OutsideExecution', () => { | ||
const devnetERC20Address = '0x49D36570D4E46F48E99674BD3FCC84644DDD6B96F7C741B1562B82F9E004DC7'; | ||
const provider = new Provider(getTestProvider()); | ||
const sender = getTestAccount(provider); | ||
let target: Account; | ||
const targetPK = utils.randomPrivateKey(); | ||
const targetOwner = getStarkKey(targetPK); | ||
// For ERC20 transfer outside call | ||
const recipient = randomAddress(); | ||
const transferAmount = 100; | ||
|
||
beforeAll(async () => { | ||
// Deploy the target account: | ||
const response = await sender.declareAndDeploy( | ||
{ | ||
contract: compiledArgentAccount, | ||
casm: compiledArgentAccountCasm, | ||
constructorCalldata: [0, targetOwner, 1], // signer = targetOwner, guardian = None | ||
}, | ||
{ maxFee: 1e18 } | ||
); | ||
const targetAddress = response.deploy.contract_address; | ||
target = new Account(provider, targetAddress, targetPK); | ||
|
||
// Transfer some tokens to the target account | ||
const transferCall = { | ||
contractAddress: devnetERC20Address, | ||
entrypoint: 'transfer', | ||
calldata: { | ||
recipient: targetAddress, | ||
amount: uint256(transferAmount), | ||
}, | ||
}; | ||
|
||
const { transaction_hash } = await sender.execute(transferCall, undefined, { maxFee: 1e18 }); | ||
await provider.waitForTransaction(transaction_hash); | ||
}); | ||
|
||
it('target account should support SNIP-9', async () => { | ||
const res = await sender.callContract({ | ||
contractAddress: target.address, | ||
entrypoint: 'supports_interface', | ||
calldata: [SNIP9_V1_INTERFACE_ID], | ||
}); | ||
|
||
expect(res[0]).toBe('0x1'); | ||
}); | ||
|
||
it('should execute OutsideExecution flow', async () => { | ||
// Create calls to ERC20 contract: transfer 100 tokens to random address | ||
const calls = [ | ||
{ | ||
contractAddress: devnetERC20Address, | ||
entrypoint: 'transfer', | ||
calldata: { | ||
recipient, | ||
amount: uint256(transferAmount), | ||
}, | ||
}, | ||
]; | ||
|
||
// Prepare time bounds | ||
const now_seconds = Math.floor(Date.now() / 1000); | ||
const hour_ago = (now_seconds - 3600).toString(); | ||
const hour_later = (now_seconds + 3600).toString(); | ||
|
||
// Create OutsideExecution object | ||
const options = { | ||
caller: sender.address, | ||
nonce: await sender.getNonce(), | ||
execute_after: hour_ago, | ||
execute_before: hour_later, | ||
}; | ||
|
||
const outsideExecution = new OutsideExecution(calls, options); | ||
|
||
// Get supported SNIP-9 version of target account | ||
const snip9Version = await target.getSnip9Version(); | ||
expect(snip9Version).toBeDefined(); | ||
|
||
// Sign the outside execution | ||
const data = outsideExecution.getTypedData(await sender.getChainId(), snip9Version!); | ||
const signature = await target.signMessage(data); | ||
|
||
// Execute OutsideExecution from sender account | ||
const response = await sender.executeFromOutside( | ||
outsideExecution, | ||
signature, | ||
target.address, | ||
{}, | ||
snip9Version | ||
); | ||
await provider.waitForTransaction(response.transaction_hash); | ||
|
||
// Check recipient's balance | ||
const balanceRes = await provider.callContract({ | ||
contractAddress: devnetERC20Address, | ||
entrypoint: 'balanceOf', | ||
calldata: { | ||
user: recipient, | ||
}, | ||
}); | ||
expect(balanceRes[0]).toBe('0x64'); // 100 tokens | ||
}); | ||
}); |
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { BigNumberish, RawArgs } from './lib'; | ||
|
||
export interface OutsideExecutionOptions { | ||
caller: string; | ||
nonce: BigNumberish; | ||
execute_after: BigNumberish; | ||
execute_before: BigNumberish; | ||
} | ||
|
||
export interface OutsideCall { | ||
to: string; | ||
selector: BigNumberish; | ||
calldata: RawArgs; | ||
} | ||
|
||
export const SNIP9_V1_INTERFACE_ID = | ||
'0x68cfd18b92d1907b8ba3cc324900277f5a3622099431ea85dd8089255e4181'; | ||
export const SNIP9_V2_INTERFACE_ID = | ||
'0x1d1144bb2138366ff28d8e9ab57456b1d332ac42196230c3a602003c89872'; | ||
|
||
export const OutsideExecutionTypesV1 = { | ||
StarkNetDomain: [ | ||
{ name: 'name', type: 'felt' }, | ||
{ name: 'version', type: 'felt' }, | ||
{ name: 'chainId', type: 'felt' }, | ||
], | ||
OutsideExecution: [ | ||
{ name: 'caller', type: 'felt' }, | ||
{ name: 'nonce', type: 'felt' }, | ||
{ name: 'execute_after', type: 'felt' }, | ||
{ name: 'execute_before', type: 'felt' }, | ||
{ name: 'calls_len', type: 'felt' }, | ||
{ name: 'calls', type: 'OutsideCall*' }, | ||
], | ||
OutsideCall: [ | ||
{ name: 'to', type: 'felt' }, | ||
{ name: 'selector', type: 'felt' }, | ||
{ name: 'calldata_len', type: 'felt' }, | ||
{ name: 'calldata', type: 'felt*' }, | ||
], | ||
}; | ||
|
||
export const OutsideExecutionTypesV2 = { | ||
StarknetDomain: [ | ||
// SNIP-12 revision 1 is used, so should be "StarknetDomain", not "StarkNetDomain" | ||
{ name: 'name', type: 'shortstring' }, | ||
{ name: 'version', type: 'shortstring' }, // set to 2 in v2 | ||
{ name: 'chainId', type: 'shortstring' }, | ||
{ name: 'revision', type: 'shortstring' }, | ||
], | ||
OutsideExecution: [ | ||
{ name: 'Caller', type: 'ContractAddress' }, | ||
{ name: 'Nonce', type: 'felt' }, | ||
{ name: 'Execute After', type: 'u128' }, | ||
{ name: 'Execute Before', type: 'u128' }, | ||
{ name: 'Calls', type: 'Call*' }, | ||
], | ||
Call: [ | ||
{ name: 'To', type: 'ContractAddress' }, | ||
{ name: 'Selector', type: 'selector' }, | ||
{ name: 'Calldata', type: 'felt*' }, | ||
], | ||
}; | ||
|
||
export enum EOutsideExecutionVersion { | ||
UNSUPPORTED = '0', | ||
V1 = '1', | ||
V2 = '2', | ||
} |
Oops, something went wrong.