mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-27 20:45:48 +00:00
implement evmstate CLI for evmlab
This commit is contained in:
parent
1c1c6f56cc
commit
ec59819953
9
Makefile
9
Makefile
@ -245,10 +245,19 @@ t8n: | build deps
|
|||||||
t8n_test: | build deps t8n
|
t8n_test: | build deps t8n
|
||||||
$(ENV_SCRIPT) nim c -r $(NIM_PARAMS) -d:chronicles_default_output_device=stderr "tools/t8n/$@.nim"
|
$(ENV_SCRIPT) nim c -r $(NIM_PARAMS) -d:chronicles_default_output_device=stderr "tools/t8n/$@.nim"
|
||||||
|
|
||||||
|
# builds evm state test tool
|
||||||
|
evmstate: | build deps
|
||||||
|
$(ENV_SCRIPT) nim c $(NIM_PARAMS) -d:chronicles_enabled=off "tools/evmstate/$@.nim"
|
||||||
|
|
||||||
|
# builds and runs evm state tool test suite
|
||||||
|
evmstate_test: | build deps evmstate
|
||||||
|
$(ENV_SCRIPT) nim c -r $(NIM_PARAMS) "tools/evmstate/$@.nim"
|
||||||
|
|
||||||
# usual cleaning
|
# usual cleaning
|
||||||
clean: | clean-common
|
clean: | clean-common
|
||||||
rm -rf build/{nimbus,fluffy,lc_proxy,$(TOOLS_CSV),all_tests,test_kvstore_rocksdb,test_rpc,all_fluffy_tests,all_fluffy_portal_spec_tests,test_portal_testnet,portalcli,blockwalk,eth_data_exporter,utp_test_app,utp_test,*.dSYM}
|
rm -rf build/{nimbus,fluffy,lc_proxy,$(TOOLS_CSV),all_tests,test_kvstore_rocksdb,test_rpc,all_fluffy_tests,all_fluffy_portal_spec_tests,test_portal_testnet,portalcli,blockwalk,eth_data_exporter,utp_test_app,utp_test,*.dSYM}
|
||||||
rm -rf tools/t8n/{t8n,t8n_test}
|
rm -rf tools/t8n/{t8n,t8n_test}
|
||||||
|
rm -rf tools/evmstate/{evmstate,evmstate_test}
|
||||||
ifneq ($(USE_LIBBACKTRACE), 0)
|
ifneq ($(USE_LIBBACKTRACE), 0)
|
||||||
+ $(MAKE) -C vendor/nim-libbacktrace clean $(HANDLE_OUTPUT)
|
+ $(MAKE) -C vendor/nim-libbacktrace clean $(HANDLE_OUTPUT)
|
||||||
endif
|
endif
|
||||||
|
80
tools/evmstate/config.nim
Normal file
80
tools/evmstate/config.nim
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import
|
||||||
|
std/[os, options],
|
||||||
|
confutils, confutils/defs
|
||||||
|
|
||||||
|
export
|
||||||
|
confutils, defs
|
||||||
|
|
||||||
|
type
|
||||||
|
StateConf* = object of RootObj
|
||||||
|
dumpEnabled* {.
|
||||||
|
desc: "dumps the state after the run"
|
||||||
|
defaultValue: false
|
||||||
|
name: "dump" }: bool
|
||||||
|
|
||||||
|
jsonEnabled* {.
|
||||||
|
desc: "output trace logs in machine readable format (json)"
|
||||||
|
defaultValue: false
|
||||||
|
name: "json" }: bool
|
||||||
|
|
||||||
|
debugEnabled* {.
|
||||||
|
desc: "output full trace logs"
|
||||||
|
defaultValue: false
|
||||||
|
name: "debug" }: bool
|
||||||
|
|
||||||
|
disableMemory* {.
|
||||||
|
desc: "disable memory output"
|
||||||
|
defaultValue: true
|
||||||
|
name: "nomemory" }: bool
|
||||||
|
|
||||||
|
disableStack* {.
|
||||||
|
desc: "disable stack output"
|
||||||
|
defaultValue: false
|
||||||
|
name: "nostack" }: bool
|
||||||
|
|
||||||
|
disableStorage* {.
|
||||||
|
desc: "disable storage output"
|
||||||
|
defaultValue: false
|
||||||
|
name: "nostorage" }: bool
|
||||||
|
|
||||||
|
disableReturnData* {.
|
||||||
|
desc: "enable return data output"
|
||||||
|
defaultValue: true
|
||||||
|
name: "noreturndata" }: bool
|
||||||
|
|
||||||
|
fork* {.
|
||||||
|
desc: "choose which fork to be tested"
|
||||||
|
defaultValue: ""
|
||||||
|
name: "fork" }: string
|
||||||
|
|
||||||
|
index* {.
|
||||||
|
desc: "if index is unset, all subtest in the fork will be tested"
|
||||||
|
defaultValue: none(int)
|
||||||
|
name: "index" }: Option[int]
|
||||||
|
|
||||||
|
pretty* {.
|
||||||
|
desc: "pretty print the trace result"
|
||||||
|
defaultValue: false
|
||||||
|
name: "pretty" }: bool
|
||||||
|
|
||||||
|
verbosity* {.
|
||||||
|
desc: "sets the verbosity level"
|
||||||
|
defaultValue: 0
|
||||||
|
name: "verbosity" }: int
|
||||||
|
|
||||||
|
inputFile* {.
|
||||||
|
desc: "json file contains state test data"
|
||||||
|
argument }: string
|
||||||
|
|
||||||
|
const
|
||||||
|
Copyright = "Copyright (c) 2022 Status Research & Development GmbH"
|
||||||
|
Version = "Nimbus-evmstate 0.1.0"
|
||||||
|
|
||||||
|
proc init*(_: type StateConf, cmdLine = commandLineParams()): StateConf =
|
||||||
|
{.push warning[ProveInit]: off.}
|
||||||
|
result = StateConf.load(
|
||||||
|
cmdLine,
|
||||||
|
version = Version,
|
||||||
|
copyrightBanner = Version & "\n" & Copyright
|
||||||
|
)
|
||||||
|
{.pop.}
|
309
tools/evmstate/evmstate.nim
Normal file
309
tools/evmstate/evmstate.nim
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
import
|
||||||
|
std/[json, strutils, sets, tables, options],
|
||||||
|
eth/[common, keys],
|
||||||
|
stew/[results, byteutils],
|
||||||
|
stint,
|
||||||
|
eth/trie/[db, trie_defs],
|
||||||
|
../../nimbus/[forks, vm_types, chain_config, vm_state],
|
||||||
|
../../nimbus/db/[db_chain, accounts_cache],
|
||||||
|
../../nimbus/transaction,
|
||||||
|
../../nimbus/p2p/executor,
|
||||||
|
"."/[config, helpers]
|
||||||
|
|
||||||
|
type
|
||||||
|
StateContext = object
|
||||||
|
name: string
|
||||||
|
header: BlockHeader
|
||||||
|
tx: Transaction
|
||||||
|
expectedHash: Hash256
|
||||||
|
expectedLogs: Hash256
|
||||||
|
fork: Fork
|
||||||
|
index: int
|
||||||
|
tracerFlags: set[TracerFlags]
|
||||||
|
error: string
|
||||||
|
|
||||||
|
DumpAccount = ref object
|
||||||
|
balance : UInt256
|
||||||
|
nonce : AccountNonce
|
||||||
|
root : Hash256
|
||||||
|
codeHash: Hash256
|
||||||
|
code : Blob
|
||||||
|
key : Hash256
|
||||||
|
storage : Table[UInt256, UInt256]
|
||||||
|
|
||||||
|
StateDump = ref object
|
||||||
|
root: Hash256
|
||||||
|
accounts: Table[EthAddress, DumpAccount]
|
||||||
|
|
||||||
|
StateResult = object
|
||||||
|
name : string
|
||||||
|
pass : bool
|
||||||
|
fork : string
|
||||||
|
error: string
|
||||||
|
state: StateDump
|
||||||
|
|
||||||
|
proc extractNameAndFixture(ctx: var StateContext, n: JsonNode): JsonNode =
|
||||||
|
for label, child in n:
|
||||||
|
result = child
|
||||||
|
ctx.name = label
|
||||||
|
return
|
||||||
|
doAssert(false, "unreachable")
|
||||||
|
|
||||||
|
proc parseTx(txData, index: JsonNode): Transaction =
|
||||||
|
let
|
||||||
|
dataIndex = index["data"].getInt
|
||||||
|
gasIndex = index["gas"].getInt
|
||||||
|
valIndex = index["value"].getInt
|
||||||
|
parseTx(txData, dataIndex, gasIndex, valIndex)
|
||||||
|
|
||||||
|
proc toBytes(x: string): seq[byte] =
|
||||||
|
result = newSeq[byte](x.len)
|
||||||
|
for i in 0..<x.len: result[i] = x[i].byte
|
||||||
|
|
||||||
|
method getAncestorHash*(vmState: BaseVMState; blockNumber: BlockNumber): Hash256 {.gcsafe.} =
|
||||||
|
if blockNumber >= vmState.blockNumber:
|
||||||
|
return
|
||||||
|
elif blockNumber < 0:
|
||||||
|
return
|
||||||
|
elif blockNumber < vmState.blockNumber - 256:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
return keccakHash(toBytes($blockNumber))
|
||||||
|
|
||||||
|
proc verifyResult(ctx: var StateContext, vmState: BaseVMState) =
|
||||||
|
ctx.error = ""
|
||||||
|
let obtainedHash = vmState.readOnlyStateDB.rootHash
|
||||||
|
if obtainedHash != ctx.expectedHash:
|
||||||
|
ctx.error = "post state root mismatch: got $1, want $2" %
|
||||||
|
[$obtainedHash, $ctx.expectedHash]
|
||||||
|
return
|
||||||
|
|
||||||
|
let logEntries = vmState.getAndClearLogEntries()
|
||||||
|
let actualLogsHash = rlpHash(logEntries)
|
||||||
|
if actualLogsHash != ctx.expectedLogs:
|
||||||
|
ctx.error = "post state log hash mismatch: got $1, want $2" %
|
||||||
|
[$actualLogsHash, $ctx.expectedLogs]
|
||||||
|
return
|
||||||
|
|
||||||
|
proc `%`(x: UInt256): JsonNode =
|
||||||
|
%("0x" & x.toHex)
|
||||||
|
|
||||||
|
proc `%`(x: Blob): JsonNode =
|
||||||
|
%("0x" & x.toHex)
|
||||||
|
|
||||||
|
proc `%`(x: Hash256): JsonNode =
|
||||||
|
%("0x" & x.data.toHex)
|
||||||
|
|
||||||
|
proc `%`(x: AccountNonce): JsonNode =
|
||||||
|
%("0x" & x.toHex)
|
||||||
|
|
||||||
|
proc `%`(x: Table[UInt256, UInt256]): JsonNode =
|
||||||
|
result = newJObject()
|
||||||
|
for k, v in x:
|
||||||
|
result["0x" & k.toHex] = %(v)
|
||||||
|
|
||||||
|
proc `%`(x: DumpAccount): JsonNode =
|
||||||
|
result = %{
|
||||||
|
"balance" : %(x.balance),
|
||||||
|
"nonce" : %(x.nonce),
|
||||||
|
"root" : %(x.root),
|
||||||
|
"codeHash": %(x.codeHash),
|
||||||
|
"code" : %(x.code),
|
||||||
|
"key" : %(x.key)
|
||||||
|
}
|
||||||
|
if x.storage.len > 0:
|
||||||
|
result["storage"] = %(x.storage)
|
||||||
|
|
||||||
|
proc `%`(x: Table[EthAddress, DumpAccount]): JsonNode =
|
||||||
|
result = newJObject()
|
||||||
|
for k, v in x:
|
||||||
|
result["0x" & k.toHex] = %(v)
|
||||||
|
|
||||||
|
proc `%`(x: StateDump): JsonNode =
|
||||||
|
result = %{
|
||||||
|
"root": %(x.root),
|
||||||
|
"accounts": %(x.accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
proc writeResultToStdout(stateRes: seq[StateResult]) =
|
||||||
|
var n = newJArray()
|
||||||
|
for res in stateRes:
|
||||||
|
let z = %{
|
||||||
|
"name" : %(res.name),
|
||||||
|
"pass" : %(res.pass),
|
||||||
|
"fork" : %(res.fork),
|
||||||
|
"error": %(res.error)
|
||||||
|
}
|
||||||
|
if res.state.isNil.not:
|
||||||
|
z["state"] = %(res.state)
|
||||||
|
n.add(z)
|
||||||
|
|
||||||
|
stdout.write(n.pretty)
|
||||||
|
stdout.write("\n")
|
||||||
|
|
||||||
|
proc dumpAccounts(db: AccountsCache): Table[EthAddress, DumpAccount] =
|
||||||
|
for accAddr in db.addresses():
|
||||||
|
let acc = DumpAccount(
|
||||||
|
balance : db.getBalance(accAddr),
|
||||||
|
nonce : db.getNonce(accAddr),
|
||||||
|
root : db.getStorageRoot(accAddr),
|
||||||
|
codeHash: db.getCodeHash(accAddr),
|
||||||
|
code : db.getCode(accAddr),
|
||||||
|
key : keccakHash(accAddr)
|
||||||
|
)
|
||||||
|
for k, v in db.storage(accAddr):
|
||||||
|
acc.storage[k] = v
|
||||||
|
result[accAddr] = acc
|
||||||
|
|
||||||
|
proc dumpState(vmState: BaseVMState): StateDump =
|
||||||
|
StateDump(
|
||||||
|
root: vmState.readOnlyStateDB.rootHash,
|
||||||
|
accounts: dumpAccounts(vmState.stateDB)
|
||||||
|
)
|
||||||
|
|
||||||
|
proc writeTraceToStderr(vmState: BaseVMState, pretty: bool) =
|
||||||
|
let trace = vmState.getTracingResult()
|
||||||
|
if pretty:
|
||||||
|
stderr.writeLine(trace.pretty)
|
||||||
|
else:
|
||||||
|
let logs = trace["structLogs"]
|
||||||
|
trace.delete("structLogs")
|
||||||
|
for x in logs:
|
||||||
|
stderr.writeLine($x)
|
||||||
|
stderr.writeLine($trace)
|
||||||
|
|
||||||
|
proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult =
|
||||||
|
let
|
||||||
|
chainParams = NetworkParams(config: chainConfigForNetwork(MainNet))
|
||||||
|
chainDB = newBaseChainDB(newMemoryDB(), pruneTrie = false, params = chainParams)
|
||||||
|
parent = BlockHeader(stateRoot: emptyRlpHash)
|
||||||
|
|
||||||
|
# set total difficulty
|
||||||
|
chainDB.setScore(parent.blockHash, 0.u256)
|
||||||
|
|
||||||
|
if ctx.fork >= FkParis:
|
||||||
|
chainDB.config.terminalTotalDifficulty = some(0.u256)
|
||||||
|
|
||||||
|
let vmState = BaseVMState.new(
|
||||||
|
parent = parent,
|
||||||
|
header = ctx.header,
|
||||||
|
chainDB = chainDB,
|
||||||
|
tracerFlags = ctx.tracerFlags,
|
||||||
|
pruneTrie = chainDB.pruneTrie)
|
||||||
|
|
||||||
|
var gasUsed: GasInt
|
||||||
|
let sender = ctx.tx.getSender()
|
||||||
|
|
||||||
|
vmState.mutateStateDB:
|
||||||
|
setupStateDB(pre, db)
|
||||||
|
db.persist() # settle accounts storage
|
||||||
|
|
||||||
|
defer:
|
||||||
|
ctx.verifyResult(vmState)
|
||||||
|
result = StateResult(
|
||||||
|
name : ctx.name,
|
||||||
|
pass : ctx.error.len == 0,
|
||||||
|
fork : toString(ctx.fork),
|
||||||
|
error: ctx.error
|
||||||
|
)
|
||||||
|
if conf.dumpEnabled:
|
||||||
|
result.state = dumpState(vmState)
|
||||||
|
if conf.jsonEnabled:
|
||||||
|
writeTraceToStderr(vmState, conf.pretty)
|
||||||
|
|
||||||
|
let rc = vmState.processTransaction(
|
||||||
|
ctx.tx, sender, ctx.header, ctx.fork)
|
||||||
|
if rc.isOk:
|
||||||
|
gasUsed = rc.value
|
||||||
|
|
||||||
|
let miner = ctx.header.coinbase
|
||||||
|
if miner in vmState.selfDestructs:
|
||||||
|
vmState.mutateStateDB:
|
||||||
|
db.addBalance(miner, 0.u256)
|
||||||
|
if ctx.fork >= FkSpurious:
|
||||||
|
if db.isEmptyAccount(miner):
|
||||||
|
db.deleteAccount(miner)
|
||||||
|
db.persist()
|
||||||
|
|
||||||
|
proc toTracerFlags(conf: Stateconf): set[TracerFlags] =
|
||||||
|
result = {
|
||||||
|
TracerFlags.DisableStateDiff,
|
||||||
|
TracerFlags.EnableTracing
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.disableMemory : result.incl TracerFlags.DisableMemory
|
||||||
|
if conf.disablestack : result.incl TracerFlags.DisableStack
|
||||||
|
if conf.disableReturnData: result.incl TracerFlags.DisableReturnData
|
||||||
|
if conf.disableStorage : result.incl TracerFlags.DisableStorage
|
||||||
|
|
||||||
|
template hasError(ctx: StateContext): bool =
|
||||||
|
ctx.error.len > 0
|
||||||
|
|
||||||
|
proc prepareAndRun(ctx: var StateContext, conf: StateConf): bool =
|
||||||
|
let
|
||||||
|
fixture = json.parseFile(conf.inputFile)
|
||||||
|
n = ctx.extractNameAndFixture(fixture)
|
||||||
|
txData = n["transaction"]
|
||||||
|
post = n["post"]
|
||||||
|
pre = n["pre"]
|
||||||
|
|
||||||
|
ctx.header = parseHeader(n["env"])
|
||||||
|
|
||||||
|
if conf.debugEnabled or conf.jsonEnabled:
|
||||||
|
ctx.tracerFlags = toTracerFlags(conf)
|
||||||
|
|
||||||
|
var
|
||||||
|
stateRes = newSeqOfCap[StateResult](post.len)
|
||||||
|
index = 1
|
||||||
|
hasError = false
|
||||||
|
|
||||||
|
template prepareFork(forkName: string) =
|
||||||
|
let fork = parseFork(forkName)
|
||||||
|
doAssert(fork.isSome, "unsupported fork: " & forkName)
|
||||||
|
ctx.fork = fork.get()
|
||||||
|
ctx.index = index
|
||||||
|
inc index
|
||||||
|
|
||||||
|
template runSubTest(subTest: JsonNode) =
|
||||||
|
ctx.expectedHash = Hash256.fromJson(subTest["hash"])
|
||||||
|
ctx.expectedLogs = Hash256.fromJson(subTest["logs"])
|
||||||
|
ctx.tx = parseTx(txData, subTest["indexes"])
|
||||||
|
let res = ctx.runExecution(conf, pre)
|
||||||
|
stateRes.add res
|
||||||
|
hasError = hasError or ctx.hasError
|
||||||
|
|
||||||
|
if conf.fork.len > 0:
|
||||||
|
if not post.hasKey(conf.fork):
|
||||||
|
stdout.writeLine("selected fork not available: " & conf.fork)
|
||||||
|
return false
|
||||||
|
|
||||||
|
let forkData = post[conf.fork]
|
||||||
|
prepareFork(conf.fork)
|
||||||
|
if conf.index.isNone:
|
||||||
|
for subTest in forkData:
|
||||||
|
runSubTest(subTest)
|
||||||
|
else:
|
||||||
|
let index = conf.index.get()
|
||||||
|
if index > forkData.len or index < 0:
|
||||||
|
stdout.writeLine("selected index out of range(0-$1), requested $2" %
|
||||||
|
[$forkData.len, $index])
|
||||||
|
return false
|
||||||
|
|
||||||
|
let subTest = forkData[index]
|
||||||
|
runSubTest(subTest)
|
||||||
|
else:
|
||||||
|
for forkName, forkData in post:
|
||||||
|
prepareFork(forkName)
|
||||||
|
for subTest in forkData:
|
||||||
|
runSubTest(subTest)
|
||||||
|
|
||||||
|
writeResultToStdout(stateRes)
|
||||||
|
not hasError
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
let conf = StateConf.init()
|
||||||
|
var ctx: StateContext
|
||||||
|
if not ctx.prepareAndRun(conf):
|
||||||
|
quit(QuitFailure)
|
||||||
|
|
||||||
|
main()
|
57
tools/evmstate/evmstate_test.nim
Normal file
57
tools/evmstate/evmstate_test.nim
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import
|
||||||
|
std/[os, osproc, strutils, tables],
|
||||||
|
unittest2,
|
||||||
|
testutils/markdown_reports,
|
||||||
|
../../tests/test_allowed_to_fail
|
||||||
|
|
||||||
|
const
|
||||||
|
inputFolder = "tests" / "fixtures" / "eth_tests" / "GeneralStateTests"
|
||||||
|
|
||||||
|
proc runTest(filename: string): bool =
|
||||||
|
let appDir = getAppDir()
|
||||||
|
let cmd = appDir / ("evmstate " & filename)
|
||||||
|
let (res, exitCode) = execCmdEx(cmd)
|
||||||
|
if exitCode != QuitSuccess:
|
||||||
|
echo res
|
||||||
|
return false
|
||||||
|
|
||||||
|
true
|
||||||
|
|
||||||
|
template skipTest(folder, name: untyped): bool =
|
||||||
|
skipNewGSTTests(folder, name)
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
suite "evmstate test suite":
|
||||||
|
var status = initOrderedTable[string, OrderedTable[string, Status]]()
|
||||||
|
var filenames: seq[string] = @[]
|
||||||
|
for filename in walkDirRec(inputFolder):
|
||||||
|
if not filename.endsWith(".json"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
let (folder, name) = filename.splitPath()
|
||||||
|
let last = folder.splitPath().tail
|
||||||
|
if not status.hasKey(last):
|
||||||
|
status[last] = initOrderedTable[string, Status]()
|
||||||
|
status[last][name] = Status.Skip
|
||||||
|
if skipTest(last, name):
|
||||||
|
continue
|
||||||
|
|
||||||
|
filenames.add filename
|
||||||
|
|
||||||
|
for inputFile in filenames:
|
||||||
|
let testName = substr(inputFile, inputFolder.len+1)
|
||||||
|
test testName:
|
||||||
|
let (folder, name) = inputFile.splitPath()
|
||||||
|
let last = folder.splitPath().tail
|
||||||
|
status[last][name] = Status.Fail
|
||||||
|
let res = runTest(inputFile)
|
||||||
|
check true == res
|
||||||
|
if res:
|
||||||
|
status[last][name] = Status.OK
|
||||||
|
|
||||||
|
status.sort do (a: (string, OrderedTable[string, Status]),
|
||||||
|
b: (string, OrderedTable[string, Status])) -> int: cmp(a[0], b[0])
|
||||||
|
|
||||||
|
generateReport("evmstate", status)
|
||||||
|
|
||||||
|
main()
|
164
tools/evmstate/helpers.nim
Normal file
164
tools/evmstate/helpers.nim
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import
|
||||||
|
std/[options, json, strutils],
|
||||||
|
eth/[common, keys],
|
||||||
|
eth/trie/trie_defs,
|
||||||
|
stint,
|
||||||
|
stew/byteutils,
|
||||||
|
../../nimbus/[transaction, forks],
|
||||||
|
../../nimbus/db/accounts_cache
|
||||||
|
|
||||||
|
template fromJson(T: type EthAddress, n: JsonNode): EthAddress =
|
||||||
|
hexToByteArray(n.getStr, sizeof(T))
|
||||||
|
|
||||||
|
proc fromJson(T: type UInt256, n: JsonNode): UInt256 =
|
||||||
|
# stTransactionTest/ValueOverflow.json
|
||||||
|
# prevent parsing exception and subtitute it with max uint256
|
||||||
|
let hex = n.getStr
|
||||||
|
if ':' in hex:
|
||||||
|
high(UInt256)
|
||||||
|
else:
|
||||||
|
UInt256.fromHex(hex)
|
||||||
|
|
||||||
|
template fromJson*(T: type Hash256, n: JsonNode): Hash256 =
|
||||||
|
Hash256(data: hexToByteArray(n.getStr, 32))
|
||||||
|
|
||||||
|
proc fromJson(T: type Blob, n: JsonNode): Blob =
|
||||||
|
let hex = n.getStr
|
||||||
|
if hex.len == 0:
|
||||||
|
@[]
|
||||||
|
else:
|
||||||
|
hexToSeqByte(hex)
|
||||||
|
|
||||||
|
template fromJson(T: type GasInt, n: JsonNode): GasInt =
|
||||||
|
fromHex[GasInt](n.getStr)
|
||||||
|
|
||||||
|
template fromJson(T: type AccountNonce, n: JsonNode): AccountNonce =
|
||||||
|
fromHex[AccountNonce](n.getStr)
|
||||||
|
|
||||||
|
template fromJson(T: type EthTime, n: JsonNode): EthTime =
|
||||||
|
fromUnix(fromHex[int64](n.getStr))
|
||||||
|
|
||||||
|
proc fromJson(T: type PrivateKey, n: JsonNode): PrivateKey =
|
||||||
|
var secretKey = n.getStr
|
||||||
|
removePrefix(secretKey, "0x")
|
||||||
|
PrivateKey.fromHex(secretKey).tryGet()
|
||||||
|
|
||||||
|
proc fromJson(T: type AccessList, n: JsonNode): AccessList =
|
||||||
|
if n.kind == JNull:
|
||||||
|
return
|
||||||
|
|
||||||
|
for x in n:
|
||||||
|
var ap = AccessPair(
|
||||||
|
address: EthAddress.fromJson(x["address"])
|
||||||
|
)
|
||||||
|
let sks = x["storageKeys"]
|
||||||
|
for sk in sks:
|
||||||
|
ap.storageKeys.add hexToByteArray(sk.getStr, 32)
|
||||||
|
result.add ap
|
||||||
|
|
||||||
|
template required(T: type, nField: string): auto =
|
||||||
|
fromJson(T, n[nField])
|
||||||
|
|
||||||
|
template required(T: type, nField: string, index: int): auto =
|
||||||
|
fromJson(T, n[nField][index])
|
||||||
|
|
||||||
|
template omitZero(T: type, nField: string): auto =
|
||||||
|
if n.hasKey(nField):
|
||||||
|
fromJson(T, n[nField])
|
||||||
|
else:
|
||||||
|
default(T)
|
||||||
|
|
||||||
|
template omitZero(T: type, nField: string, index: int): auto =
|
||||||
|
if n.hasKey(nField):
|
||||||
|
fromJson(T, n[nField][index])
|
||||||
|
else:
|
||||||
|
default(T)
|
||||||
|
|
||||||
|
template optional(T: type, nField: string): auto =
|
||||||
|
if n.hasKey(nField):
|
||||||
|
some(T.fromJson(n[nField]))
|
||||||
|
else:
|
||||||
|
none(T)
|
||||||
|
|
||||||
|
proc txType(n: JsonNode): TxType =
|
||||||
|
if "gasPrice" notin n:
|
||||||
|
return TxEip1559
|
||||||
|
if "accessLists" in n:
|
||||||
|
return TxEip2930
|
||||||
|
TxLegacy
|
||||||
|
|
||||||
|
proc parseHeader*(n: JsonNode): BlockHeader =
|
||||||
|
BlockHeader(
|
||||||
|
coinbase : required(EthAddress, "currentCoinbase"),
|
||||||
|
difficulty : required(DifficultyInt, "currentDifficulty"),
|
||||||
|
blockNumber: required(BlockNumber, "currentNumber"),
|
||||||
|
gasLimit : required(GasInt, "currentGasLimit"),
|
||||||
|
timestamp : required(EthTime, "currentTimestamp"),
|
||||||
|
stateRoot : emptyRlpHash,
|
||||||
|
mixDigest : omitZero(Hash256, "currentRandom"),
|
||||||
|
fee : optional(UInt256, "currentBaseFee")
|
||||||
|
)
|
||||||
|
|
||||||
|
proc parseTx*(n: JsonNode, dataIndex, gasIndex, valueIndex: int): Transaction =
|
||||||
|
var tx = Transaction(
|
||||||
|
txType : txType(n),
|
||||||
|
nonce : required(AccountNonce, "nonce"),
|
||||||
|
gasLimit: required(GasInt, "gasLimit", gasIndex),
|
||||||
|
value : required(UInt256, "value", valueIndex),
|
||||||
|
payload : required(Blob, "data", dataIndex),
|
||||||
|
chainId : ChainId(1),
|
||||||
|
gasPrice: omitZero(GasInt, "gasPrice"),
|
||||||
|
maxFee : omitZero(GasInt, "maxFeePerGas"),
|
||||||
|
accessList: omitZero(AccessList, "accessLists", dataIndex),
|
||||||
|
maxPriorityFee: omitZero(GasInt, "maxPriorityFeePerGas")
|
||||||
|
)
|
||||||
|
|
||||||
|
let rawTo = n["to"].getStr
|
||||||
|
if rawTo != "":
|
||||||
|
tx.to = some(hexToByteArray(rawTo, 20))
|
||||||
|
|
||||||
|
let secretKey = required(PrivateKey, "secretKey")
|
||||||
|
signTransaction(tx, secretKey, tx.chainId, false)
|
||||||
|
|
||||||
|
proc setupStateDB*(wantedState: JsonNode, stateDB: AccountsCache) =
|
||||||
|
for ac, accountData in wantedState:
|
||||||
|
let account = hexToByteArray[20](ac)
|
||||||
|
for slot, value in accountData{"storage"}:
|
||||||
|
stateDB.setStorage(account, fromHex(UInt256, slot), fromHex(UInt256, value.getStr))
|
||||||
|
|
||||||
|
stateDB.setNonce(account, fromJson(AccountNonce, accountData["nonce"]))
|
||||||
|
stateDB.setCode(account, fromJson(Blob, accountData["code"]))
|
||||||
|
stateDB.setBalance(account, fromJson(UInt256, accountData["balance"]))
|
||||||
|
|
||||||
|
proc parseFork*(x: string): Option[Fork] =
|
||||||
|
case x
|
||||||
|
of "Frontier" : some(FkFrontier)
|
||||||
|
of "Homestead" : some(FkHomestead)
|
||||||
|
of "EIP150" : some(FkTangerine)
|
||||||
|
of "EIP158" : some(FkSpurious)
|
||||||
|
of "Byzantium" : some(FkByzantium)
|
||||||
|
of "Constantinople" : some(FkConstantinople)
|
||||||
|
of "ConstantinopleFix": some(FkPetersburg)
|
||||||
|
of "Istanbul" : some(FkIstanbul)
|
||||||
|
of "Berlin" : some(FkBerlin)
|
||||||
|
of "London" : some(FkLondon)
|
||||||
|
of "Merge" : some(FkParis)
|
||||||
|
of "Shanghai" : some(FkShanghai)
|
||||||
|
of "Cancun" : some(FkCancun)
|
||||||
|
else: none(Fork)
|
||||||
|
|
||||||
|
proc toString*(x: Fork): string =
|
||||||
|
case x
|
||||||
|
of FkFrontier : "Frontier"
|
||||||
|
of FkHomestead : "Homestead"
|
||||||
|
of FkTangerine : "EIP150"
|
||||||
|
of FkSpurious : "EIP158"
|
||||||
|
of FkByzantium : "Byzantium"
|
||||||
|
of FkConstantinople: "Constantinople"
|
||||||
|
of FkPetersburg : "ConstantinopleFix"
|
||||||
|
of FkIstanbul : "Istanbul"
|
||||||
|
of FkBerlin : "Berlin"
|
||||||
|
of FkLondon : "London"
|
||||||
|
of FkParis : "Merge"
|
||||||
|
of FkShanghai : "Shanghai"
|
||||||
|
of FkCancun : "Cancun"
|
49
tools/evmstate/readme.md
Normal file
49
tools/evmstate/readme.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
## EVM state test tool
|
||||||
|
|
||||||
|
The `evmstate` tool to execute state test.
|
||||||
|
|
||||||
|
### Build instructions
|
||||||
|
|
||||||
|
There are few options to build `evmstate` tool like any other nimbus tools.
|
||||||
|
|
||||||
|
1. Use nimble to install dependencies and your system Nim compiler(version <= 1.6.0).
|
||||||
|
```
|
||||||
|
$> nimble install -y --depsOnly
|
||||||
|
$> nim c -d:release -d:chronicles_enabled=off tools/evmstate/evmstate
|
||||||
|
$> nim c -r -d:release tools/evmstate/evmstate_test
|
||||||
|
```
|
||||||
|
2. Use nimbus shipped Nim compiler and dependencies.
|
||||||
|
```
|
||||||
|
$> make update
|
||||||
|
$> ./env.sh nim c -d:release -d:chronicles_enabled=off tools/evmstate/evmstate
|
||||||
|
$> ./env.sh nim c -r -d:release tools/evmstate/evmstate_test
|
||||||
|
```
|
||||||
|
3. Use nimbus makefile.
|
||||||
|
```
|
||||||
|
$> make update
|
||||||
|
$> make evmstate
|
||||||
|
$> make evmstate_test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command line params
|
||||||
|
|
||||||
|
Available command line params
|
||||||
|
```
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
evmstate [OPTIONS]... <inputFile>
|
||||||
|
|
||||||
|
<inputFile> json file contains state test data.
|
||||||
|
|
||||||
|
The following options are available:
|
||||||
|
|
||||||
|
--dump dumps the state after the run [=false].
|
||||||
|
--json output trace logs in machine readable format (json) [=false].
|
||||||
|
--debug output full trace logs [=false].
|
||||||
|
--nomemory disable memory output [=true].
|
||||||
|
--nostack disable stack output [=false].
|
||||||
|
--nostorage disable storage output [=false].
|
||||||
|
--noreturndata enable return data output [=true].
|
||||||
|
--verbosity sets the verbosity level [=0].
|
||||||
|
|
||||||
|
```
|
Loading…
x
Reference in New Issue
Block a user