Skip to content

Commit

Permalink
🐛 Fix: Validate params bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
William Cory authored and William Cory committed Jun 13, 2024
1 parent 9bd957f commit 01d3cb6
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 36 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-pans-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/actions": patch
---

Fixed bug where tevmCall and related methods would improperly validate params such as depth and value. Previously it would not throw a validation error if these numbers are negative
97 changes: 65 additions & 32 deletions packages/actions/src/BaseCall/BaseCallParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,31 @@ import type { Address, BlockOverrideSet, BlockParam, Hex, StateOverrideSet } fro

/**
* Properties shared across call-like params
* - [tevmCallParams]
* - [tevmContractParams]
* - [tevmDeployParams]
* - [tevmScriptParams]
* - [CallParams](https://tevm.sh/reference/tevm/actions/type-aliases/callparams-1/)
* - [ContractParams](https://tevm.sh/reference/tevm/actions/type-aliases/contractparams-1/)
* - [DeployParams](https://tevm.sh/reference/tevm/actions/type-aliases/deployparams-1/)
* - [ScriptParams](https://tevm.sh/reference/tevm/actions/type-aliases/scriptparams-1/)
* @extends BaseParams
*/
export type BaseCallParams<TThrowOnFail extends boolean = boolean> = BaseParams<TThrowOnFail> & {
/**
* Whether to return a complete trace with the call
* Defaults to `false`
* @example
* ```ts
* const {trace} = await tevm.call({address: '0x1234', data: '0x1234', createTrace: true})
* trace.structLogs.forEach(console.log)
* ```
*/
readonly createTrace?: boolean
/**
* Whether to return an access list
* Defaults to `false`
* @example
* ```ts
* const {accessList} = await tevm.call({address: '0x1234', data: '0x1234', createAccessList: true})
* console.log(accessList) // { "0x...": Set(["0x..."])}
* ```
*/
readonly createAccessList?: boolean
/**
Expand All @@ -29,10 +39,20 @@ export type BaseCallParams<TThrowOnFail extends boolean = boolean> = BaseParams<
* - `false`: alias for `never`
* Always will still not include the transaction if it's not valid to be included in
* the chain such as the gas limit being too low.
* If set to true and a tx is submitted the `txHash` will be returned in the response.
* The tx will not be included in the chain until it is mined though
* @example
* ```typescript
* const {txHash} = await client.call({address: '0x1234', data: '0x1234', createTransaction: 'on-success'})
* await client.mine()
* const receipt = await client.getTransactionReceipt({hash: txHash})
* ```
*/
readonly createTransaction?: 'on-success' | 'always' | 'never' | boolean
/**
* The block number or block tag to execute the call at. Defaults to `latest`
* The block number or block tag to execute the call at. Defaults to `latest`.
* Setting to `pending` will run the tx against a block built with the pending tx in the txpool
* that have not yet been mined.
*/
readonly blockTag?: BlockParam
/**
Expand All @@ -43,19 +63,20 @@ export type BaseCallParams<TThrowOnFail extends boolean = boolean> = BaseParams<
readonly skipBalance?: boolean
/**
* The gas limit for the call.
* Defaults to 0xffffff (16_777_215n)
* Defaults to the block gas limit as specified by common or the fork url
*/
readonly gas?: bigint
/**
* The gas price for the call.
*/
readonly gasPrice?: bigint
/**
* Low level control
* Refund counter. Defaults to `0`
*/
readonly gasRefund?: bigint
/**
* The from address for the call. Defaults to the zero address.
* The from address for the call. Defaults to the zero address on reads and account[0] on writes.
* It is also possible to set the `origin` and `caller` addresses seperately using
* those options. Otherwise both are set to the `from` address
*/
Expand All @@ -75,7 +96,8 @@ export type BaseCallParams<TThrowOnFail extends boolean = boolean> = BaseParams<
*/
readonly value?: bigint
/**
* The call depth. Defaults to `0`
* Low level control over the EVM call depth. Useful if you want to simulate an internal call.
* Defaults to `0`
*/
readonly depth?: number
/**
Expand All @@ -84,42 +106,53 @@ export type BaseCallParams<TThrowOnFail extends boolean = boolean> = BaseParams<
readonly selfdestruct?: Set<Address>
/**
* The address of the account that is executing this code (`address(this)`). Defaults to the zero address.
* To is not set for create transactions but required for most transactions
*/
readonly to?: Address
/**
* Versioned hashes for each blob in a blob transaction
* Versioned hashes for each blob in a blob transaction for 4844 transactions
*/
readonly blobVersionedHashes?: Hex[]
// state override description and api is adapted from geth https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-eth
/**
* The state override set is an optional address-to-state mapping, where each entry specifies some state to be ephemerally overridden prior to executing the call. Each address maps to an object containing:
* This option cannot be used when `createTransaction` is set to `true`
*
* The goal of the state override set is manyfold:
* It can be used by DApps to reduce the amount of contract code needed to be deployed on chain. Code that simply returns internal state or does pre-defined validations can be kept off chain and fed to the node on-demand.
* It can be used for smart contract analysis by extending the code deployed on chain with custom methods and invoking them. This avoids having to download and reconstruct the entire state in a sandbox to run custom code against.
* It can be used to debug smart contracts in an already deployed large suite of contracts by selectively overriding some code or state and seeing how execution changes. Specialized tooling will probably be necessary.
* @example
* ```ts
* {
* "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3": {
* "balance": "0xde0b6b3a7640000"
* },
* "0xebe8efa441b9302a0d7eaecc277c09d20d684540": {
* "code": "0x...",
* "state": {
* "0x...": "0x..."
* }
* }
* }
* ```
*/
* The state override set is an optional address-to-state mapping, where each entry specifies some state to be ephemerally overridden prior to executing the call. Each address maps to an object containing:
* This option cannot be used when `createTransaction` is set to `true`
*
* The goal of the state override set is manyfold:
*
* It can be used by DApps to reduce the amount of contract code needed to be deployed on chain. Code that simply returns internal state or does pre-defined validations can be kept off chain and fed to the node on-demand.
* It can be used for smart contract analysis by extending the code deployed on chain with custom methods and invoking them. This avoids having to download and reconstruct the entire state in a sandbox to run custom code against.
* It can be used to debug smart contracts in an already deployed large suite of contracts by selectively overriding some code or state and seeing how execution changes. Specialized tooling will probably be necessary.
* @example
* ```ts
* const stateOverride = {
* "0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3": {
* "balance": "0xde0b6b3a7640000"
* },
* "0xebe8efa441b9302a0d7eaecc277c09d20d684540": {
* "code": "0x...",
* "state": {
* "0x...": "0x..."
* }
* }
* }
* const res = await client.call({address: '0x1234', data: '0x1234', stateOverrideSet: stateOverride})
* ```
*/
readonly stateOverrideSet?: StateOverrideSet
/**
* The fields of this optional object customize the block as part of which the call is simulated. The object contains the following fields:
* This option cannot be used when `createTransaction` is set to `true`
* Setting the block number to past block will not run in the context of that blocks state. To do that fork that block number first.
* @example
* ```ts
* const blockOverride = {
* "number": "0x1b4",
* "hash": "0x
* "parentHash": "0x",
* "nonce": "0x0000000000000042",
* }
* const res = await client.call({address: '0x1234', data: '0x1234', blockOverrideSet: blockOverride})
*/
readonly blockOverrideSet?: BlockOverrideSet
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Bun Snapshot v1, https://goo.gl/fbAQLP

exports[`should match snapshot for invalid parameters 1`] = `
[
[InvalidSkipBalanceError: Expected boolean, received string
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidskipbalanceerror/
Version: 1.1.0.next-73],
[InvalidGasRefundError: Expected bigint, received number
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidgasrefunderror/
Version: 1.1.0.next-73],
[InvalidBlockError: Invalid literal value, expected "latest"
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidblockerror/
Version: 1.1.0.next-73],
[InvalidBlockError: Invalid literal value, expected "earliest"
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidblockerror/
Version: 1.1.0.next-73],
[InvalidBlockError: Invalid literal value, expected "pending"
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidblockerror/
Version: 1.1.0.next-73],
[InvalidBlockError: Invalid literal value, expected "safe"
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidblockerror/
Version: 1.1.0.next-73],
[InvalidBlockError: Invalid literal value, expected "finalized"
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidblockerror/
Version: 1.1.0.next-73],
[InvalidBlockError: Expected bigint, received number
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidblockerror/
Version: 1.1.0.next-73],
[InvalidBlockError: Expected string, received number
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidblockerror/
Version: 1.1.0.next-73],
[InvalidGasPriceError: Expected bigint, received string
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidgaspriceerror/
Version: 1.1.0.next-73],
[InvalidOriginError: Invalid Address invalid address
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidoriginerror/
Version: 1.1.0.next-73],
[InvalidCallerError: Expected string, received number
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidcallererror/
Version: 1.1.0.next-73],
[InvalidGasPriceError: Expected bigint, received string
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidgaspriceerror/
Version: 1.1.0.next-73],
[InvalidValueError: Expected bigint, received string
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidvalueerror/
Version: 1.1.0.next-73],
[InvalidSelfdestructError: Expected set, received string
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidselfdestructerror/
Version: 1.1.0.next-73],
[InvalidToError: Expected string, received number
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidtoerror/
Version: 1.1.0.next-73],
]
`;

exports[`should validate if top level is wrong 1`] = `
[
[InvalidParamsError: Expected object, received string
Docs: https://tevm.sh/reference/tevm/errors/classes/invalidparamserror/
Version: 1.1.0.next-73],
]
`;
2 changes: 2 additions & 0 deletions packages/actions/src/BaseCall/validateBaseCallParams.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { zBaseCallParams } from './zBaseCallParams.js'
*/

/**
* @internal can break on a minor release
* Validates that the parameters are correct with zod
* @param {import('../BaseCall/BaseCallParams.js').BaseCallParams} action
*/
export const validateBaseCallParams = (action) => {
Expand Down
Loading

0 comments on commit 01d3cb6

Please sign in to comment.