# Nimbus # Copyright (c) 2018-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # at your option. This file may not be copied, modified, or distributed except according to those terms. import std/[importutils, sequtils], unittest2, eth/common/[keys, transaction_utils], ../nimbus/common, ../nimbus/transaction, ../nimbus/evm/types, ../nimbus/evm/state, ../nimbus/evm/evm_errors, ../nimbus/evm/stack, ../nimbus/evm/memory, ../nimbus/evm/code_stream, ../nimbus/evm/internals, ../nimbus/constants, ../nimbus/core/pow/header, ../nimbus/db/ledger, ../nimbus/transaction/call_evm template testPush(value: untyped, expected: untyped): untyped = privateAccess(EvmStack) var stack = EvmStack.init() defer: stack.dispose() check stack.push(value).isOk check(toSeq(stack.items()) == @[expected]) proc runStackTests() = suite "Stack tests": test "push only valid": testPush(0'u, 0.u256) testPush(UINT_256_MAX, UINT_256_MAX) # testPush("ves".toBytes, "ves".toBytes.bigEndianToInt) test "push does not allow stack to exceed 1024": var stack = EvmStack.init() defer: stack.dispose() for z in 0 ..< 1024: check stack.push(z.uint).isOk check(stack.len == 1024) check stack.push(1025).error.code == EvmErrorCode.StackFull test "dup does not allow stack to exceed 1024": var stack = EvmStack.init() defer: stack.dispose() check stack.push(1.u256).isOk for z in 0 ..< 1023: check stack.dup(1).isOk check(stack.len == 1024) check stack.dup(1).error.code == EvmErrorCode.StackFull test "pop returns latest stack item": var stack = EvmStack.init() defer: stack.dispose() for element in @[1'u, 2'u, 3'u]: check stack.push(element).isOk check(stack.popInt.get == 3.u256) test "pop requires stack item": var stack = EvmStack.init() defer: stack.dispose() check: stack.pop().isErr() stack.push(1'u).isOk() stack.pop().isOk() test "swap correct": privateAccess(EvmStack) var stack = EvmStack.init() defer: stack.dispose() for z in 0 ..< 5: check stack.push(z.uint).isOk check(toSeq(stack.items()) == @[0.u256, 1.u256, 2.u256, 3.u256, 4.u256]) check stack.swap(3).isOk check(toSeq(stack.items()) == @[0.u256, 4.u256, 2.u256, 3.u256, 1.u256]) check stack.swap(1).isOk check(toSeq(stack.items()) == @[0.u256, 4.u256, 2.u256, 1.u256, 3.u256]) test "dup correct": privateAccess(EvmStack) var stack = EvmStack.init() defer: stack.dispose() for z in 0 ..< 5: check stack.push(z.uint).isOk check(toSeq(stack.items()) == @[0.u256, 1.u256, 2.u256, 3.u256, 4.u256]) check stack.dup(1).isOk check(toSeq(stack.items()) == @[0.u256, 1.u256, 2.u256, 3.u256, 4.u256, 4.u256]) check stack.dup(5).isOk check(toSeq(stack.items()) == @[0.u256, 1.u256, 2.u256, 3.u256, 4.u256, 4.u256, 1.u256]) test "pop raises InsufficientStack appropriately": var stack = EvmStack.init() defer: stack.dispose() check stack.popInt().error.code == EvmErrorCode.StackInsufficient test "swap raises InsufficientStack appropriately": var stack = EvmStack.init() defer: stack.dispose() check stack.swap(0).error.code == EvmErrorCode.StackInsufficient test "dup raises InsufficientStack appropriately": var stack = EvmStack.init() defer: stack.dispose() check stack.dup(0).error.code == EvmErrorCode.StackInsufficient test "binary operations raises InsufficientStack appropriately": # https://github.com/status-im/nimbus/issues/31 # ./tests/fixtures/VMTests/vmArithmeticTest/mulUnderFlow.json var stack = EvmStack.init() defer: stack.dispose() check stack.push(123).isOk check stack.binaryOp(`+`).error.code == EvmErrorCode.StackInsufficient proc memory32: EvmMemory = result = EvmMemory.init(32) proc memory128: EvmMemory = result = EvmMemory.init(123) proc runMemoryTests() = suite "Memory tests": test "write": var mem = memory32() # Test that write creates 32byte string == value padded with zeros check mem.write(startPos = 0, value = @[1.byte, 0.byte, 1.byte, 0.byte]).isOk check(mem.bytes == @[1.byte, 0.byte, 1.byte, 0.byte].concat(repeat(0.byte, 28))) test "write rejects values beyond memory size": var mem = memory128() check mem.write(startPos = 128, value = @[1.byte, 0.byte, 1.byte, 0.byte]).error.code == EvmErrorCode.MemoryFull check mem.write(startPos = 128, value = 1.byte).error.code == EvmErrorCode.MemoryFull test "extends appropriately extends memory": var mem = EvmMemory.init() # Test extends to 32 byte array: 0 < (start_position + size) <= 32 mem.extend(startPos = 0, size = 10) check(mem.bytes == repeat(0.byte, 32)) # Test will extend past length if params require: 32 < (start_position + size) <= 64 mem.extend(startPos = 28, size = 32) check(mem.bytes == repeat(0.byte, 64)) # Test won't extend past length unless params require: 32 < (start_position + size) <= 64 mem.extend(startPos = 48, size = 10) check(mem.bytes == repeat(0.byte, 64)) test "read returns correct bytes": var mem = memory32() check mem.write(startPos = 5, value = @[1.byte, 0.byte, 1.byte, 0.byte]).isOk check(@(mem.read(startPos = 5, size = 4)) == @[1.byte, 0.byte, 1.byte, 0.byte]) check(@(mem.read(startPos = 6, size = 4)) == @[0.byte, 1.byte, 0.byte, 0.byte]) check(@(mem.read(startPos = 1, size = 3)) == @[0.byte, 0.byte, 0.byte]) proc runCodeStreamTests() = suite "Codestream tests": test "accepts bytes": let codeStream = CodeStream.init("\x01") check(codeStream.len == 1) test "next returns the correct opcode": var codeStream = CodeStream.init("\x01\x02\x30") check(codeStream.next == Op.ADD) check(codeStream.next == Op.MUL) check(codeStream.next == Op.ADDRESS) test "peek returns next opcode without changing location": var codeStream = CodeStream.init("\x01\x02\x30") check(codeStream.pc == 0) check(codeStream.peek == Op.ADD) check(codeStream.pc == 0) check(codeStream.next == Op.ADD) check(codeStream.pc == 1) check(codeStream.peek == Op.MUL) check(codeStream.pc == 1) test "stop opcode is returned when end reached": var codeStream = CodeStream.init("\x01\x02") discard codeStream.next discard codeStream.next check(codeStream.next == Op.STOP) test "[] returns opcode": let codeStream = CodeStream.init("\x01\x02\x30") check(codeStream[0] == Op.ADD) check(codeStream[1] == Op.MUL) check(codeStream[2] == Op.ADDRESS) test "isValidOpcode invalidates after PUSHXX": var codeStream = CodeStream.init("\x02\x60\x02\x04") check(codeStream.isValidOpcode(0)) check(codeStream.isValidOpcode(1)) check(not codeStream.isValidOpcode(2)) check(codeStream.isValidOpcode(3)) check(not codeStream.isValidOpcode(4)) test "isValidOpcode 0": var codeStream = CodeStream.init(@[2.byte, 3.byte, 0x72.byte].concat(repeat(4.byte, 32)).concat(@[5.byte])) # valid: 0 - 2 :: 22 - 35 # invalid: 3-21 (PUSH19) :: 36+ (too long) check(codeStream.isValidOpcode(0)) check(codeStream.isValidOpcode(1)) check(codeStream.isValidOpcode(2)) check(not codeStream.isValidOpcode(3)) check(not codeStream.isValidOpcode(21)) check(codeStream.isValidOpcode(22)) check(codeStream.isValidOpcode(35)) check(not codeStream.isValidOpcode(36)) test "isValidOpcode 1": let test = @[2.byte, 3.byte, 0x7d.byte].concat(repeat(4.byte, 32)).concat(@[5.byte, 0x7e.byte]).concat(repeat(4.byte, 35)).concat(@[1.byte, 0x61.byte, 1.byte, 1.byte, 1.byte]) var codeStream = CodeStream.init(test) # valid: 0 - 2 :: 33 - 36 :: 68 - 73 :: 76 # invalid: 3 - 32 (PUSH30) :: 37 - 67 (PUSH31) :: 74, 75 (PUSH2) :: 77+ (too long) check(codeStream.isValidOpcode(0)) check(codeStream.isValidOpcode(1)) check(codeStream.isValidOpcode(2)) check(not codeStream.isValidOpcode(3)) check(not codeStream.isValidOpcode(32)) check(codeStream.isValidOpcode(33)) check(codeStream.isValidOpcode(36)) check(not codeStream.isValidOpcode(37)) check(not codeStream.isValidOpcode(67)) check(codeStream.isValidOpcode(68)) check(codeStream.isValidOpcode(71)) check(codeStream.isValidOpcode(72)) check(codeStream.isValidOpcode(73)) check(not codeStream.isValidOpcode(74)) check(not codeStream.isValidOpcode(75)) check(codeStream.isValidOpcode(76)) check(not codeStream.isValidOpcode(77)) test "right number of bytes invalidates": var codeStream = CodeStream.init("\x02\x03\x60\x02\x02") check(codeStream.isValidOpcode(0)) check(codeStream.isValidOpcode(1)) check(codeStream.isValidOpcode(2)) check(not codeStream.isValidOpcode(3)) check(codeStream.isValidOpcode(4)) check(not codeStream.isValidOpcode(5)) proc initGasMeter(startGas: GasInt): GasMeter = result.init(startGas) proc gasMeters: seq[GasMeter] = @[initGasMeter(10), initGasMeter(100), initGasMeter(999)] template runTest(body: untyped) = var res = gasMeters() for gasMeter {.inject.} in res.mitems: let StartGas {.inject.} = gasMeter.gasRemaining body proc runGasMeterTests() = suite "GasMeter tests": test "consume spends": runTest: check(gasMeter.gasRemaining == StartGas) let consume = StartGas check gasMeter.consumeGas(consume, "0").isOk check(gasMeter.gasRemaining - (StartGas - consume) == 0) test "consume errors": runTest: check(gasMeter.gasRemaining == StartGas) check gasMeter.consumeGas(StartGas + 1, "").error.code == EvmErrorCode.OutOfGas test "return refund works correctly": runTest: check(gasMeter.gasRemaining == StartGas) check(gasMeter.gasRefunded == 0) check gasMeter.consumeGas(5, "").isOk check(gasMeter.gasRemaining == StartGas - 5) gasMeter.returnGas(5) check(gasMeter.gasRemaining == StartGas) gasMeter.refundGas(5) check(gasMeter.gasRefunded == 5) proc runMiscTests() = suite "Misc test suite": test "calcGasLimitEIP1559": type GLT = object limit: GasInt max : GasInt min : GasInt const testData = [ GLT(limit: 20000000, max: 20019530, min: 19980470), GLT(limit: 40000000, max: 40039061, min: 39960939) ] for x in testData: # Increase var have = calcGasLimit1559(x.limit, 2*x.limit) var want = x.max check have == want # Decrease have = calcGasLimit1559(x.limit, 0) want = x.min check have == want # Small decrease have = calcGasLimit1559(x.limit, x.limit-1) want = x.limit-1 check have == want # Small increase have = calcGasLimit1559(x.limit, x.limit+1) want = x.limit+1 check have == want # No change have = calcGasLimit1559(x.limit, x.limit) want = x.limit check have == want const data = [0x5b.uint8, 0x5a, 0x5a, 0x30, 0x30, 0x30, 0x30, 0x72, 0x00, 0x00, 0x00, 0x58, 0x58, 0x24, 0x58, 0x58, 0x3a, 0x19, 0x75, 0x75, 0x2e, 0x2e, 0x2e, 0x2e, 0xec, 0x9f, 0x69, 0x67, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x6c, 0x5a, 0x32, 0x07, 0xf4, 0x75, 0x75, 0xf5, 0x75, 0x75, 0x75, 0x7f, 0x5b, 0xd9, 0x32, 0x5a, 0x07, 0x19, 0x34, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0xec, 0x9f, 0x69, 0x67, 0x7f, 0xff, 0xff, 0xff, 0xff, 0x6c, 0xfc, 0xf7, 0xfc, 0xfc, 0xfc, 0xfc, 0xf4, 0x03, 0x03, 0x81, 0x81, 0x81, 0xfb, 0x7a, 0x30, 0x80, 0x3d, 0x59, 0x59, 0x59, 0x59, 0x81, 0x00, 0x59, 0x2f, 0x45, 0x30, 0x32, 0xf4, 0x5d, 0x5b, 0x37, 0x19] codeAddress = address"000000000000000000000000636f6e7472616374" coinbase = address"4444588443C3a91288c5002483449Aba1054192b" proc runTestOverflow() = test "GasCall unhandled overflow": let header = Header( stateRoot: EMPTY_ROOT_HASH, number: 1150000'u64, coinBase: coinbase, gasLimit: 30000000, timeStamp: EthTime(123456), ) let com = CommonRef.new( newCoreDbRef(DefaultDbMemory), nil, config = chainConfigForNetwork(MainNet) ) let s = BaseVMState.new( header, header, com, ) s.stateDB.setCode(codeAddress, @data) let unsignedTx = Transaction( txType: TxLegacy, nonce: 0, chainId: MainNet.ChainId, gasPrice: 0.GasInt, gasLimit: 30000000, to: Opt.some codeAddress, value: 0.u256, payload: @data ) let privateKey = PrivateKey.fromHex("0000000000000000000000000000000000000000000000000000001000000000")[] let tx = signTransaction(unsignedTx, privateKey, false) let res = testCallEvm(tx, tx.recoverSender().expect("valid signature"), s) when defined(evmc_enabled): check res.error == "EVMC_FAILURE" else: # After gasCall values always on positive, this test become OOG check res.error == "Opcode Dispatch Error: OutOfGas, depth=1" proc evmSupportMain*() = runStackTests() runMemoryTests() runCodeStreamTests() runGasMeterTests() runMiscTests() runTestOverflow() when isMainModule: evmSupportMain()