reduce code duplication in multiple test runners

This commit is contained in:
jangko 2023-01-11 00:02:21 +07:00
parent 735d780424
commit a232c7eb1d
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
6 changed files with 95 additions and 257 deletions

View File

@ -20,7 +20,8 @@ import
../nimbus/utils/utils, ../nimbus/utils/utils,
../nimbus/core/[executor, validate, pow/header], ../nimbus/core/[executor, validate, pow/header],
../stateless/[tree_from_witness, witness_types], ../stateless/[tree_from_witness, witness_types],
../tools/common/helpers, ../tools/common/helpers as chp,
../tools/evmstate/helpers,
../nimbus/common/common ../nimbus/common/common
type type
@ -344,7 +345,7 @@ proc testFixture(node: JsonNode, testStatusIMPL: var TestStatus, debugMode = fal
# - mine block # - mine block
# 3 - diff resulting state with expected state # 3 - diff resulting state with expected state
# 4 - check that all previous blocks were valid # 4 - check that all previous blocks were valid
let specifyIndex = test_config.getConfiguration().index let specifyIndex = test_config.getConfiguration().index.get(0)
var fixtureIndex = 0 var fixtureIndex = 0
var fixtureTested = false var fixtureTested = false

View File

@ -1,7 +1,6 @@
import import
std/[parseopt, strutils, tables], std/[parseopt, strutils, options],
../nimbus/common/evmforks, ../nimbus/common/evmforks
./test_helpers
type type
ConfigStatus* = enum ConfigStatus* = enum
@ -15,8 +14,8 @@ type
Configuration = ref object Configuration = ref object
testSubject*: string testSubject*: string
fork*: EVMFork fork*: string
index*: int index*: Option[int]
trace*: bool trace*: bool
legacy*: bool legacy*: bool
pruning*: bool pruning*: bool
@ -25,8 +24,6 @@ var testConfig {.threadvar.}: Configuration
proc initConfiguration(): Configuration = proc initConfiguration(): Configuration =
result = new Configuration result = new Configuration
result.fork = FkFrontier
result.index = 0
result.trace = true result.trace = true
result.pruning = true result.pruning = true
@ -47,8 +44,8 @@ proc processArguments*(msg: var string): ConfigStatus =
config.testSubject = key config.testSubject = key
of cmdLongOption, cmdShortOption: of cmdLongOption, cmdShortOption:
case key.toLowerAscii() case key.toLowerAscii()
of "fork": config.fork = nameToFork[strip(value)] of "fork": config.fork = value
of "index": config.index = parseInt(value) of "index": config.index = some(parseInt(value))
of "trace": config.trace = parseBool(value) of "trace": config.trace = parseBool(value)
of "legacy": config.legacy = parseBool(value) of "legacy": config.legacy = parseBool(value)
of "pruning": config.pruning = parseBool(value) of "pruning": config.pruning = parseBool(value)

View File

@ -14,6 +14,8 @@ import
../nimbus/db/accounts_cache, ../nimbus/db/accounts_cache,
../nimbus/common/common, ../nimbus/common/common,
../nimbus/utils/utils, ../nimbus/utils/utils,
../tools/common/helpers as chp,
../tools/evmstate/helpers,
chronicles, chronicles,
eth/rlp, eth/rlp,
eth/trie/trie_defs, eth/trie/trie_defs,
@ -26,9 +28,9 @@ type
header: BlockHeader header: BlockHeader
pre: JsonNode pre: JsonNode
tx: Transaction tx: Transaction
expectedHash: string expectedHash: Hash256
expectedLogs: string expectedLogs: Hash256
fork: EVMFork chainConfig: ChainConfig
debugMode: bool debugMode: bool
trace: bool trace: bool
index: int index: int
@ -83,18 +85,9 @@ proc dumpDebugData(tester: Tester, vmState: BaseVMState, sender: EthAddress, gas
let status = if success: "_success" else: "_failed" let status = if success: "_success" else: "_failed"
writeFile("debug_" & tester.name & "_" & $tester.index & status & ".json", debugData.pretty()) writeFile("debug_" & tester.name & "_" & $tester.index & status & ".json", debugData.pretty())
# using only one networkParams will reduce execution
# time ~90% instead of create it for every test
let params = chainConfigForNetwork(MainNet)
proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) = proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
if tester.fork == FkParis:
params.terminalTotalDifficulty = some(0.u256)
else:
params.terminalTotalDifficulty = none(BlockNumber)
let let
com = CommonRef.new(newMemoryDB(), params, getConfiguration().pruning) com = CommonRef.new(newMemoryDB(), tester.chainConfig, getConfiguration().pruning)
parent = BlockHeader(stateRoot: emptyRlpHash) parent = BlockHeader(stateRoot: emptyRlpHash)
let vmState = BaseVMState.new( let vmState = BaseVMState.new(
@ -106,6 +99,7 @@ proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
var gasUsed: GasInt var gasUsed: GasInt
let sender = tester.tx.getSender() let sender = tester.tx.getSender()
let fork = com.toEVMFork(tester.header.blockNumber)
vmState.mutateStateDB: vmState.mutateStateDB:
setupStateDB(tester.pre, db) setupStateDB(tester.pre, db)
@ -116,18 +110,17 @@ proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
db.persist() db.persist()
defer: defer:
let obtainedHash = "0x" & `$`(vmState.readOnlyStateDB.rootHash).toLowerAscii let obtainedHash = vmState.readOnlyStateDB.rootHash
check obtainedHash == tester.expectedHash check obtainedHash == tester.expectedHash
let logEntries = vmState.getAndClearLogEntries() let logEntries = vmState.getAndClearLogEntries()
let actualLogsHash = hashLogEntries(logEntries) let actualLogsHash = hashLogEntries(logEntries)
let expectedLogsHash = toLowerAscii(tester.expectedLogs) check(tester.expectedLogs == actualLogsHash)
check(expectedLogsHash == actualLogsHash)
if tester.debugMode: if tester.debugMode:
let success = expectedLogsHash == actualLogsHash and obtainedHash == tester.expectedHash let success = tester.expectedLogs == actualLogsHash and obtainedHash == tester.expectedHash
tester.dumpDebugData(vmState, sender, gasUsed, success) tester.dumpDebugData(vmState, sender, gasUsed, success)
let rc = vmState.processTransaction( let rc = vmState.processTransaction(
tester.tx, sender, tester.header, tester.fork) tester.tx, sender, tester.header, fork)
if rc.isOk: if rc.isOk:
gasUsed = rc.value gasUsed = rc.value
@ -144,7 +137,7 @@ proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
if miner in vmState.selfDestructs: if miner in vmState.selfDestructs:
vmState.mutateStateDB: vmState.mutateStateDB:
db.addBalance(miner, 0.u256) db.addBalance(miner, 0.u256)
if tester.fork >= FkSpurious: if fork >= FkSpurious:
if db.isEmptyAccount(miner): if db.isEmptyAccount(miner):
db.deleteAccount(miner) db.deleteAccount(miner)
@ -155,7 +148,7 @@ proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
db.persist() db.persist()
proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus, proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus,
trace = false, debugMode = false, supportedForks: set[EVMFork] = supportedForks) = trace = false, debugMode = false) =
var tester: Tester var tester: Tester
var fixture: JsonNode var fixture: JsonNode
for label, child in fixtures: for label, child in fixtures:
@ -163,57 +156,53 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus,
tester.name = label tester.name = label
break break
let fenv = fixture["env"] tester.pre = fixture["pre"]
tester.header = BlockHeader( tester.header = parseHeader(fixture["env"])
coinbase : fenv["currentCoinbase"].getStr.ethAddressFromHex,
difficulty : fromHex(UInt256, fenv{"currentDifficulty"}.getStr),
blockNumber: fenv{"currentNumber"}.getHexadecimalInt.u256,
gasLimit : fenv{"currentGasLimit"}.getHexadecimalInt.GasInt,
timestamp : fenv{"currentTimestamp"}.getHexadecimalInt.int64.fromUnix,
stateRoot : emptyRlpHash
)
if "currentBaseFee" in fenv:
tester.header.baseFee = fromHex(UInt256, fenv{"currentBaseFee"}.getStr)
if "currentRandom" in fenv:
tester.header.prevRandao.data = hexToByteArray[32](fenv{"currentRandom"}.getStr)
let specifyIndex = getConfiguration().index
tester.trace = trace tester.trace = trace
tester.debugMode = debugMode tester.debugMode = debugMode
let ftrans = fixture["transaction"]
var testedInFork = false
var numIndex = -1
for fork in supportedForks:
if fixture["post"].hasKey(forkNames[fork]):
numIndex = fixture["post"][forkNames[fork]].len
for expectation in fixture["post"][forkNames[fork]]:
inc tester.index
if specifyIndex > 0 and tester.index != specifyIndex:
continue
testedInFork = true
tester.expectedHash = expectation["hash"].getStr
tester.expectedLogs = expectation["logs"].getStr
let
indexes = expectation["indexes"]
dataIndex = indexes["data"].getInt
gasIndex = indexes["gas"].getInt
valueIndex = indexes["value"].getInt
tester.tx = ftrans.getFixtureTransaction(dataIndex, gasIndex, valueIndex)
tester.pre = fixture["pre"]
tester.fork = fork
testFixtureIndexes(tester, testStatusIMPL)
if not testedInFork: let
echo "test subject '", tester.name, "' not tested in any forks/subtests" post = fixture["post"]
if specifyIndex <= 0 or specifyIndex > numIndex: txData = fixture["transaction"]
echo "Maximum subtest available: ", numIndex conf = getConfiguration()
template prepareFork(forkName: string) =
try:
tester.chainConfig = getChainConfig(forkName)
except ValueError as ex:
debugEcho ex.msg
return
template runSubTest(subTest: JsonNode) =
tester.expectedHash = Hash256.fromJson(subTest["hash"])
tester.expectedLogs = Hash256.fromJson(subTest["logs"])
tester.tx = parseTx(txData, subTest["indexes"])
tester.testFixtureIndexes(testStatusIMPL)
if conf.fork.len > 0:
if not post.hasKey(conf.fork):
debugEcho "selected fork not available: " & conf.fork
return
let forkData = post[conf.fork]
prepareFork(conf.fork)
if conf.index.isNone:
for subTest in forkData:
runSubTest(subTest)
else: else:
echo "available forks in this test:" let index = conf.index.get()
for fork in test_helpers.supportedForks: if index > forkData.len or index < 0:
if fixture["post"].hasKey(forkNames[fork]): debugEcho "selected index out of range(0-$1), requested $2" %
echo fork [$forkData.len, $index]
return
let subTest = forkData[index]
runSubTest(subTest)
else:
for forkName, forkData in post:
prepareFork(forkName)
for subTest in forkData:
runSubTest(subTest)
proc generalStateJsonMain*(debugMode = false) = proc generalStateJsonMain*(debugMode = false) =
const const
@ -239,9 +228,7 @@ proc generalStateJsonMain*(debugMode = false) =
let path = "tests" / "fixtures" / folder let path = "tests" / "fixtures" / folder
let n = json.parseFile(path / config.testSubject) let n = json.parseFile(path / config.testSubject)
var testStatusIMPL: TestStatus var testStatusIMPL: TestStatus
var forks: set[EVMFork] = {} testFixture(n, testStatusIMPL, config.trace, true)
forks.incl config.fork
testFixture(n, testStatusIMPL, config.trace, true, forks)
when isMainModule: when isMainModule:
var message: string var message: string

View File

@ -34,19 +34,6 @@ const
FkParis: "Merge" FkParis: "Merge"
}.toTable }.toTable
supportedForks* = {
FkFrontier,
FkHomestead,
FkTangerine,
FkSpurious,
FkByzantium,
FkConstantinople,
FkPetersburg,
FkIstanbul,
FkBerlin,
FkLondon,
FkParis}
nameToFork* = revmap(forkNames) nameToFork* = revmap(forkNames)
func skipNothing*(folder: string, name: string): bool = false func skipNothing*(folder: string, name: string): bool = false
@ -121,20 +108,6 @@ func getHexadecimalInt*(j: JsonNode): int64 =
data = fromHex(StUint[64], j.getStr) data = fromHex(StUint[64], j.getStr)
result = cast[int64](data) result = cast[int64](data)
proc setupStateDB*(wantedState: JsonNode, stateDB: AccountsCache) =
for ac, accountData in wantedState:
let account = ethAddressFromHex(ac)
for slot, value in accountData{"storage"}:
stateDB.setStorage(account, fromHex(UInt256, slot), fromHex(UInt256, value.getStr))
let nonce = accountData{"nonce"}.getHexadecimalInt.AccountNonce
let code = accountData{"code"}.getStr.safeHexToSeqByte
let balance = UInt256.fromHex accountData{"balance"}.getStr
stateDB.setNonce(account, nonce)
stateDB.setCode(account, code)
stateDB.setBalance(account, balance)
proc verifyStateDB*(wantedState: JsonNode, stateDB: ReadOnlyStateDB) = proc verifyStateDB*(wantedState: JsonNode, stateDB: ReadOnlyStateDB) =
for ac, accountData in wantedState: for ac, accountData in wantedState:
let account = ethAddressFromHex(ac) let account = ethAddressFromHex(ac)
@ -165,93 +138,8 @@ proc verifyStateDB*(wantedState: JsonNode, stateDB: ReadOnlyStateDB) =
if wantedNonce != actualNonce: if wantedNonce != actualNonce:
raise newException(ValidationError, &"{ac} nonceDiff {wantedNonce.toHex} != {actualNonce.toHex}") raise newException(ValidationError, &"{ac} nonceDiff {wantedNonce.toHex} != {actualNonce.toHex}")
proc parseAccessList(n: JsonNode): AccessList = proc hashLogEntries*(logs: seq[Log]): Hash256 =
if n.kind == JNull: keccakHash(rlp.encode(logs))
return
for x in n:
var ap = AccessPair(
address: parseAddress(x["address"].getStr)
)
let sks = x["storageKeys"]
for sk in sks:
ap.storageKeys.add hexToByteArray[32](sk.getStr())
result.add ap
proc getFixtureTransaction*(j: JsonNode, dataIndex, gasIndex, valueIndex: int): Transaction =
let dynamicFeeTx = "gasPrice" notin j
let nonce = j["nonce"].getHexadecimalInt.AccountNonce
let gasLimit = j["gasLimit"][gasIndex].getHexadecimalInt
var toAddr: Option[EthAddress]
# Fixture transactions with `"to": ""` are contract creations.
#
# Fixture transactions with `"to": "0x..."` or `"to": "..."` where `...` are
# 40 hex digits are call/transfer transactions. Even if the digits are all
# zeros, because the all-zeros address is a legitimate account.
#
# There are no other formats. The number of digits if present is always 40,
# "0x" prefix is used in some but not all fixtures, and upper case hex digits
# occur in a few.
let rawTo = j["to"].getStr
if rawTo != "":
toAddr = some(rawTo.parseAddress)
let hexStr = j["value"][valueIndex].getStr
# stTransactionTest/ValueOverflow.json
# prevent parsing exception and subtitute it with max uint256
let value = if ':' in hexStr: high(UInt256) else: fromHex(UInt256, hexStr)
let payload = j["data"][dataIndex].getStr.safeHexToSeqByte
var secretKey = j["secretKey"].getStr
removePrefix(secretKey, "0x")
let privateKey = PrivateKey.fromHex(secretKey).tryGet()
if dynamicFeeTx:
let accList = j["accessLists"][dataIndex]
var tx = Transaction(
txType: TxEip1559,
nonce: nonce,
maxFee: j["maxFeePerGas"].getHexadecimalInt,
maxPriorityFee: j["maxPriorityFeePerGas"].getHexadecimalInt,
gasLimit: gasLimit,
to: toAddr,
value: value,
payload: payload,
accessList: parseAccessList(accList),
chainId: ChainId(1)
)
return signTransaction(tx, privateKey, ChainId(1), false)
let gasPrice = j["gasPrice"].getHexadecimalInt
if j.hasKey("accessLists"):
let accList = j["accessLists"][dataIndex]
var tx = Transaction(
txType: TxEip2930,
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to: toAddr,
value: value,
payload: payload,
accessList: parseAccessList(accList),
chainId: ChainId(1)
)
signTransaction(tx, privateKey, ChainId(1), false)
else:
var tx = Transaction(
txType: TxLegacy,
nonce: nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to: toAddr,
value: value,
payload: payload
)
signTransaction(tx, privateKey, ChainId(1), false)
proc hashLogEntries*(logs: seq[Log]): string =
toLowerAscii("0x" & $keccakHash(rlp.encode(logs)))
proc setupEthNode*( proc setupEthNode*(
conf: NimbusConf, ctx: EthContext, conf: NimbusConf, ctx: EthContext,

View File

@ -20,6 +20,7 @@ import
../../nimbus/transaction, ../../nimbus/transaction,
../../nimbus/core/executor, ../../nimbus/core/executor,
../../nimbus/common/common, ../../nimbus/common/common,
../common/helpers as chp,
"."/[config, helpers] "."/[config, helpers]
type type
@ -29,7 +30,7 @@ type
tx: Transaction tx: Transaction
expectedHash: Hash256 expectedHash: Hash256
expectedLogs: Hash256 expectedLogs: Hash256
fork: EVMFork chainConfig: ChainConfig
index: int index: int
tracerFlags: set[TracerFlags] tracerFlags: set[TracerFlags]
error: string error: string
@ -64,13 +65,6 @@ proc extractNameAndFixture(ctx: var StateContext, n: JsonNode): JsonNode =
return return
doAssert(false, "unreachable") 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] = proc toBytes(x: string): seq[byte] =
result = newSeq[byte](x.len) result = newSeq[byte](x.len)
for i in 0..<x.len: result[i] = x[i].byte for i in 0..<x.len: result[i] = x[i].byte
@ -190,7 +184,6 @@ proc writeTraceToStderr(vmState: BaseVMState, pretty: bool) =
if pretty: if pretty:
stderr.writeLine(trace.pretty) stderr.writeLine(trace.pretty)
else: else:
var gasUsed = 0
let logs = trace["structLogs"] let logs = trace["structLogs"]
trace.delete("structLogs") trace.delete("structLogs")
for x in logs: for x in logs:
@ -204,14 +197,9 @@ proc writeTraceToStderr(vmState: BaseVMState, pretty: bool) =
proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult = proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult =
let let
params = chainConfigForNetwork(MainNet) com = CommonRef.new(newMemoryDB(), ctx.chainConfig, pruneTrie = false)
if ctx.fork == FkParis:
params.terminalTotalDifficulty = some(0.u256)
let
com = CommonRef.new(newMemoryDB(), params, pruneTrie = false)
parent = BlockHeader(stateRoot: emptyRlpHash) parent = BlockHeader(stateRoot: emptyRlpHash)
fork = com.toEVMFork(ctx.header.blockNumber)
let vmState = TestVMState() let vmState = TestVMState()
vmState.init( vmState.init(
@ -233,7 +221,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
name : ctx.name, name : ctx.name,
pass : ctx.error.len == 0, pass : ctx.error.len == 0,
root : vmState.stateDB.rootHash, root : vmState.stateDB.rootHash,
fork : toString(ctx.fork), fork : $fork,
error: ctx.error error: ctx.error
) )
if conf.dumpEnabled: if conf.dumpEnabled:
@ -242,7 +230,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
writeTraceToStderr(vmState, conf.pretty) writeTraceToStderr(vmState, conf.pretty)
let rc = vmState.processTransaction( let rc = vmState.processTransaction(
ctx.tx, sender, ctx.header, ctx.fork) ctx.tx, sender, ctx.header, fork)
if rc.isOk: if rc.isOk:
gasUsed = rc.value gasUsed = rc.value
@ -250,7 +238,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
if miner in vmState.selfDestructs: if miner in vmState.selfDestructs:
vmState.mutateStateDB: vmState.mutateStateDB:
db.addBalance(miner, 0.u256) db.addBalance(miner, 0.u256)
if ctx.fork >= FkSpurious: if fork >= FkSpurious:
if db.isEmptyAccount(miner): if db.isEmptyAccount(miner):
db.deleteAccount(miner) db.deleteAccount(miner)
db.persist(clearCache = false) db.persist(clearCache = false)
@ -289,9 +277,11 @@ proc prepareAndRun(ctx: var StateContext, conf: StateConf): bool =
hasError = false hasError = false
template prepareFork(forkName: string) = template prepareFork(forkName: string) =
let fork = parseFork(forkName) try:
doAssert(fork.isSome, "unsupported fork: " & forkName) ctx.chainConfig = getChainConfig(forkName)
ctx.fork = fork.get() except ValueError as ex:
debugEcho ex.msg
return false
ctx.index = index ctx.index = index
inc index inc index
@ -332,14 +322,15 @@ proc prepareAndRun(ctx: var StateContext, conf: StateConf): bool =
not hasError not hasError
when defined(chronicles_runtime_filtering): when defined(chronicles_runtime_filtering):
proc toLogLevel(v: int): LogLevel = type Lev = chronicles.LogLevel
proc toLogLevel(v: int): Lev =
case v case v
of 1: LogLevel.ERROR of 1: Lev.ERROR
of 2: LogLevel.WARN of 2: Lev.WARN
of 3: LogLevel.INFO of 3: Lev.INFO
of 4: LogLevel.DEBUG of 4: Lev.DEBUG
of 5: LogLevel.TRACE of 5: Lev.TRACE
else: LogLevel.NONE else: Lev.NONE
proc setVerbosity(v: int) = proc setVerbosity(v: int) =
let level = v.toLogLevel let level = v.toLogLevel

View File

@ -131,6 +131,13 @@ proc parseTx*(n: JsonNode, dataIndex, gasIndex, valueIndex: int): Transaction =
let secretKey = required(PrivateKey, "secretKey") let secretKey = required(PrivateKey, "secretKey")
signTransaction(tx, secretKey, tx.chainId, false) signTransaction(tx, secretKey, tx.chainId, false)
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 setupStateDB*(wantedState: JsonNode, stateDB: AccountsCache) = proc setupStateDB*(wantedState: JsonNode, stateDB: AccountsCache) =
for ac, accountData in wantedState: for ac, accountData in wantedState:
let account = hexToByteArray[20](ac) let account = hexToByteArray[20](ac)
@ -140,36 +147,3 @@ proc setupStateDB*(wantedState: JsonNode, stateDB: AccountsCache) =
stateDB.setNonce(account, fromJson(AccountNonce, accountData["nonce"])) stateDB.setNonce(account, fromJson(AccountNonce, accountData["nonce"]))
stateDB.setCode(account, fromJson(Blob, accountData["code"])) stateDB.setCode(account, fromJson(Blob, accountData["code"]))
stateDB.setBalance(account, fromJson(UInt256, accountData["balance"])) stateDB.setBalance(account, fromJson(UInt256, accountData["balance"]))
proc parseFork*(x: string): Option[EVMFork] =
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(EVMFork)
proc toString*(x: EVMFork): 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"