-
-
Notifications
You must be signed in to change notification settings - Fork 27
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: Add anvil_deal #1492
✨ Feat: Add anvil_deal #1492
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
"@tevm/memory-client": minor | ||
"@tevm/decorators": minor | ||
"@tevm/actions": minor | ||
--- | ||
|
||
Added eth_createAccessList and anvil_deal json-rpc requests | ||
Added MemoryClient.deal action |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { ERC20 } from '@tevm/contract' | ||
import { numberToHex } from '@tevm/utils' | ||
import { encodeFunctionData } from 'viem' | ||
import { setAccountHandler } from '../SetAccount/setAccountHandler.js' | ||
import { ethCreateAccessListProcedure } from '../eth/ethCreateAccessListProcedure.js' | ||
import { anvilSetStorageAtJsonRpcProcedure } from './anvilSetStorageAtProcedure.js' | ||
|
||
/** | ||
* Deals ERC20 tokens to an account by overriding the storage of balanceOf(account) | ||
* @param {import('@tevm/node').TevmNode} client | ||
* @returns {import('./AnvilHandler.js').AnvilDealHandler} | ||
*/ | ||
export const dealHandler = | ||
(client) => | ||
async ({ erc20, account, amount }) => { | ||
if (!erc20) { | ||
return setAccountHandler(client)({ | ||
address: account, | ||
balance: amount, | ||
}) | ||
} | ||
|
||
const value = numberToHex(amount, { size: 32 }) | ||
|
||
// Get storage slots accessed by balanceOf | ||
const accessListResponse = await ethCreateAccessListProcedure(client)({ | ||
method: 'eth_createAccessList', | ||
params: [ | ||
{ | ||
to: erc20, | ||
data: encodeFunctionData({ | ||
abi: ERC20.abi, | ||
functionName: 'balanceOf', | ||
args: [account], | ||
}), | ||
}, | ||
], | ||
id: 1, | ||
jsonrpc: '2.0', | ||
}) | ||
|
||
if (!accessListResponse.result?.accessList) { | ||
throw new Error('Failed to get access list') | ||
} | ||
|
||
// Try each storage slot until we find the right one | ||
for (const { address, storageKeys } of accessListResponse.result.accessList) { | ||
for (const slot of storageKeys) { | ||
await anvilSetStorageAtJsonRpcProcedure(client)({ | ||
method: 'anvil_setStorageAt', | ||
params: [address, slot, value], | ||
id: 1, | ||
jsonrpc: '2.0', | ||
}) | ||
} | ||
} | ||
|
||
return {} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,55 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { hexToBigInt } from 'viem' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { dealHandler } from './anvilDealHandler.js' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* JSON-RPC procedure for anvil_deal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Deals ERC20 tokens to an account by overriding the storage of balanceOf(account) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @param {import('@tevm/node').TevmNode} client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @returns {import('./AnvilProcedure.js').AnvilDealProcedure} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @example | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* ```typescript | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* const response = await client.request({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* method: 'anvil_deal', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* params: [{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* erc20: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // Optional: USDC address | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* account: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* amount: 1000000n // 1 USDC (6 decimals) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure consistency in 'amount' parameter format In the example, Apply this diff to align the code with the example, assuming - amount: hexToBigInt(amount),
+ amount: amount, Alternatively, if Also applies to: 29-29 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* }], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* id: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* jsonrpc: '2.0' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const anvilDealJsonRpcProcedure = (client) => async (request) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const [{ erc20, account, amount }] = request.params | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+24
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add validation for request parameters to prevent runtime errors The code assumes that Apply this diff to add parameter validation: export const anvilDealJsonRpcProcedure = (client) => async (request) => {
+ if (!Array.isArray(request.params) || request.params.length === 0 || typeof request.params[0] !== 'object') {
+ return {
+ jsonrpc: request.jsonrpc,
+ ...(request.id !== undefined ? { id: request.id } : {}),
+ error: {
+ code: -32602,
+ message: 'Invalid params: Expected an array with at least one object.',
+ },
+ }
+ }
const [{ erc20, account, amount }] = request.params
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const result = await dealHandler(client)({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...(erc20 !== undefined ? { erc20 } : {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
account, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
amount: hexToBigInt(amount), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle potential errors when converting 'amount' with 'hexToBigInt' If Apply this diff to include error handling: + let parsedAmount
+ try {
+ parsedAmount = hexToBigInt(amount)
+ } catch (error) {
+ return {
+ jsonrpc: request.jsonrpc,
+ ...(request.id !== undefined ? { id: request.id } : {}),
+ error: {
+ code: -32602,
+ message: 'Invalid amount parameter: must be a valid hex string.',
+ },
+ }
+ } And update the - amount: hexToBigInt(amount),
+ amount: parsedAmount,
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ('errors' in result && result.errors) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @type {import('./AnvilJsonRpcResponse.js').AnvilDealJsonRpcResponse} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const out = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
jsonrpc: request.jsonrpc, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...(request.id !== undefined ? { id: request.id } : {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
method: 'anvil_deal', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove 'method' from JSON-RPC response object According to the JSON-RPC 2.0 specification, the response object should not include a Apply this diff to remove In the error response: jsonrpc: request.jsonrpc,
...(request.id !== undefined ? { id: request.id } : {}),
- method: 'anvil_deal',
error: {
code: result.errors[0]?.code ?? -32000,
message: result.errors[0]?.message ?? result.errors[0]?.name ?? 'An unknown error occurred',
}, In the success response: return {
jsonrpc: request.jsonrpc,
...(request.id !== undefined ? { id: request.id } : {}),
- method: 'anvil_deal',
result: {},
} Also applies to: 52-52 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
error: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// @ts-expect-error being lazy here | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolve TypeScript errors instead of suppressing them The comment Apply this diff to fix the TypeScript error: - // @ts-expect-error being lazy here
code: (result.errors[0]?.code ?? -32000).toString(),
+ // Ensure 'code' is of the correct type
+ code: result.errors[0]?.code ?? -32000,
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
code: (result.errors[0]?.code ?? -32000).toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure 'code' in error object is a number per JSON-RPC specification In the JSON-RPC error response, the Apply this diff to correct the error code type: - code: (result.errors[0]?.code ?? -32000).toString(),
+ code: result.errors[0]?.code ?? -32000, 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
message: result.errors[0]?.message ?? result.errors[0]?.name ?? 'An unknown error occured', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix typo in error message The error message contains a typo: 'An unknown error occured' should be 'An unknown error occurred'. Apply this diff to fix the typo: - message: result.errors[0]?.message ?? result.errors[0]?.name ?? 'An unknown error occured',
+ message: result.errors[0]?.message ?? result.errors[0]?.name ?? 'An unknown error occurred', 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return out | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+26
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for exceptions thrown by 'dealHandler' If Apply this diff to add error handling: export const anvilDealJsonRpcProcedure = (client) => async (request) => {
const [{ erc20, account, amount }] = request.params
+ try {
const result = await dealHandler(client)({
...(erc20 !== undefined ? { erc20 } : {}),
account,
amount: hexToBigInt(amount),
})
if ('errors' in result && result.errors) {
const out = {
jsonrpc: request.jsonrpc,
...(request.id !== undefined ? { id: request.id } : {}),
- method: 'anvil_deal',
error: {
code: result.errors[0]?.code ?? -32000,
message: result.errors[0]?.message ?? result.errors[0]?.name ?? 'An unknown error occurred',
},
}
return out
}
return {
jsonrpc: request.jsonrpc,
...(request.id !== undefined ? { id: request.id } : {}),
- method: 'anvil_deal',
result: {},
}
+ } catch (error) {
+ return {
+ jsonrpc: request.jsonrpc,
+ ...(request.id !== undefined ? { id: request.id } : {}),
+ error: {
+ code: -32603,
+ message: error.message || 'Internal error',
+ },
+ }
+ }
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
jsonrpc: request.jsonrpc, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
...(request.id !== undefined ? { id: request.id } : {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
method: 'anvil_deal', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
result: {}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { createAddress } from '@tevm/address' | ||
import { createTevmNode } from '@tevm/node' | ||
import { TestERC20 } from '@tevm/test-utils' | ||
import { describe, expect, it } from 'vitest' | ||
import { setAccountHandler } from '../SetAccount/setAccountHandler.js' | ||
import { anvilDealJsonRpcProcedure } from './anvilDealProcedure.js' | ||
|
||
describe('anvilDealJsonRpcProcedure', () => { | ||
it('should deal ERC20 tokens', async () => { | ||
const client = createTevmNode() | ||
const erc20 = TestERC20.withAddress(createAddress('0x66a44').toString()) | ||
|
||
// Deploy contract | ||
await setAccountHandler(client)({ | ||
address: erc20.address, | ||
deployedBytecode: erc20.deployedBytecode, | ||
}) | ||
|
||
const result = await anvilDealJsonRpcProcedure(client)({ | ||
method: 'anvil_deal', | ||
params: [ | ||
{ | ||
erc20: erc20.address, | ||
account: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', | ||
amount: '0xf4240', // 1M (6 decimals) | ||
}, | ||
], | ||
id: 1, | ||
jsonrpc: '2.0', | ||
}) | ||
|
||
expect(result).toEqual({ | ||
jsonrpc: '2.0', | ||
id: 1, | ||
method: 'anvil_deal', | ||
result: {}, | ||
}) | ||
}) | ||
|
||
it('should deal native tokens when no erc20 address provided', async () => { | ||
const client = createTevmNode() | ||
|
||
const result = await anvilDealJsonRpcProcedure(client)({ | ||
method: 'anvil_deal', | ||
params: [ | ||
{ | ||
account: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', | ||
amount: '0xde0b6b3a7640000', // 1 ETH | ||
}, | ||
], | ||
id: 1, | ||
jsonrpc: '2.0', | ||
}) | ||
|
||
expect(result).toEqual({ | ||
jsonrpc: '2.0', | ||
id: 1, | ||
method: 'anvil_deal', | ||
result: {}, | ||
}) | ||
}) | ||
Comment on lines
+40
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance test coverage for native token dealing. Similar to the ERC20 test case, this test could benefit from additional verifications and documentation. Consider applying these improvements: it('should deal native tokens when no erc20 address provided', async () => {
const client = createTevmNode()
+ const account = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
+ const amount = '0xde0b6b3a7640000' // 1 ETH (1e18 wei)
+ // Get initial balance
+ const initialBalance = await client.getBalance({
+ address: account,
+ })
const result = await anvilDealJsonRpcProcedure(client)({
method: 'anvil_deal',
params: [
{
- account: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
- amount: '0xde0b6b3a7640000', // 1 ETH
+ account,
+ amount,
},
],
id: 1,
jsonrpc: '2.0',
})
+ // Verify balance increase
+ const finalBalance = await client.getBalance({
+ address: account,
+ })
+ expect(finalBalance - initialBalance).toBe(BigInt(amount))
expect(result).toEqual({
jsonrpc: '2.0',
id: 1,
method: 'anvil_deal',
- result: {},
+ result: {
+ success: true,
+ balance: amount,
+ },
})
})
|
||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enhance error handling for access list retrieval failure
The current error message might not provide sufficient context for debugging. Consider including additional details about the failure to aid in troubleshooting.
Apply this diff to improve the error message: