@tevm/actions
Version:
A typesafe library for writing forge scripts in typescript
142 lines (135 loc) • 4.25 kB
JavaScript
import { createAddress, createContractAddress } from '@tevm/address'
import { InternalError, InternalEvmError, InvalidBytecodeError } from '@tevm/errors'
import { prefundedAccounts } from '@tevm/node'
import { createImpersonatedTx } from '@tevm/tx'
import { getAddress, hexToBytes } from '@tevm/utils'
import { runTx } from '@tevm/vm'
import { getAccountHandler } from '../GetAccount/getAccountHandler.js'
import { setAccountHandler } from '../SetAccount/setAccountHandler.js'
/**
* @internal
* Creates a script with a randomly generated address
* @param {import('@tevm/node').TevmNode} client
* @param {import('@tevm/utils').Hex} [code]
* @param {import('@tevm/utils').Hex} [deployedBytecode]
* @param {import('@tevm/utils').Address} [to]
* @returns {Promise<{errors?: never, address: import('@tevm/utils').Address} | {address?: never, errors: Array<Error>}>}
*/
export const createScript = async (client, code, deployedBytecode, to) => {
const scriptAddress =
to ??
(() => {
const randomBigInt = BigInt(Math.floor(Math.random() * 1_000_000_000_000_000))
return getAddress(createContractAddress(createAddress(`0x${'6969'.repeat(10)}`), randomBigInt).toString())
})()
const vm = await client.getVm()
if (deployedBytecode) {
const setAccountRes = await setAccountHandler(client)({
address: scriptAddress,
deployedBytecode,
throwOnFail: false,
})
if (setAccountRes.errors) {
return {
errors: setAccountRes.errors,
}
}
return {
address: scriptAddress,
}
}
if (!code) {
return {
errors: [new InternalError('Cannot create script without code or deployedBytecode')],
}
}
const parentBlock = await vm.blockchain.getCanonicalHeadBlock()
const priorityFee = 0n
const sender = createAddress(/** @type {import('@tevm/utils').Address}*/ (prefundedAccounts[0]))
let _maxFeePerGas = parentBlock.header.calcNextBaseFee() + priorityFee
const baseFeePerGas = parentBlock.header.baseFeePerGas ?? 0n
if (_maxFeePerGas < baseFeePerGas) {
_maxFeePerGas = baseFeePerGas
}
// TODO tons of dupe
const dataFee = (() => {
let out = 0n
for (const entry of hexToBytes(code) ?? []) {
// 4 gas for zero bytes, 16 gas for non-zero bytes (standard EIP-2028 costs)
out += entry === 0 ? 4n : 16n
}
return out
})()
const baseFee = (() => {
let out = dataFee
// Base transaction cost is 21000 gas
const txFee = 21000n
out += txFee
if (vm.common.ethjsCommon.gteHardfork('homestead')) {
// Contract creation cost is 32000 gas
const txCreationFee = 32000n
out += txCreationFee
}
return out
})()
const minimumGasLimit = baseFee + BigInt(0xffffffff)
const gasLimitWithExecutionBuffer = (minimumGasLimit * 11n) / 10n
try {
const res = await runTx(vm)({
block: parentBlock,
tx: createImpersonatedTx({
maxFeePerGas: _maxFeePerGas,
maxPriorityFeePerGas: 0n,
gasLimit: gasLimitWithExecutionBuffer,
data: code,
impersonatedAddress: sender,
}),
skipNonce: true,
skipBalance: true,
skipBlockGasLimitValidation: true,
skipHardForkValidation: true,
})
if (res.execResult.exceptionError?.error) {
client.logger.error('Failed to create script because deployment of script bytecode failed')
throw new InvalidBytecodeError(res.execResult.exceptionError.error, {
cause: /** @type {any}*/ (res.execResult.exceptionError),
})
}
const deployedAddress = res.createdAddress
if (!deployedAddress) {
return {
errors: [new InternalEvmError('Failed to create script')],
}
}
const account = await getAccountHandler(client)({
throwOnFail: false,
address: /** @type {import('@tevm/utils').Address}*/ (deployedAddress.toString()),
returnStorage: true,
})
if (account.errors) {
return {
errors: account.errors,
}
}
const setAccountRes = await setAccountHandler(client)({
...account,
address: scriptAddress,
throwOnFail: false,
stateDiff: account.storage ?? {},
deployedBytecode: account.deployedBytecode,
})
if (setAccountRes.errors) {
return {
errors: setAccountRes.errors,
}
}
await vm.stateManager.deleteAccount(deployedAddress)
return {
address: to ?? scriptAddress,
}
} catch (e) {
return {
errors: [/** @type any*/ (e)],
}
}
}