From 45d9e895166497f37d86644a73b040f74f23ed3d Mon Sep 17 00:00:00 2001 From: Alex Hasikos Date: Thu, 29 Feb 2024 11:31:30 +0200 Subject: [PATCH] Adds vm tests (#660) * Adds contract tests * Adds evm tests * Add test files for memory, noop, opcodes and stack * Add gas tests * Add memory table tests * Add gas_table and interpreter tests * Renames packages and handles return value * Removes unused function * Fixes linter problems * Implement Tony's suggestions * simplify noop tracer --------- Co-authored-by: Makis Christou Co-authored-by: tony --- vm/contracts_test.go | 127 +++++++++++++++++++ vm/evm_test.go | 159 ++++++++++++++++++++++++ vm/gas_table_test.go | 110 ++++++++++++++++- vm/gas_test.go | 52 ++++++++ vm/interpreter_test.go | 94 ++++++++++++++ vm/memory_table_test.go | 262 ++++++++++++++++++++++++++++++++++++++++ vm/memory_test.go | 73 +++++++++++ vm/noop_test.go | 158 ++++++++++++++++++++++++ vm/opcodes_test.go | 67 ++++++++++ vm/stack_test.go | 75 ++++++++++++ 10 files changed, 1175 insertions(+), 2 deletions(-) create mode 100644 vm/evm_test.go create mode 100644 vm/gas_test.go create mode 100644 vm/interpreter_test.go create mode 100644 vm/memory_table_test.go create mode 100644 vm/memory_test.go create mode 100644 vm/noop_test.go create mode 100644 vm/opcodes_test.go create mode 100644 vm/stack_test.go diff --git a/vm/contracts_test.go b/vm/contracts_test.go index 973ea1c14..5708ea4d0 100644 --- a/vm/contracts_test.go +++ b/vm/contracts_test.go @@ -26,6 +26,8 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" ) // precompiledTest defines the input/output pairs for precompiled contract tests. @@ -297,3 +299,128 @@ func loadJson(name string) ([]precompiledTest, error) { err = json.Unmarshal(data, &testcases) return testcases, err } + +func TestAsDelegate(t *testing.T) { + // Mock addresses + parentCallerAddress := common.HexToAddress("0x01") + objectAddress := common.HexToAddress("0x03") + + // Create a parent contract to act as the caller + parentContract := NewContract(AccountRef(parentCallerAddress), AccountRef(parentCallerAddress), big.NewInt(2000), 5000) + + // Create a child contract, which will be turned into a delegate + childContract := NewContract(parentContract, AccountRef(objectAddress), big.NewInt(2000), 5000) + + // Call AsDelegate on the child contract + delegatedContract := childContract.AsDelegate() + + // Perform your test assertions + assert.True(t, delegatedContract.DelegateCall, "Contract should be in delegate call mode") + assert.Equal(t, parentContract.CallerAddress, delegatedContract.CallerAddress, "Caller address should match parent contract caller address") + assert.Equal(t, parentContract.value, delegatedContract.value, "Value should match parent contract value") +} + +func TestValidJumpdest(t *testing.T) { + // Example bytecode: PUSH1 0x02 JUMPDEST STOP + code := []byte{0x60, 0x02, 0x5b, 0x00} + + contract := &Contract{ + Code: code, + } + + // Test a valid jump destination (position of JUMPDEST opcode) + validDest := uint256.NewInt(2) + assert.True(t, contract.validJumpdest(validDest), "Expected valid jump destination") + + // Test an invalid jump destination (within PUSH1 data) + invalidDest := uint256.NewInt(1) + assert.False(t, contract.validJumpdest(invalidDest), "Expected invalid jump destination due to being within PUSH data") + + // Test an invalid jump destination (non-existent opcode) + nonExistentDest := uint256.NewInt(100) + assert.False(t, contract.validJumpdest(nonExistentDest), "Expected invalid jump destination due to non-existent opcode") + + // Test a non-JUMPDEST opcode (STOP opcode) + nonJumpdestOpcode := uint256.NewInt(3) + assert.False(t, contract.validJumpdest(nonJumpdestOpcode), "Expected invalid jump destination due to non-JUMPDEST opcode") + + // Test edge cases + // Destination right at the start of the code + startOfCode := uint256.NewInt(0) + assert.False(t, contract.validJumpdest(startOfCode), "Expected invalid jump destination at the start of the code") + + // Destination right at the end of the code + endOfCode := uint256.NewInt(uint64(len(code) - 1)) + assert.False(t, contract.validJumpdest(endOfCode), "Expected invalid jump destination at the end of the code") +} + +func TestIsCode(t *testing.T) { + // Example bytecode: PUSH1 0x02 JUMPDEST STOP + code := []byte{0x60, 0x02, 0x5b, 0x00} + + contract := &Contract{ + Code: code, + } + + // Test when analysis is not set + assert.False(t, contract.isCode(1), "Position 1 should not be valid code") + assert.True(t, contract.isCode(2), "Position 2 should be valid code") + + // Test that analysis is now set after calling isCode + assert.NotNil(t, contract.analysis, "Analysis should be set after calling isCode") +} + +func setupContract() *Contract { + return &Contract{ + CallerAddress: common.HexToAddress("0x01"), + value: big.NewInt(1000), + Code: []byte{0x60, 0x02, 0x5b, 0x00}, // Example bytecode + CodeHash: common.HexToHash("somehash"), + CodeAddr: new(common.Address), + } +} + +func TestGetOp(t *testing.T) { + contract := setupContract() + assert.Equal(t, OpCode(0x60), contract.GetOp(0), "Expected OpCode at position 0 to match") + assert.Equal(t, OpCode(0x5b), contract.GetOp(2), "Expected OpCode at position 2 to match") +} + +func TestGetByte(t *testing.T) { + contract := setupContract() + assert.Equal(t, byte(0x60), contract.GetByte(0), "Expected byte at position 0 to match") + assert.Equal(t, byte(0x00), contract.GetByte(3), "Expected byte at position 3 to match") + assert.Equal(t, byte(0x00), contract.GetByte(10), "Expected byte at out of bounds position to be 0") +} + +func TestCaller(t *testing.T) { + contract := setupContract() + assert.Equal(t, common.HexToAddress("0x01"), contract.Caller(), "Expected caller address to match") +} + +func TestValue(t *testing.T) { + contract := setupContract() + assert.Equal(t, big.NewInt(1000), contract.Value(), "Expected value to match") +} + +func TestSetCode(t *testing.T) { + contract := setupContract() + newCode := []byte{0x01, 0x02} + newHash := common.HexToHash("newhash") + contract.SetCode(newHash, newCode) + + assert.Equal(t, newCode, contract.Code, "Expected code to be updated") + assert.Equal(t, newHash, contract.CodeHash, "Expected code hash to be updated") +} + +func TestSetCallCode(t *testing.T) { + contract := setupContract() + newCode := []byte{0x03, 0x04} + newHash := common.HexToHash("newerhash") + newAddr := common.HexToAddress("0x02") + contract.SetCallCode(&newAddr, newHash, newCode) + + assert.Equal(t, newCode, contract.Code, "Expected code to be updated") + assert.Equal(t, newHash, contract.CodeHash, "Expected codehash to be updated") + assert.Equal(t, &newAddr, contract.CodeAddr, "Expected code address to be updated") +} diff --git a/vm/evm_test.go b/vm/evm_test.go new file mode 100644 index 000000000..e522bf3b9 --- /dev/null +++ b/vm/evm_test.go @@ -0,0 +1,159 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" +) + +var _ Logger = (*noopTracer)(nil) + +type noopTracer struct{} + +func (t *noopTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +} +func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { +} +func (t *noopTracer) CaptureState(pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, rData []byte, depth int, err error) { +} +func (t *noopTracer) CaptureFault(pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) { +} +func (t *noopTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} +func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +} +func (*noopTracer) CaptureClauseStart(gasLimit uint64) {} +func (*noopTracer) CaptureClauseEnd(restGas uint64) {} + +func setupEvmTestContract(codeAddr *common.Address) (*EVM, *Contract) { + statedb := NoopStateDB{} + + evmConfig := Config{ + Tracer: &noopTracer{}, + } + + evm := NewEVM( + Context{ + BlockNumber: big.NewInt(1), + GasPrice: big.NewInt(1), + CanTransfer: NoopCanTransfer, + Transfer: NoopTransfer, + NewContractAddress: newContractAddress, + }, + statedb, + &ChainConfig{ChainConfig: *params.TestChainConfig}, evmConfig) + + contract := &Contract{ + CallerAddress: common.HexToAddress("0x01"), + Code: []byte{0x60, 0x02, 0x5b, 0x00}, + CodeHash: common.HexToHash("somehash"), + CodeAddr: codeAddr, + Gas: 500000, + DelegateCall: true, + } + + contractCode := []byte{0x60, 0x00} + contract.SetCode(common.BytesToHash(contractCode), contractCode) + + return evm, contract +} + +func TestCall(t *testing.T) { + codeAddr := common.BytesToAddress([]byte{1}) + evm, _ := setupEvmTestContract(&codeAddr) + + caller := AccountRef(common.HexToAddress("0x01")) + contractAddr := common.HexToAddress("0x1") + input := []byte{} + + ret, leftOverGas, err := evm.Call(caller, contractAddr, input, 1000000, big.NewInt(100000)) + + assert.Nil(t, err) + assert.Nil(t, ret) + assert.NotNil(t, leftOverGas) +} + +func TestCallCode(t *testing.T) { + codeAddr := common.BytesToAddress([]byte{1}) + evm, _ := setupEvmTestContract(&codeAddr) + + caller := AccountRef(common.HexToAddress("0x01")) + contractAddr := common.HexToAddress("0x1") + input := []byte{} + + ret, leftOverGas, err := evm.CallCode(caller, contractAddr, input, 1000000, big.NewInt(100000)) + + assert.Nil(t, err) + assert.Nil(t, ret) + assert.NotNil(t, leftOverGas) +} + +func TestDelegateCall(t *testing.T) { + codeAddr := common.BytesToAddress([]byte{1}) + evm, _ := setupEvmTestContract(&codeAddr) + + parentCallerAddress := common.HexToAddress("0x01") + objectAddress := common.HexToAddress("0x03") + input := []byte{} + + parentContract := NewContract(AccountRef(parentCallerAddress), AccountRef(parentCallerAddress), big.NewInt(2000), 5000) + childContract := NewContract(parentContract, AccountRef(objectAddress), big.NewInt(2000), 5000) + + ret, leftOverGas, err := evm.DelegateCall(childContract, parentContract.CallerAddress, input, 1000000) + + assert.Nil(t, err) + assert.Nil(t, ret) + assert.NotNil(t, leftOverGas) +} + +func TestStaticCall(t *testing.T) { + codeAddr := common.BytesToAddress([]byte{1}) + evm, _ := setupEvmTestContract(&codeAddr) + + parentCallerAddress := common.HexToAddress("0x01") + objectAddress := common.HexToAddress("0x03") + input := []byte{} + + parentContract := NewContract(AccountRef(parentCallerAddress), AccountRef(parentCallerAddress), big.NewInt(2000), 5000) + childContract := NewContract(parentContract, AccountRef(objectAddress), big.NewInt(2000), 5000) + + ret, leftOverGas, err := evm.StaticCall(childContract, parentContract.CallerAddress, input, 1000000) + + assert.Nil(t, err) + assert.Nil(t, ret) + assert.NotNil(t, leftOverGas) +} + +func TestCreate(t *testing.T) { + codeAddr := common.BytesToAddress([]byte{1}) + evm, _ := setupEvmTestContract(&codeAddr) + + parentCallerAddress := common.HexToAddress("0x01234567A") + input := []byte{} + + ret, addr, leftOverGas, err := evm.Create(AccountRef(parentCallerAddress), input, 1000000, big.NewInt(2000)) + + assert.Nil(t, err) + assert.NotNil(t, addr) + assert.Nil(t, ret) + assert.NotNil(t, leftOverGas) +} + +func TestCreate2(t *testing.T) { + codeAddr := common.BytesToAddress([]byte{1}) + evm, _ := setupEvmTestContract(&codeAddr) + + parentCallerAddress := common.HexToAddress("0x01234567A") + input := []byte{} + + ret, addr, leftOverGas, err := evm.Create2(AccountRef(parentCallerAddress), input, 10000, big.NewInt(2000), uint256.NewInt(10000)) + + assert.Nil(t, err) + assert.NotNil(t, addr) + assert.Nil(t, ret) + assert.NotNil(t, leftOverGas) +} diff --git a/vm/gas_table_test.go b/vm/gas_table_test.go index 1b91aee56..d7e2c5773 100644 --- a/vm/gas_table_test.go +++ b/vm/gas_table_test.go @@ -16,10 +16,46 @@ package vm -import "testing" +import ( + "math/big" + "reflect" + "runtime" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" +) + +func newContractAddress(evm *EVM, counter uint32) common.Address { + return common.HexToAddress("0x012345657ABC") +} + +func GetFunctionArguments() (*EVM, *Stack) { + statedb := NoopStateDB{} + + evm := NewEVM(Context{ + BlockNumber: big.NewInt(1), + GasPrice: big.NewInt(1), + CanTransfer: NoopCanTransfer, + Transfer: NoopTransfer, + NewContractAddress: newContractAddress, + }, + statedb, + &ChainConfig{ChainConfig: *params.TestChainConfig}, Config{}) + + stack := &Stack{} + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + + return evm, stack +} func TestMemoryGasCost(t *testing.T) { - //size := uint64(math.MaxUint64 - 64) size := uint64(0xffffffffe0) v, err := memoryGasCost(&Memory{}, size) if err != nil { @@ -34,3 +70,73 @@ func TestMemoryGasCost(t *testing.T) { t.Error("expected error") } } + +func TestGasFunctions(t *testing.T) { + evm, stack := GetFunctionArguments() + + // Define the function signature + type gasFuncType func(params.GasTable, *EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) + + // Create a struct to hold a function reference and its expected result + type testItem struct { + function gasFuncType + memerySize uint64 + expected uint64 + } + + // Create a list of functions to test + tests := []testItem{ + {gasCallDataCopy, 0, uint64(0x1800000000000003)}, + {gasReturnDataCopy, 0, uint64(0x1800000000000003)}, + {gasSha3, 0, uint64(0x300000000000001e)}, + {gasCodeCopy, 0, uint64(0x1800000000000003)}, + {gasExtCodeCopy, 0, uint64(0x1800000000000000)}, + {gasExtCodeHash, 0, uint64(0x0)}, + {gasMLoad, 0, uint64(0x3)}, + {gasMStore8, 0, uint64(0x3)}, + {gasMStore, 0, uint64(0x3)}, + {gasCreate, 0, uint64(0x7d00)}, + {gasCreate2, 0, uint64(0x3000000000007d00)}, + {gasBalance, 0, uint64(0x0)}, + {gasExtCodeSize, 0, uint64(0x0)}, + {gasSLoad, 0, uint64(0x0)}, + {gasExp, 0, uint64(0xa)}, + {gasReturn, 0, uint64(0x0)}, + {gasRevert, 0, uint64(0x0)}, + {gasDelegateCall, 0, uint64(0xffffffffffffffff)}, + {gasStaticCall, 0, uint64(0xffffffffffffffff)}, + {gasPush, 0, uint64(0x3)}, + {gasSwap, 0, uint64(0x3)}, + {gasDup, 0, uint64(0x3)}, + } + + for _, test := range tests { + result, err := test.function(params.GasTable{}, evm, &Contract{}, stack, &Memory{}, test.memerySize) + if err != nil { + t.Errorf("Function %v returned an error: %v", runtime.FuncForPC(reflect.ValueOf(test.function).Pointer()).Name(), err) + } + assert.Equal(t, result, test.expected, "Mismatch in gas calculation for function %v", runtime.FuncForPC(reflect.ValueOf(test.function).Pointer()).Name()) + } +} + +func TestGasCall(t *testing.T) { + evm, stack := GetFunctionArguments() + gas, _ := gasCall(params.GasTable{}, evm, &Contract{}, stack, &Memory{}, 0) + + assert.Equal(t, gas, uint64(0x0)) +} + +func TestGasCallCode(t *testing.T) { + evm, stack := GetFunctionArguments() + gas, _ := gasCallCode(params.GasTable{}, evm, &Contract{}, stack, &Memory{}, 0) + + assert.Equal(t, gas, uint64(0x0)) +} + +func TestGasLog(t *testing.T) { + evm, stack := GetFunctionArguments() + gasFunc := makeGasLog(0) + + gas, _ := gasFunc(params.GasTable{}, evm, &Contract{}, stack, &Memory{}, 0) + assert.Equal(t, gas, uint64(0x0)) +} diff --git a/vm/gas_test.go b/vm/gas_test.go new file mode 100644 index 000000000..fc838dcb0 --- /dev/null +++ b/vm/gas_test.go @@ -0,0 +1,52 @@ +package vm + +import ( + "testing" + + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +func TestCallGas(t *testing.T) { + gasTable := params.GasTableEIP150 + + // Define test cases + tests := []struct { + name string + availableGas uint64 + base uint64 + callCost *uint256.Int + expectedGas uint64 + expectingError bool + }{ + { + name: "Basic Calculation", + availableGas: 1000, + base: 200, + callCost: uint256.NewInt(500), + expectedGas: 500, + expectingError: false, + }, + { + name: "Invalid Gas", + availableGas: 1000, + base: 200, + callCost: uint256.NewInt(351238154142), + expectedGas: 788, + expectingError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := callGas(gasTable, tt.availableGas, tt.base, tt.callCost) + if (err != nil) != tt.expectingError { + t.Errorf("callGas() error = %v, expectingError %v", err, tt.expectingError) + return + } + if got != tt.expectedGas { + t.Errorf("callGas() = %v, want %v", got, tt.expectedGas) + } + }) + } +} diff --git a/vm/interpreter_test.go b/vm/interpreter_test.go new file mode 100644 index 000000000..35bd862bd --- /dev/null +++ b/vm/interpreter_test.go @@ -0,0 +1,94 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/stretchr/testify/assert" +) + +func GetNewInterpreter(jumpTable JumpTable) *Interpreter { + statedb := NoopStateDB{} + + evmConfig := Config{ + Tracer: &noopTracer{}, + JumpTable: jumpTable, + } + + evm := NewEVM(Context{ + BlockNumber: big.NewInt(1), + GasPrice: big.NewInt(1), + CanTransfer: NoopCanTransfer, + Transfer: NoopTransfer, + NewContractAddress: newContractAddress, + }, + statedb, + &ChainConfig{ChainConfig: *params.TestChainConfig}, evmConfig) + + interpreter := NewInterpreter(evm, evmConfig) + + return interpreter +} + +func GetNewContractFromBytecode(byteCode []byte) *Contract { + caller := AccountRef(common.BytesToAddress([]byte{1})) + object := AccountRef(common.BytesToAddress([]byte{2})) + + value := big.NewInt(0) // Value being sent to the contract + var gasLimit uint64 = 3000000 // Gas limit + + contract := NewContract(caller, object, value, gasLimit) + + contract.SetCode(common.BytesToHash([]byte{0x1}), byteCode) + + return contract +} + +func TestNewInterpreter(t *testing.T) { + interpreter := GetNewInterpreter(nil) + assert.NotNil(t, interpreter) +} + +func TestInterpreter_Run(t *testing.T) { + interpreter := GetNewInterpreter(nil) + + // Some valid byteCode + byteCode := []byte{0x60} + + contract := GetNewContractFromBytecode(byteCode) + + ret, err := interpreter.Run(contract, []byte{0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f}) + + assert.Nil(t, ret) + assert.Nil(t, err) +} + +func TestInterpreterInvalidStack_Run(t *testing.T) { + interpreter := GetNewInterpreter(nil) + + // Some valid byteCode + byteCode := []byte{0xF0, 0x10, 0x60, 0x00, 0x56} + + contract := GetNewContractFromBytecode(byteCode) + + ret, err := interpreter.Run(contract, []byte{0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f}) + + assert.Nil(t, ret) + assert.NotNil(t, err) +} + +func TestInterpreterInvalidOpcode_Run(t *testing.T) { + interpreter := GetNewInterpreter(nil) + + // Some invalid byteCode + byteCode := []byte{0xfe} + + contract := GetNewContractFromBytecode(byteCode) + + ret, err := interpreter.Run(contract, []byte{0x1a, 0x2b, 0x3c, 0x4d, 0x5e, 0x6f}) + + assert.Nil(t, ret) + assert.NotNil(t, err) +} diff --git a/vm/memory_table_test.go b/vm/memory_table_test.go new file mode 100644 index 000000000..7c9b4dec5 --- /dev/null +++ b/vm/memory_table_test.go @@ -0,0 +1,262 @@ +package vm + +import ( + "math" + "testing" + + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" +) + +func mockStack(data ...int64) *Stack { + stack := newstack() + + for _, item := range data { + // Convert int64 to uint256 and push onto the stack + stack.push(uint256.NewInt(uint64(item))) + } + + return stack +} + +// Test for memorySha3 function +func TestMemorySha3(t *testing.T) { + tests := []struct { + name string + stackData []int64 + expected uint64 + overflow bool + }{ + { + name: "Normal case", + stackData: []int64{10, 32}, + expected: 42, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stack := mockStack(tt.stackData...) + got, _ := memorySha3(stack) + if got != tt.expected { + t.Errorf("memorySha3() got = %v, want %v", got, tt.expected) + } + }) + } +} + +// Test for memoryCallDataCopy function +func TestMemoryCallDataCopy(t *testing.T) { + tests := []struct { + name string + stackData []int64 + expected uint64 + }{ + { + name: "Normal case", + stackData: []int64{0, 10, 32}, // Position 0, Size 32 + expected: 0, + }, + { + name: "Overflow case", + stackData: []int64{0, math.MaxInt64, 1}, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stack := mockStack(tt.stackData...) + got, _ := memoryCallDataCopy(stack) + if got != tt.expected { + t.Errorf("memoryCallDataCopy() got = %v, want %v", got, tt.expected) + } + }) + } +} + +// Test for memoryReturnDataCopy function +func TestMemoryReturnDataCopy(t *testing.T) { + tests := []struct { + name string + stackData []int64 + expected uint64 + }{ + { + name: "Normal case", + stackData: []int64{0, 10, 32}, // Position 0, Size 32 + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stack := mockStack(tt.stackData...) + got, _ := memoryReturnDataCopy(stack) + if got != tt.expected { + t.Errorf("memoryReturnDataCopy() got = %v, want %v", got, tt.expected) + } + }) + } +} + +// Test for memoryCodeCopy function +func TestMemoryCodeCopy(t *testing.T) { + tests := []struct { + name string + stackData []int64 + expected uint64 + }{ + { + name: "Normal case", + stackData: []int64{0, 10, 32}, // Position 0, Size 32 + expected: 0, + }, + // Additional test cases can be added here... + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stack := mockStack(tt.stackData...) + got, _ := memoryCodeCopy(stack) + if got != tt.expected { + t.Errorf("memoryCodeCopy() got = %v, want %v", got, tt.expected) + } + }) + } +} + +// Test for memoryExtCodeCopy function +func TestMemoryExtCodeCopy(t *testing.T) { + stack := mockStack(0, 10, 0, 32) // Example stack data + expected := uint64(0) // Replace with expected value + got, _ := memoryExtCodeCopy(stack) + if got != expected { + t.Errorf("memoryExtCodeCopy() got = %v, want %v", got, expected) + } +} + +// Test for memoryMLoad function +func TestMemoryMLoad(t *testing.T) { + stack := mockStack(10) // Example stack data + expected := uint64(42) // Replace with expected value + got, _ := memoryMLoad(stack) + if got != expected { + t.Errorf("memoryMLoad() got = %v, want %v", got, expected) + } +} + +// Test for memoryMStore8 function +func TestMemoryMStore8(t *testing.T) { + stack := mockStack(10) // Example stack data + expected := uint64(11) // Replace with expected value + got, _ := memoryMStore8(stack) + if got != expected { + t.Errorf("memoryMStore8() got = %v, want %v", got, expected) + } +} + +// Test for memoryMStore function +func TestMemoryMStore(t *testing.T) { + stack := mockStack(10) // Example stack data + expected := uint64(42) // Replace with expected value + got, _ := memoryMStore(stack) + if got != expected { + t.Errorf("memoryMStore() got = %v, want %v", got, expected) + } +} + +// Test for memoryCreate function +func TestMemoryCreate(t *testing.T) { + stack := mockStack(0, 10, 32) // Example stack data + expected := uint64(0) // Replace with expected value + got, _ := memoryCreate(stack) + if got != expected { + t.Errorf("memoryCreate() got = %v, want %v", got, expected) + } +} + +// Test for memoryCreate2 function +func TestMemoryCreate2(t *testing.T) { + stack := mockStack(0, 10, 32) // Example stack data + expected := uint64(0) // Replace with expected value + got, _ := memoryCreate2(stack) + if got != expected { + t.Errorf("memoryCreate2() got = %v, want %v", got, expected) + } +} + +// Test for memoryCall function +func TestMemoryCall(t *testing.T) { + stack := mockStack(0, 0, 0, 10, 0, 0, 32) // Example stack data + expected := uint64(0) // Replace with expected value + got, _ := memoryCall(stack) + if got != expected { + t.Errorf("memoryCall() got = %v, want %v", got, expected) + } +} + +func TestMemoryCallOverflow(t *testing.T) { + stack := mockStack() // Example stack data + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + stack.push(uint256.NewInt(uint64(math.MaxUint64))) + + _, overflow := memoryCall(stack) + + assert.True(t, overflow) +} + +// Test for memoryDelegateCall function +func TestMemoryDelegateCall(t *testing.T) { + stack := mockStack(0, 0, 0, 10, 0, 32) // Example stack data + expected := uint64(0) // Replace with expected value + got, _ := memoryDelegateCall(stack) + if got != expected { + t.Errorf("memoryDelegateCall() got = %v, want %v", got, expected) + } +} + +// Test for memoryStaticCall function +func TestMemoryStaticCall(t *testing.T) { + stack := mockStack(0, 0, 0, 10, 0, 32) // Example stack data + expected := uint64(0) // Replace with expected value + got, _ := memoryStaticCall(stack) + if got != expected { + t.Errorf("memoryStaticCall() got = %v, want %v", got, expected) + } +} + +// Test for memoryReturn function +func TestMemoryReturn(t *testing.T) { + stack := mockStack(10, 32) // Example stack data + expected := uint64(42) // Replace with expected value + got, _ := memoryReturn(stack) + if got != expected { + t.Errorf("memoryReturn() got = %v, want %v", got, expected) + } +} + +// Test for memoryRevert function +func TestMemoryRevert(t *testing.T) { + stack := mockStack(10, 32) // Example stack data + expected := uint64(42) // Replace with expected value + got, _ := memoryRevert(stack) + if got != expected { + t.Errorf("memoryRevert() got = %v, want %v", got, expected) + } +} + +// Test for memoryLog function +func TestMemoryLog(t *testing.T) { + stack := mockStack(10, 32) // Example stack data + expected := uint64(42) // Replace with expected value + got, _ := memoryLog(stack) + if got != expected { + t.Errorf("memoryLog() got = %v, want %v", got, expected) + } +} diff --git a/vm/memory_test.go b/vm/memory_test.go new file mode 100644 index 000000000..28f56e2b2 --- /dev/null +++ b/vm/memory_test.go @@ -0,0 +1,73 @@ +package vm + +import ( + "testing" + + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" +) + +func TestNewMemory(t *testing.T) { + mem := NewMemory() + assert.NotNil(t, mem) + mem.Set(1, 0, []byte{}) + + mem.Print() +} + +func TestSet(t *testing.T) { + mem := NewMemory() + assert.NotNil(t, mem) + + testData := []byte{1, 2, 3, 4} + mem.Resize(uint64(len(testData))) + mem.Set(0, uint64(len(testData)), testData) + + retrievedData := mem.GetPtr(0, int64(len(testData))) + assert.Equal(t, testData, retrievedData) + + mem.Print() + + data := mem.Data() + + assert.NotNil(t, data) +} + +func TestSet32AndGetCopy(t *testing.T) { + mem := NewMemory() + assert.NotNil(t, mem) + + // Create a uint256 value and set it in memory + testValue := uint256.NewInt(123).SetUint64(12345678) + mem.Resize(32) // Ensure there's enough space + mem.Set32(0, testValue) + + // Retrieve the value using GetCopy + copiedData := mem.GetCopy(0, 32) + assert.NotNil(t, copiedData, "Copied data should not be nil") + + // Convert copiedData back to uint256 for comparison + retrievedValue := new(uint256.Int).SetBytes(copiedData) + assert.Equal(t, testValue, retrievedValue, "Retrieved value should match the original") +} + +func TestSetPanic(t *testing.T) { + mem := NewMemory() + assert.NotNil(t, mem) + + // This should cause a panic because the memory store is not resized + assert.Panics(t, func() { + mem.Set(0, 10, []byte{1, 2, 3, 4}) // Trying to set 10 bytes in an empty store + }, "Set should panic when trying to set more data than the size of the store") +} + +func TestSet32Panic(t *testing.T) { + mem := NewMemory() + assert.NotNil(t, mem) + + // This should cause a panic because the memory store is not resized + testVal := uint256.NewInt(123).SetUint64(12345) + assert.Panics(t, func() { + mem.Set32(0, testVal) // Trying to set 32 bytes in an empty store + }, "Set32 should panic when trying to set 32 bytes in an empty store") +} diff --git a/vm/noop_test.go b/vm/noop_test.go new file mode 100644 index 000000000..605757c57 --- /dev/null +++ b/vm/noop_test.go @@ -0,0 +1,158 @@ +package vm + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/assert" +) + +func TestNoopCanTransfer(t *testing.T) { + assert.True(t, NoopCanTransfer(nil, common.Address{}, big.NewInt(0)), "NoopCanTransfer should always return true") +} + +func TestNoopTransfer(t *testing.T) { + assert.NotPanics(t, func() { NoopTransfer(nil, common.Address{}, common.Address{}, big.NewInt(0)) }, "NoopTransfer should not panic") +} + +func TestNoopEVMCallContext_Call(t *testing.T) { + var ctx NoopEVMCallContext + data, err := ctx.Call(nil, common.Address{}, nil, big.NewInt(0), big.NewInt(0)) + assert.Nil(t, err, "Call should not return an error") + assert.Nil(t, data, "Call should return nil data") +} + +func TestNoopEVMCallContext_CallCode(t *testing.T) { + var ctx NoopEVMCallContext + data, err := ctx.CallCode(nil, common.Address{}, nil, big.NewInt(0), big.NewInt(0)) + assert.Nil(t, err, "CallCode should not return an error") + assert.Nil(t, data, "CallCode should return nil data") +} + +func TestNoopEVMCallContext_Create(t *testing.T) { + var ctx NoopEVMCallContext + data, addr, err := ctx.Create(nil, nil, big.NewInt(0), big.NewInt(0)) + assert.Nil(t, err, "Create should not return an error") + assert.Nil(t, data, "Create should return nil data") + assert.Equal(t, common.Address{}, addr, "Create should return an empty address") +} + +func TestNoopEVMCallContext_DelegateCall(t *testing.T) { + var ctx NoopEVMCallContext + data, err := ctx.DelegateCall(nil, common.Address{}, nil, big.NewInt(0)) + assert.Nil(t, err, "DelegateCall should not return an error") + assert.Nil(t, data, "DelegateCall should return nil data") +} + +func TestNoopStateDB_GetBalance(t *testing.T) { + var db NoopStateDB + balance := db.GetBalance(common.Address{}) + assert.Nil(t, balance, "GetBalance should return nil") +} + +func TestNoopStateDB_CreateAccount(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.CreateAccount(common.Address{}) }, "CreateAccount should not panic") +} + +func TestNoopStateDB_SubBalance(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.SubBalance(common.Address{}, big.NewInt(0)) }, "SubBalance should not panic") +} + +func TestNoopStateDB_AddBalance(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.AddBalance(common.Address{}, big.NewInt(0)) }, "AddBalance should not panic") +} + +func TestNoopStateDB_SetNonce(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.SetNonce(common.Address{}, 0) }, "SetNonce should not panic") +} + +func TestNoopStateDB_GetNonce(t *testing.T) { + var db NoopStateDB + assert.Equal(t, db.GetNonce(common.Address{}), uint64(0)) +} + +func TestNoopStateDB_GetCodeSize(t *testing.T) { + var db NoopStateDB + assert.Equal(t, db.GetCodeSize(common.Address{}), 0) +} + +func TestNoopStateDB_GetRefund(t *testing.T) { + var db NoopStateDB + assert.Equal(t, db.GetRefund(), uint64(0)) +} + +func TestNoopStateDB_GetCodeHash(t *testing.T) { + var db NoopStateDB + assert.Equal(t, db.GetCodeHash(common.Address{}), common.Hash{}) +} + +func TestNoopStateDB_GetCode(t *testing.T) { + var db NoopStateDB + assert.Nil(t, db.GetCode(common.Address{})) +} + +func TestNoopStateDB_SetCode(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.SetCode(common.Address{}, []byte{}) }, "SetCode should not panic") +} + +func TestNoopStateDB_AddRefund(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.AddRefund(0) }, "AddRefund should not panic") +} + +func TestNoopStateDB_SetState(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.SetState(common.Address{}, common.Hash{}, common.Hash{}) }, "SetState should not panic") +} + +func TestNoopStateDB_Suicide(t *testing.T) { + var db NoopStateDB + assert.False(t, db.Suicide(common.Address{}), "Suicide should return false") +} + +func TestNoopStateDB_HasSuicided(t *testing.T) { + var db NoopStateDB + assert.False(t, db.HasSuicided(common.Address{}), "HasSuicided should return false") +} + +func TestNoopStateDB_Exist(t *testing.T) { + var db NoopStateDB + assert.False(t, db.Exist(common.Address{}), "Exist should return false") +} + +func TestNoopStateDB_Empty(t *testing.T) { + var db NoopStateDB + assert.False(t, db.Empty(common.Address{}), "Empty should return false") +} + +func TestNoopStateDB_RevertToSnapshot(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.RevertToSnapshot(0) }, "RevertToSnapshot should not panic") +} + +func TestNoopStateDB_Snapshot(t *testing.T) { + var db NoopStateDB + assert.Equal(t, 0, db.Snapshot(), "Snapshot should return 0") +} + +func TestNoopStateDB_AddLog(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.AddLog(&types.Log{}) }, "AddLog should not panic") +} + +func TestNoopStateDB_AddPreimage(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.AddPreimage(common.Hash{}, []byte{}) }, "AddPreimage should not panic") +} + +func TestNoopStateDB_ForEachStorage(t *testing.T) { + var db NoopStateDB + assert.NotPanics(t, func() { db.ForEachStorage(common.Address{}, func(common.Hash, common.Hash) bool { return true }) }, "ForEachStorage should not panic") +} diff --git a/vm/opcodes_test.go b/vm/opcodes_test.go new file mode 100644 index 000000000..b7494134e --- /dev/null +++ b/vm/opcodes_test.go @@ -0,0 +1,67 @@ +package vm + +import ( + "testing" +) + +// TestOpCodeString tests the String method of OpCode. +func TestOpCodeString(t *testing.T) { + tests := []struct { + op OpCode + expected string + }{ + {STOP, "STOP"}, + {ADD, "ADD"}, + {MUL, "MUL"}, + // ... add more tests for different opcodes + } + + for _, tt := range tests { + actual := tt.op.String() + if actual != tt.expected { + t.Errorf("OpCode.String() for %v: expected %s, got %s", tt.op, tt.expected, actual) + } + } +} + +// TestStringToOp tests the StringToOp function. +func TestStringToOp(t *testing.T) { + tests := []struct { + name string + expected OpCode + }{ + {"STOP", STOP}, + {"ADD", ADD}, + {"MUL", MUL}, + // ... add more tests for different opcode names + } + + for _, tt := range tests { + actual := StringToOp(tt.name) + if actual != tt.expected { + t.Errorf("StringToOp(%s): expected %v, got %v", tt.name, tt.expected, actual) + } + } +} + +// TestOpCodeIsPush tests the IsPush method of OpCode. +func TestOpCodeIsPush(t *testing.T) { + if !PUSH1.IsPush() { + t.Errorf("PUSH1 should be a push operation") + } + if STOP.IsPush() { + t.Errorf("STOP should not be a push operation") + } + // ... add more tests for different opcodes +} + +// TestOpCodeIsStaticJump tests the IsStaticJump method of OpCode. +func TestOpCodeIsStaticJump(t *testing.T) { + if !JUMP.IsStaticJump() { + t.Errorf("JUMP should be a static jump operation") + } + if ADD.IsStaticJump() { + t.Errorf("ADD should not be a static jump operation") + } + // ... add more tests for different opcodes +} diff --git a/vm/stack_test.go b/vm/stack_test.go new file mode 100644 index 000000000..08ade652c --- /dev/null +++ b/vm/stack_test.go @@ -0,0 +1,75 @@ +package vm + +import ( + "testing" + + "github.com/holiman/uint256" + "github.com/stretchr/testify/assert" +) + +func TestStackPushPop(t *testing.T) { + stack := newstack() + defer returnStack(stack) + + val := uint256.NewInt(42) + stack.push(val) + + assert.Equal(t, 1, stack.len(), "Stack should have one item after push") + assert.Equal(t, val, stack.peek(), "Top item should be the one that was pushed") + + popped := stack.pop() + assert.Equal(t, val, &popped, "Popped item should be equal to pushed item") + assert.Equal(t, 0, stack.len(), "Stack should be empty after pop") +} + +func TestStackSwap(t *testing.T) { + stack := newstack() + defer returnStack(stack) + + first := uint256.NewInt(1) + second := uint256.NewInt(2) + stack.push(first) + stack.push(second) + + stack.swap(2) + assert.Equal(t, first, stack.peek(), "Top item should be the first one after swap") +} + +func TestStackDup(t *testing.T) { + stack := newstack() + defer returnStack(stack) + + val := uint256.NewInt(42) + stack.push(val) + stack.dup(1) + + assert.Equal(t, 2, stack.len(), "Stack should have two items after dup") + assert.Equal(t, val, stack.peek(), "Top item should be the same as the duplicated one") +} + +func TestStackBack(t *testing.T) { + stack := newstack() + defer returnStack(stack) + + first := uint256.NewInt(1) + second := uint256.NewInt(2) + stack.push(first) + stack.push(second) + + back := stack.Back(1) + assert.Equal(t, first, back, "Back should return the first item") +} + +func TestStackPrint(t *testing.T) { + stack := newstack() + defer returnStack(stack) + + // Test printing an empty and a non-empty stack + // Since Print() doesn't return anything, we are just + // making sure it doesn't crash for now + stack.Print() + + val := uint256.NewInt(42) + stack.push(val) + stack.Print() +}