Skip to content

Commit

Permalink
feat: stateOverride on estimateGas (#2275)
Browse files Browse the repository at this point in the history
* feat: implement stateOverride in estimateGas action

* chore: tweaks

* Update sharp-spies-roll.md

---------

Co-authored-by: Conway <43109407+conwayconstar@users.noreply.github.com>
  • Loading branch information
jxom and conwayconstar committed May 19, 2024
1 parent 3d79e93 commit 1902685
Show file tree
Hide file tree
Showing 11 changed files with 400 additions and 221 deletions.
5 changes: 5 additions & 0 deletions .changeset/sharp-spies-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `stateOverride` on `estimateGas`.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ node_modules
tsconfig*.tsbuildinfo
wagmi
**/trusted-setups/**/*.txt
.idea

# local env files
.env
Expand Down
Binary file modified bun.lockb
Binary file not shown.
26 changes: 26 additions & 0 deletions site/pages/docs/actions/public/estimateGas.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,32 @@ const gas = await publicClient.estimateGas({
})
```

### stateOverride (optional)

- **Type:** [`StateOverride`](/docs/glossary/types#stateoverride)

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.

```ts
const data = await publicClient.estimateGas({
account,
data: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
stateOverride: [ // [!code focus]
{ // [!code focus]
address: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC', // [!code focus]
balance: parseEther('1'), // [!code focus]
stateDiff: [ // [!code focus]
{ // [!code focus]
slot: '0x3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0', // [!code focus]
value: '0x00000000000000000000000000000000000000000000000000000000000001a4', // [!code focus]
}, // [!code focus]
], // [!code focus]
} // [!code focus]
], // [!code focus]
})
```

## JSON-RPC Methods

[`eth_estimateGas`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_estimategas)
130 changes: 1 addition & 129 deletions src/actions/public/call.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import { anvilMainnet } from '../../../test/src/anvil.js'
import {
http,
type Hex,
type StateMapping,
type StateOverride,
createClient,
encodeAbiParameters,
pad,
Expand All @@ -29,12 +27,7 @@ import {
toBlobs,
toHex,
} from '../../index.js'
import {
call,
getRevertErrorData,
parseAccountStateOverride,
parseStateMapping,
} from './call.js'
import { call, getRevertErrorData } from './call.js'

const client = anvilMainnet.getClient({ account: accounts[0].address })

Expand Down Expand Up @@ -1030,124 +1023,3 @@ describe('getRevertErrorData', () => {
).toBe('0x556f1830')
})
})

describe('parsing overrides', () => {
test('state mapping', () => {
const stateMapping: StateMapping = [
{
slot: `0x${fourTwenty}`,
value: `0x${fourTwenty}`,
},
]
expect(parseStateMapping(stateMapping)).toMatchInlineSnapshot(`
{
"0x${fourTwenty}": "0x${fourTwenty}",
}
`)
})

test('state mapping: undefined', () => {
expect(parseStateMapping(undefined)).toMatchInlineSnapshot('undefined')
})

test('state mapping: invalid key', () => {
const stateMapping: StateMapping = [
{
// invalid bytes length
slot: `0x${fourTwenty.slice(0, -1)}`,
value: `0x${fourTwenty}`,
},
]

expect(() =>
parseStateMapping(stateMapping),
).toThrowErrorMatchingInlineSnapshot(`
[InvalidBytesLengthError: Hex is expected to be 66 hex long, but is 65 hex long.
Version: viem@1.0.2]
`)
})

test('state mapping: invalid value', () => {
const stateMapping: StateMapping = [
{
slot: `0x${fourTwenty}`,
value: `0x${fourTwenty.slice(0, -1)}`,
},
]

expect(() =>
parseStateMapping(stateMapping),
).toThrowErrorMatchingInlineSnapshot(`
[InvalidBytesLengthError: Hex is expected to be 66 hex long, but is 65 hex long.
Version: viem@1.0.2]
`)
})

test('args: code', () => {
const stateOverride: Omit<StateOverride[number], 'address'> = {
code: `0x${fourTwenty}`,
}

expect(parseAccountStateOverride(stateOverride)).toMatchInlineSnapshot(`
{
"code": "0x${fourTwenty}",
}
`)

const emptyStateOverride: Omit<StateOverride[number], 'address'> = {
code: undefined,
}

expect(
parseAccountStateOverride(emptyStateOverride),
).toMatchInlineSnapshot(`
{}
`)
})

test('args: balance', () => {
const stateOverride: Omit<StateOverride[number], 'address'> = {
balance: 1n,
}

expect(parseAccountStateOverride(stateOverride)).toMatchInlineSnapshot(`
{
"balance": "0x1",
}
`)

const emptyStateOverride: Omit<StateOverride[number], 'address'> = {
balance: undefined,
}

expect(
parseAccountStateOverride(emptyStateOverride),
).toMatchInlineSnapshot(`
{}
`)
})

test('args: nonce', () => {
const stateOverride: Omit<StateOverride[number], 'address'> = {
nonce: 1,
}

expect(parseAccountStateOverride(stateOverride)).toMatchInlineSnapshot(`
{
"nonce": "0x1",
}
`)

const emptyStateOverride: Omit<StateOverride[number], 'address'> = {
nonce: undefined,
}

expect(
parseAccountStateOverride(emptyStateOverride),
).toMatchInlineSnapshot(`
{}
`)
})
})
99 changes: 8 additions & 91 deletions src/actions/public/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import type { Client } from '../../clients/createClient.js'
import type { Transport } from '../../clients/transports/createTransport.js'
import { multicall3Abi } from '../../constants/abis.js'
import { aggregate3Signature } from '../../constants/contract.js'
import {
InvalidAddressError,
type InvalidAddressErrorType,
} from '../../errors/address.js'
import { BaseError } from '../../errors/base.js'
import {
ChainDoesNotSupportContract,
Expand All @@ -22,27 +18,12 @@ import {
RawContractError,
type RawContractErrorType,
} from '../../errors/contract.js'
import {
InvalidBytesLengthError,
type InvalidBytesLengthErrorType,
} from '../../errors/data.js'
import {
AccountStateConflictError,
type AccountStateConflictErrorType,
StateAssignmentConflictError,
type StateAssignmentConflictErrorType,
} from '../../errors/stateOverride.js'
import type { ErrorType } from '../../errors/utils.js'
import type { BlockTag } from '../../types/block.js'
import type { Chain } from '../../types/chain.js'
import type { Hex } from '../../types/misc.js'
import type {
RpcAccountStateOverride,
RpcStateMapping,
RpcStateOverride,
RpcTransactionRequest,
} from '../../types/rpc.js'
import type { StateMapping, StateOverride } from '../../types/stateOverride.js'
import type { RpcTransactionRequest } from '../../types/rpc.js'
import type { StateOverride } from '../../types/stateOverride.js'
import type { TransactionRequest } from '../../types/transaction.js'
import type { ExactPartial, UnionOmit } from '../../types/utils.js'
import {
Expand All @@ -53,7 +34,6 @@ import {
type EncodeFunctionDataErrorType,
encodeFunctionData,
} from '../../utils/abi/encodeFunctionData.js'
import { isAddress } from '../../utils/address/isAddress.js'
import type { RequestErrorType } from '../../utils/buildRequest.js'
import {
type GetChainContractAddressErrorType,
Expand All @@ -77,6 +57,10 @@ import {
type CreateBatchSchedulerErrorType,
createBatchScheduler,
} from '../../utils/promise/createBatchScheduler.js'
import {
type SerializeStateOverrideErrorType,
serializeStateOverride,
} from '../../utils/stateOverride.js'
import { assertRequest } from '../../utils/transaction/assertRequest.js'
import type {
AssertRequestErrorType,
Expand Down Expand Up @@ -113,7 +97,7 @@ export type CallReturnType = { data: Hex | undefined }

export type CallErrorType = GetCallErrorReturnType<
| ParseAccountErrorType
| ParseStateOverrideErrorType
| SerializeStateOverrideErrorType
| AssertRequestErrorType
| NumberToHexErrorType
| FormatTransactionRequestErrorType
Expand Down Expand Up @@ -177,7 +161,7 @@ export async function call<TChain extends Chain | undefined>(
const blockNumberHex = blockNumber ? numberToHex(blockNumber) : undefined
const block = blockNumberHex || blockTag

const rpcStateOverride = parseStateOverride(stateOverride)
const rpcStateOverride = serializeStateOverride(stateOverride)

const chainFormat = client.chain?.formatters?.transactionRequest?.format
const format = chainFormat || formatTransactionRequest
Expand Down Expand Up @@ -368,70 +352,3 @@ export function getRevertErrorData(err: unknown) {
const error = err.walk() as RawContractError
return typeof error?.data === 'object' ? error.data?.data : error.data
}

export type ParseStateMappingErrorType = InvalidBytesLengthErrorType

export function parseStateMapping(
stateMapping: StateMapping | undefined,
): RpcStateMapping | undefined {
if (!stateMapping || stateMapping.length === 0) return undefined
return stateMapping.reduce((acc, { slot, value }) => {
if (slot.length !== 66)
throw new InvalidBytesLengthError({
size: slot.length,
targetSize: 66,
type: 'hex',
})
if (value.length !== 66)
throw new InvalidBytesLengthError({
size: value.length,
targetSize: 66,
type: 'hex',
})
acc[slot] = value
return acc
}, {} as RpcStateMapping)
}

export type ParseAccountStateOverrideErrorType =
| NumberToHexErrorType
| StateAssignmentConflictErrorType
| ParseStateMappingErrorType

export function parseAccountStateOverride(
args: Omit<StateOverride[number], 'address'>,
): RpcAccountStateOverride {
const { balance, nonce, state, stateDiff, code } = args
const rpcAccountStateOverride: RpcAccountStateOverride = {}
if (code !== undefined) rpcAccountStateOverride.code = code
if (balance !== undefined)
rpcAccountStateOverride.balance = numberToHex(balance)
if (nonce !== undefined) rpcAccountStateOverride.nonce = numberToHex(nonce)
if (state !== undefined)
rpcAccountStateOverride.state = parseStateMapping(state)
if (stateDiff !== undefined) {
if (rpcAccountStateOverride.state) throw new StateAssignmentConflictError()
rpcAccountStateOverride.stateDiff = parseStateMapping(stateDiff)
}
return rpcAccountStateOverride
}

export type ParseStateOverrideErrorType =
| InvalidAddressErrorType
| AccountStateConflictErrorType
| ParseAccountStateOverrideErrorType

export function parseStateOverride(
args?: StateOverride | undefined,
): RpcStateOverride | undefined {
if (!args) return undefined
const rpcStateOverride: RpcStateOverride = {}
for (const { address, ...accountState } of args) {
if (!isAddress(address, { strict: false }))
throw new InvalidAddressError({ address })
if (rpcStateOverride[address])
throw new AccountStateConflictError({ address: address })
rpcStateOverride[address] = parseAccountStateOverride(accountState)
}
return rpcStateOverride
}
28 changes: 28 additions & 0 deletions src/actions/public/estimateGas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { accounts } from '~test/src/constants.js'
import { kzg } from '~test/src/kzg.js'
import { anvilMainnet } from '../../../test/src/anvil.js'
import { privateKeyToAccount } from '../../accounts/privateKeyToAccount.js'
import { maxUint256 } from '../../constants/number.js'

import { toBlobs } from '../../utils/blob/toBlobs.js'
import { toHex } from '../../utils/encoding/toHex.js'
import { parseEther } from '../../utils/unit/parseEther.js'
import { parseGwei } from '../../utils/unit/parseGwei.js'
import { reset } from '../test/reset.js'
Expand Down Expand Up @@ -137,6 +139,32 @@ test('args: blobs', async () => {
).toMatchInlineSnapshot('53001n')
})

test('args: override', async () => {
const transferData =
'0xa9059cbb00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000000de0b6b3a7640000'
const balanceSlot =
'0xc651ee22c6951bb8b5bd29e8210fb394645a94315fe10eff2cc73de1aa75c137'

expect(
await estimateGas(client, {
data: transferData,
account: accounts[0].address,
to: wethContractAddress,
stateOverride: [
{
address: wethContractAddress,
stateDiff: [
{
slot: balanceSlot,
value: toHex(maxUint256),
},
],
},
],
}),
).toMatchInlineSnapshot('51594n')
})

describe('local account', () => {
test('default', async () => {
expect(
Expand Down
Loading

0 comments on commit 1902685

Please sign in to comment.