Skip to content

Commit

Permalink
✅ Test: Add a bunch of viem api tests (#1109)
Browse files Browse the repository at this point in the history
## Description

_Concise description of proposed changes_

## Testing

Explain the quality checks that have been done on the code changes

## Additional Information

- [ ] I read the [contributing docs](../docs/contributing.md) (if this
is your first contribution)

Your ENS/address:



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Resolved issues with impersonated transactions not being copied
correctly into new blocks.
  - Fixed incorrect reading of some storage slots.
  - Corrected `ethGetCode` to update properly after blockchain refactor.
  - Addressed decoding issues by setting `blobGasUsed` correctly.
  - Fixed last block's state root mutation during new block mining.
- Made `blobGasPrice` and `blobGasUsed` fields optional in transaction
receipts.
  - Improved error messages for storage key length validation.

- **Chores**
- Removed `console.log` statements from various methods to clean up
console output.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: William Cory <williamcory@Williams-MacBook-Pro.local>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed May 21, 2024
1 parent 4d3d1c0 commit 9eeba47
Show file tree
Hide file tree
Showing 34 changed files with 525 additions and 984 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-phones-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/actions-types": patch
---

Fixed bug with blob params in receipt return type being required rather than optional
6 changes: 6 additions & 0 deletions .changeset/eighty-cameras-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tevm/contract": patch
"@tevm/test-utils": patch
---

Fixed copy-pasta bug with copying deployedBytecode into bytecode for simpleContract. The correct bytecode is now set.
5 changes: 5 additions & 0 deletions .changeset/flat-badgers-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/block": patch
---

Fixed bug in @tevm/block where impersonated tx were not being properly copied into newly created blocks
5 changes: 5 additions & 0 deletions .changeset/gentle-cars-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/actions": patch
---

Fixed bug with ethGetCode not getting updated post blockchain refactor
5 changes: 5 additions & 0 deletions .changeset/perfect-suns-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/procedures": patch
---

Fixed bug with blobGasUsed being '0x' rather than undefined. This causes issues with viem decoding.
5 changes: 5 additions & 0 deletions .changeset/popular-eggs-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/receipt-manager": patch
---

Removed console.log
5 changes: 5 additions & 0 deletions .changeset/spicy-rice-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/actions": patch
---

Fix: Bug with last blocks state root accidentally being mutated when mining new blocks
5 changes: 5 additions & 0 deletions .changeset/tame-foxes-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tevm/actions": patch
---

Fixed bug in getStorageAtHandler preventing some storage slots to not be read correctly.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/actions-types/src/common/TransactionReceiptResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ export type TransactionReceiptResult = {
readonly to: Hex
readonly transactionHash: Hex
readonly transactionIndex: bigint
readonly blobGasUsed: bigint
readonly blobGasPrice: bigint
readonly blobGasUsed?: bigint
readonly blobGasPrice?: bigint
}
1 change: 1 addition & 0 deletions packages/actions/src/eth/ethGetTransactionReceipt.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export const ethGetTransactionReceiptHandler = (client) => async (params) => {
: /** @type any*/ (tx).maxFeePerGas - (block.header.baseFeePerGas ?? 0n) + (block.header.baseFeePerGas ?? 0n)

vm.common.setHardfork(tx.common.hardfork())
await vm.stateManager.setStateRoot(parentBlock.header.stateRoot)
// Run tx through copied vm to get tx gasUsed and createdAddress
const runBlockResult = await vm.runBlock({
block,
Expand Down
4 changes: 2 additions & 2 deletions packages/actions/src/eth/getCodeHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ export const getCodeHandler =
async (params) => {
const vm = await getVm()
const tag = params.blockTag ?? 'pending'
if (tag === 'pending') {
if (tag === 'latest') {
return bytesToHex(await vm.stateManager.getContractCode(EthjsAddress.fromString(params.address)))
}
if (!forkUrl) {
throw new NoForkUrlSetError(
`Cannot eth_getCode for block tag ${tag} without a fork url set. Try passing in a forkUrl option to getCodeHandler.`,
'getCode is not supported for any block tags other than latest atm. This will be fixed in the next few releases',
)
}
const fetcher = createJsonRpcFetcher(forkUrl)
Expand Down
11 changes: 8 additions & 3 deletions packages/actions/src/eth/getStorageAtHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ export const getStorageAtHandler =
async (params) => {
const vm = await getVm()
const tag = params.blockTag ?? 'pending'
if (tag === 'pending' || tag === 'latest') {
if (tag === 'latest') {
return bytesToHex(
await vm.stateManager.getContractStorage(EthjsAddress.fromString(params.address), hexToBytes(params.position)),
await vm.stateManager.getContractStorage(
EthjsAddress.fromString(params.address),
hexToBytes(params.position, { size: 32 }),
),
)
}
if (!forkUrl) {
throw new NoForkUrlSetError('Fork URL is required if tag is not "latest" or "pending"')
throw new NoForkUrlSetError(
'No forkurl set. Block tags other than latest for getStorageAt has not yet been implemented',
)
}
const fetcher = createJsonRpcFetcher(forkUrl)
return fetcher
Expand Down
179 changes: 96 additions & 83 deletions packages/actions/src/tevm/mineHandler.js
Original file line number Diff line number Diff line change
@@ -1,99 +1,112 @@
import { bytesToHex } from '@tevm/utils'
import { bytesToHex, hexToBytes } from '@tevm/utils'
import { maybeThrowOnFail } from './maybeThrowOnFail.js'
import { validateMineParams } from '@tevm/zod'

// TODO Errors can leave us in bad states

/**
* @param {import("@tevm/base-client").BaseClient} client
* @param {object} [options]
* @param {boolean} [options.throwOnFail] whether to default to throwing or not when errors occur
* @returns {import('@tevm/actions-types').MineHandler}
*/
* @param {import("@tevm/base-client").BaseClient} client
* @param {object} [options]
* @param {boolean} [options.throwOnFail] whether to default to throwing or not when errors occur
* @returns {import('@tevm/actions-types').MineHandler}
*/
export const mineHandler =
(client, options = {}) =>
async ({ throwOnFail = options.throwOnFail ?? true, ...params } = {}) => {
client.logger.debug({ throwOnFail, ...params }, 'mineHandler called with params')
const errors = validateMineParams(params)
if (errors.length > 0) {
return maybeThrowOnFail(throwOnFail, { errors })
}
const { interval = 1, blockCount = 1 } = params
(client, options = {}) =>
async ({ throwOnFail = options.throwOnFail ?? true, ...params } = {}) => {
client.logger.debug({ throwOnFail, ...params }, 'mineHandler called with params')
const errors = validateMineParams(params)
if (errors.length > 0) {
return maybeThrowOnFail(throwOnFail, { errors })
}
const { interval = 1, blockCount = 1 } = params

/**
* @type {Array<import('@tevm/utils').Hex>}
*/
const blockHashes = []

client.logger.debug({ blockCount }, 'processing txs')
const pool = await client.getTxPool()
const originalVm = await client.getVm()
const vm = await originalVm.deepCopy()

for (let count = 0; count < blockCount; count++) {
const receiptsManager = await client.getReceiptsManager()
const parentBlock = await vm.blockchain.getCanonicalHeadBlock()

let timestamp = Math.max(Math.floor(Date.now() / 1000), Number(parentBlock.header.timestamp))
timestamp = count === 0 ? timestamp : timestamp + interval

/**
* @type {Array<import('@tevm/utils').Hex>}
*/
const blockHashes = []
const blockBuilder = await vm.buildBlock({
parentBlock,
headerData: {
timestamp,
number: parentBlock.header.number + 1n,
// The following 2 are currently not supported
// difficulty: undefined,
// coinbase,
gasLimit: parentBlock.header.gasLimit,
baseFeePerGas: parentBlock.header.calcNextBaseFee(),
},
blockOpts: {
// Proof of authority not currently supported
// cliqueSigner,
// proof of work not currently supported
//calcDifficultyFromHeader,
//ck
freeze: false,
setHardfork: false,
putBlockIntoBlockchain: false,
common: vm.common
},
})
// TODO create a Log manager
const orderedTx = await pool.txsByPriceAndNonce({ baseFee: parentBlock.header.calcNextBaseFee() })

client.logger.debug({ blockCount }, 'processing txs')
let index = 0
let blockFull = false
/**
* @type {Array<import('@tevm/receipt-manager').TxReceipt>}
*/
const receipts = []
while (index < orderedTx.length && !blockFull) {
const nextTx = /** @type {import('@tevm/tx').TypedTransaction}*/(orderedTx[index])
client.logger.debug(bytesToHex(nextTx.hash()), 'new tx added')
const txResult = await blockBuilder.addTransaction(nextTx, {
skipHardForkValidation: true,
})
if (txResult.execResult.exceptionError) {
if (txResult.execResult.exceptionError.error === 'out of gas') {
client.logger.debug(txResult.execResult.executionGasUsed, 'out of gas')
}
client.logger.debug(txResult.execResult.exceptionError, `There was an exception when building block for tx ${bytesToHex(nextTx.hash())}`)
}
receipts.push(txResult.receipt)
index++
}
await vm.stateManager.checkpoint()
const createNewStateRoot = true
await vm.stateManager.commit(createNewStateRoot)
const block = await blockBuilder.build()
await Promise.all([
receiptsManager.saveReceipts(block, receipts),
vm.blockchain.putBlock(block),
])
pool.removeNewBlockTxs([block])

for (let count = 0; count < blockCount; count++) {
const pool = await client.getTxPool()
const vm = await client.getVm()
const receiptsManager = await client.getReceiptsManager()
const parentBlock = await vm.blockchain.getCanonicalHeadBlock()
blockHashes.push(bytesToHex(block.hash()))

let timestamp = Math.max(Math.floor(Date.now() / 1000), Number(parentBlock.header.timestamp))
timestamp = count === 0 ? timestamp : timestamp + interval
const value = vm.stateManager._baseState.stateRoots.get(bytesToHex(block.header.stateRoot))

const blockBuilder = await vm.buildBlock({
parentBlock,
headerData: {
timestamp,
number: parentBlock.header.number + 1n,
// The following 2 are currently not supported
// difficulty: undefined,
// coinbase,
gasLimit: parentBlock.header.gasLimit,
baseFeePerGas: parentBlock.header.calcNextBaseFee(),
},
blockOpts: {
// Proof of authority not currently supported
// cliqueSigner,
// proof of work not currently supported
//calcDifficultyFromHeader,
//ck
freeze: false,
setHardfork: false,
putBlockIntoBlockchain: false,
common: vm.common
},
})
// TODO create a Log manager
const orderedTx = await pool.txsByPriceAndNonce({ baseFee: parentBlock.header.calcNextBaseFee() })
if (!value) {
throw new Error('InternalError: State root not found in mineHandler. This indicates a potential inconsistency in state management.')
}

let index = 0
let blockFull = false
/**
* @type {Array<import('@tevm/receipt-manager').TxReceipt>}
*/
const receipts = []
while (index < orderedTx.length && !blockFull) {
const nextTx = /** @type {import('@tevm/tx').TypedTransaction}*/(orderedTx[index])
client.logger.debug(bytesToHex(nextTx.hash()), 'new tx added')
const txResult = await blockBuilder.addTransaction(nextTx, {
skipHardForkValidation: true,
})
if (txResult.execResult.exceptionError) {
if (txResult.execResult.exceptionError.error === 'out of gas') {
client.logger.debug(txResult.execResult.executionGasUsed, 'out of gas')
originalVm.stateManager.saveStateRoot(block.header.stateRoot, value)
}
client.logger.debug(txResult.execResult.exceptionError, `There was an exception when building block for tx ${bytesToHex(nextTx.hash())}`)
}
receipts.push(txResult.receipt)
index++
}
await vm.stateManager.checkpoint()
const createNewStateRoot = true
await vm.stateManager.commit(createNewStateRoot)
const block = await blockBuilder.build()
await Promise.all([
receiptsManager.saveReceipts(block, receipts),
vm.blockchain.putBlock(block),
])
pool.removeNewBlockTxs([block])
originalVm.blockchain = vm.blockchain
originalVm.evm.blockchain = vm.evm.blockchain
await originalVm.stateManager.setStateRoot(hexToBytes(vm.stateManager._baseState.getCurrentStateRoot()))

blockHashes.push(bytesToHex(block.hash()))
}
return { blockHashes }
return { blockHashes }
}
2 changes: 1 addition & 1 deletion packages/actions/src/tevm/mineHandler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe(mineHandler.name, () => {
expect(
await getBlockNumber(client)
).toBe(bn + 1n)
}, { timeout: 10_000 })
}, { timeout: 20_000 })

it('can be passed blockCount and interval props', async () => {
const client = createBaseClient()
Expand Down

0 comments on commit 9eeba47

Please sign in to comment.