From f20097c401e7f34506cfd273ff6ed7b957e654d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= <93934272+Stefan-Ethernal@users.noreply.github.com> Date: Wed, 29 May 2024 13:37:58 +0200 Subject: [PATCH] Introducing OptimizedStack based on uint256 integers (#170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introducing OptimizedStack based on uint256 integers (#156) * Introducing OptimizedStack based on uint256 integers. * Replacing big.int with uint256 for jump instruction. * Update state/runtime/evm/instructions.go Co-authored-by: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> * Code review fixes. * opMStore is changed to use newly introduced WriteToSlice() function with unrolled loop. This allows CPU to execute multiple instructions in parallel providing >200% increased speed compared to uint256.WriteToSlice() implementation. If needed, can be additionally implemented for different array lengths. * Fixing linter errors. * setBytes to use uint256 instead of big.int. * Fixing linter errors. * Go mod tidy * Lint fix * Revert go version * Address comments * Remove pointer dereferencing and use value type reference --------- Co-authored-by: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> Co-authored-by: Stefan Negovanović * Updating tracer to use optimized stack directly, instead of big.int. * Removing remaining big.int references from instructions.go and state.go where possible. Removing unused functions and constants. * Adding error handling for invalid balance and invalid message value. * Adding benchmark tests for OptimizedStack. * Group dependencies * Fixing state error code name for errInvalidBalanceValue. --------- Co-authored-by: cokicm Co-authored-by: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> --- e2e-polybft/e2e/jsonrpc_test.go | 2 +- e2e-polybft/e2e/storage_test.go | 2 +- e2e-polybft/e2e/txpool_test.go | 4 +- go.mod | 1 + go.sum | 2 + state/runtime/evm/dispatch_table_test.go | 7 +- state/runtime/evm/evm_test.go | 13 +- state/runtime/evm/instructions.go | 565 +++++++-------- .../evm/instructions_benchmark_test.go | 281 ++++++++ state/runtime/evm/instructions_test.go | 681 ++++++++---------- state/runtime/evm/optimized_stack.go | 80 ++ .../evm/optimized_stack_benchmark_test.go | 84 +++ state/runtime/evm/optimized_stack_test.go | 102 +++ state/runtime/evm/state.go | 113 ++- state/runtime/evm/state_test.go | 7 +- state/runtime/runtime.go | 3 +- .../runtime/tracer/calltracer/call_tracer.go | 3 +- state/runtime/tracer/structtracer/tracer.go | 35 +- .../tracer/structtracer/tracer_test.go | 33 +- state/runtime/tracer/types.go | 3 +- 20 files changed, 1188 insertions(+), 833 deletions(-) create mode 100644 state/runtime/evm/instructions_benchmark_test.go create mode 100644 state/runtime/evm/optimized_stack.go create mode 100644 state/runtime/evm/optimized_stack_benchmark_test.go create mode 100644 state/runtime/evm/optimized_stack_test.go diff --git a/e2e-polybft/e2e/jsonrpc_test.go b/e2e-polybft/e2e/jsonrpc_test.go index 6c1248a916..bd40fae31a 100644 --- a/e2e-polybft/e2e/jsonrpc_test.go +++ b/e2e-polybft/e2e/jsonrpc_test.go @@ -223,7 +223,7 @@ func TestE2E_JsonRPC(t *testing.T) { require.NoError(t, err) resp, err := ethClient.Call(&jsonrpc.CallMsg{ - From: types.Address(acctZeroBalance.Address()), + From: acctZeroBalance.Address(), To: &target, Data: input, }, jsonrpc.LatestBlockNumber, nil) diff --git a/e2e-polybft/e2e/storage_test.go b/e2e-polybft/e2e/storage_test.go index a031fd9a75..d9b5881d5d 100644 --- a/e2e-polybft/e2e/storage_test.go +++ b/e2e-polybft/e2e/storage_test.go @@ -66,7 +66,7 @@ func TestE2E_Storage(t *testing.T) { } txn.SetFrom(sender.Address()) - txn.SetTo((*types.Address)(&to)) + txn.SetTo(&to) txn.SetGas(21000) txn.SetValue(big.NewInt(int64(i))) txn.SetNonce(uint64(i)) diff --git a/e2e-polybft/e2e/txpool_test.go b/e2e-polybft/e2e/txpool_test.go index c1d007cd91..cac5a9e991 100644 --- a/e2e-polybft/e2e/txpool_test.go +++ b/e2e-polybft/e2e/txpool_test.go @@ -59,7 +59,7 @@ func TestE2E_TxPool_Transfer(t *testing.T) { if i%2 == 0 { txData = types.NewDynamicFeeTx( types.WithFrom(sender.Address()), - types.WithTo((*types.Address)(&to)), + types.WithTo(&to), types.WithGas(30000), // enough to send a transfer types.WithValue(big.NewInt(int64(sendAmount))), types.WithNonce(uint64(i)), @@ -69,7 +69,7 @@ func TestE2E_TxPool_Transfer(t *testing.T) { } else { txData = types.NewLegacyTx( types.WithFrom(sender.Address()), - types.WithTo((*types.Address)(&to)), + types.WithTo(&to), types.WithGas(30000), types.WithValue(big.NewInt(int64(sendAmount))), types.WithNonce(uint64(i)), diff --git a/go.mod b/go.mod index 935a55d8fe..14e7aca5bc 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 github.com/hashicorp/hcl v1.0.1-vault-5 github.com/hashicorp/vault/api v1.13.0 + github.com/holiman/uint256 v1.2.4 github.com/json-iterator/go v1.1.12 github.com/libp2p/go-libp2p v0.33.2 github.com/libp2p/go-libp2p-kbucket v0.6.3 diff --git a/go.sum b/go.sum index 27b4534906..7cc0f18161 100644 --- a/go.sum +++ b/go.sum @@ -316,6 +316,8 @@ github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31 github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/vault/api v1.13.0 h1:RTCGpE2Rgkn9jyPcFlc7YmNocomda44k5ck8FKMH41Y= github.com/hashicorp/vault/api v1.13.0/go.mod h1:0cb/uZUv1w2cVu9DIvuW1SMlXXC6qtATJt+LXJRx+kg= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= diff --git a/state/runtime/evm/dispatch_table_test.go b/state/runtime/evm/dispatch_table_test.go index 9c296660aa..1efed0f376 100644 --- a/state/runtime/evm/dispatch_table_test.go +++ b/state/runtime/evm/dispatch_table_test.go @@ -25,11 +25,10 @@ func TestPushOpcodes(t *testing.T) { assert.False(t, s.stop) - res := s.pop().Bytes() - assert.Len(t, res, c) - - assert.True(t, bytes.HasPrefix(code[1:], res)) + v := s.pop() + assert.Len(t, v.Bytes(), c) + assert.True(t, bytes.HasPrefix(code[1:], v.Bytes())) c++ } } diff --git a/state/runtime/evm/evm_test.go b/state/runtime/evm/evm_test.go index f690305fcb..119326307a 100644 --- a/state/runtime/evm/evm_test.go +++ b/state/runtime/evm/evm_test.go @@ -8,6 +8,7 @@ import ( "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/state/runtime/tracer" "github.com/0xPolygon/polygon-edge/types" + "github.com/holiman/uint256" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -259,7 +260,7 @@ type mockTracer struct { func (m *mockTracer) CaptureState( memory []byte, - stack []*big.Int, + stack []uint256.Int, opCode int, contractAddress types.Address, sp int, @@ -330,7 +331,7 @@ func TestRunWithTracer(t *testing.T) { name: "CaptureState", args: map[string]interface{}{ "memory": []byte{}, - "stack": []*big.Int{}, + "stack": []uint256.Int{}, "opCode": int(PUSH1), "contractAddress": contractAddress, "sp": 0, @@ -353,8 +354,8 @@ func TestRunWithTracer(t *testing.T) { name: "CaptureState", args: map[string]interface{}{ "memory": []byte{}, - "stack": []*big.Int{ - big.NewInt(1), + "stack": []uint256.Int{ + *uint256.NewInt(1), }, "opCode": int(0), "contractAddress": contractAddress, @@ -375,7 +376,7 @@ func TestRunWithTracer(t *testing.T) { name: "CaptureState", args: map[string]interface{}{ "memory": []byte{}, - "stack": []*big.Int{}, + "stack": []uint256.Int{}, "opCode": int(POP), "contractAddress": contractAddress, "sp": 0, @@ -425,7 +426,7 @@ func TestRunWithTracer(t *testing.T) { state.config = config // make sure stack, memory, and returnData are empty - state.stack = make([]*big.Int, 0) + state.stack.data = make([]uint256.Int, 0) state.memory = make([]byte, 0) state.returnData = make([]byte, 0) diff --git a/state/runtime/evm/instructions.go b/state/runtime/evm/instructions.go index b7d369b648..c66ba052e4 100644 --- a/state/runtime/evm/instructions.go +++ b/state/runtime/evm/instructions.go @@ -3,8 +3,6 @@ package evm import ( "errors" - "math/big" - "math/bits" "sync" "github.com/0xPolygon/polygon-edge/crypto" @@ -12,6 +10,7 @@ import ( "github.com/0xPolygon/polygon-edge/helper/keccak" "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" + "github.com/holiman/uint256" ) type instruction func(c *state) @@ -23,11 +22,32 @@ const ( ) var ( - zero = big.NewInt(0) - one = big.NewInt(1) - wordSize = big.NewInt(32) + wordSize256 = uint256.NewInt(32) ) +// Checks if the value overflows 1 byte, or 8 bits +func equalOrOverflows8bits(b *uint256.Int) bool { + return b.BitLen() > 8 +} + +var bufPool = sync.Pool{ + New: func() interface{} { + // Store pointer to avoid heap allocation in caller + // Please check SA6002 in StaticCheck for details + buf := make([]byte, 128) + + return &buf + }, +} + +func min(i, j uint64) uint64 { + if i < j { + return i + } + + return j +} + func (c *state) calculateGasForEIP2929(addr types.Address) uint64 { var gas uint64 if c.host.ContainsAccessListAddress(addr) { @@ -41,106 +61,100 @@ func (c *state) calculateGasForEIP2929(addr types.Address) uint64 { return gas } +// Generic WriteToSlice function that calls optimized function when +// applicable or generic one. +func WriteToSlice(z uint256.Int, dest []byte) { + if len(dest) == 32 { + WriteToSlice32(z, dest) + } else { + z.WriteToSlice(dest) + } +} + +// Optimized write to slice when destination size is 32 bytes. This way +// the CPU does not execute code in loop achieving greater paralelization +func WriteToSlice32(z uint256.Int, dest []byte) { + dest[31] = byte(z[0] >> uint64(8*0)) + dest[30] = byte(z[0] >> uint64(8*1)) + dest[29] = byte(z[0] >> uint64(8*2)) + dest[28] = byte(z[0] >> uint64(8*3)) + dest[27] = byte(z[0] >> uint64(8*4)) + dest[26] = byte(z[0] >> uint64(8*5)) + dest[25] = byte(z[0] >> uint64(8*6)) + dest[24] = byte(z[0] >> uint64(8*7)) + dest[23] = byte(z[1] >> uint64(8*0)) + dest[22] = byte(z[1] >> uint64(8*1)) + dest[21] = byte(z[1] >> uint64(8*2)) + dest[20] = byte(z[1] >> uint64(8*3)) + dest[19] = byte(z[1] >> uint64(8*4)) + dest[18] = byte(z[1] >> uint64(8*5)) + dest[17] = byte(z[1] >> uint64(8*6)) + dest[16] = byte(z[1] >> uint64(8*7)) + dest[15] = byte(z[2] >> uint64(8*0)) + dest[14] = byte(z[2] >> uint64(8*1)) + dest[13] = byte(z[2] >> uint64(8*2)) + dest[12] = byte(z[2] >> uint64(8*3)) + dest[11] = byte(z[2] >> uint64(8*4)) + dest[10] = byte(z[2] >> uint64(8*5)) + dest[9] = byte(z[2] >> uint64(8*6)) + dest[8] = byte(z[2] >> uint64(8*7)) + dest[7] = byte(z[3] >> uint64(8*0)) + dest[6] = byte(z[3] >> uint64(8*1)) + dest[5] = byte(z[3] >> uint64(8*2)) + dest[4] = byte(z[3] >> uint64(8*3)) + dest[3] = byte(z[3] >> uint64(8*4)) + dest[2] = byte(z[3] >> uint64(8*5)) + dest[1] = byte(z[3] >> uint64(8*6)) + dest[0] = byte(z[3] >> uint64(8*7)) +} + func opAdd(c *state) { a := c.pop() b := c.top() - b.Add(a, b) - toU256(b) + b.Add(&a, b) } func opMul(c *state) { a := c.pop() b := c.top() - b.Mul(a, b) - toU256(b) + b.Mul(&a, b) } func opSub(c *state) { a := c.pop() b := c.top() - b.Sub(a, b) - toU256(b) + b.Sub(&a, b) } func opDiv(c *state) { a := c.pop() b := c.top() - if b.Sign() == 0 { - // division by zero - b.Set(zero) - } else { - b.Div(a, b) - toU256(b) - } + b.Div(&a, b) } func opSDiv(c *state) { - a := to256(c.pop()) - b := to256(c.top()) - - if b.Sign() == 0 { - // division by zero - b.Set(zero) - } else { - neg := a.Sign() != b.Sign() - b.Div(a.Abs(a), b.Abs(b)) - - if neg { - b.Neg(b) - } + a := c.pop() + b := c.top() - toU256(b) - } + b.SDiv(&a, b) } func opMod(c *state) { a := c.pop() b := c.top() - if b.Sign() == 0 { - // division by zero - b.Set(zero) - } else { - b.Mod(a, b) - toU256(b) - } + b.Mod(&a, b) } func opSMod(c *state) { - a := to256(c.pop()) - b := to256(c.top()) - - if b.Sign() == 0 { - // division by zero - b.Set(zero) - } else { - neg := a.Sign() < 0 - b.Mod(a.Abs(a), b.Abs(b)) - - if neg { - b.Neg(b) - } - - toU256(b) - } -} - -var bigPool = sync.Pool{ - New: func() interface{} { - return new(big.Int) - }, -} - -func acquireBig() *big.Int { - return bigPool.Get().(*big.Int) -} + a := c.pop() + b := c.top() -func releaseBig(b *big.Int) { - bigPool.Put(b) + b.SMod(&a, b) } func opExp(c *state) { @@ -159,23 +173,7 @@ func opExp(c *state) { return } - z := acquireBig().Set(one) - - // https://www.programminglogic.com/fast-exponentiation-algorithms/ - for _, d := range y.Bits() { - for i := 0; i < _W; i++ { - if d&1 == 1 { - toU256(z.Mul(z, x)) - } - - d >>= 1 - - toU256(x.Mul(x, x)) - } - } - - y.Set(z) - releaseBig(z) + y.Exp(&x, y) } func opAddMod(c *state) { @@ -183,14 +181,7 @@ func opAddMod(c *state) { b := c.pop() z := c.top() - if z.Sign() == 0 { - // division by zero - z.Set(zero) - } else { - a = a.Add(a, b) - z = z.Mod(a, z) - toU256(z) - } + z.AddMod(&a, &b, z) } func opMulMod(c *state) { @@ -198,67 +189,49 @@ func opMulMod(c *state) { b := c.pop() z := c.top() - if z.Sign() == 0 { - // division by zero - z.Set(zero) - } else { - a = a.Mul(a, b) - z = z.Mod(a, z) - toU256(z) - } + z.MulMod(&a, &b, z) } func opAnd(c *state) { a := c.pop() b := c.top() - b.And(a, b) + b.And(&a, b) } func opOr(c *state) { a := c.pop() b := c.top() - b.Or(a, b) + b.Or(&a, b) } func opXor(c *state) { a := c.pop() b := c.top() - b.Xor(a, b) + b.Xor(&a, b) } -var opByteMask = big.NewInt(255) - func opByte(c *state) { x := c.pop() y := c.top() - indx := x.Int64() - if indx > 31 { - y.Set(zero) - } else { - sh := (31 - indx) * 8 - y.Rsh(y, uint(sh)) - y.And(y, opByteMask) - } + y.Byte(&x) } func opNot(c *state) { a := c.top() - a.Not(a) - toU256(a) } func opIsZero(c *state) { a := c.top() - if a.Sign() == 0 { - a.Set(one) + if a.IsZero() { + a.SetOne() } else { - a.Set(zero) + a.SetUint64(0) } } @@ -266,10 +239,10 @@ func opEq(c *state) { a := c.pop() b := c.top() - if a.Cmp(b) == 0 { - b.Set(one) + if a.Eq(b) { + b.SetOne() } else { - b.Set(zero) + b.SetUint64(0) } } @@ -277,10 +250,10 @@ func opLt(c *state) { a := c.pop() b := c.top() - if a.Cmp(b) < 0 { - b.Set(one) + if a.Lt(b) { + b.SetOne() } else { - b.Set(zero) + b.SetUint64(0) } } @@ -288,32 +261,32 @@ func opGt(c *state) { a := c.pop() b := c.top() - if a.Cmp(b) > 0 { - b.Set(one) + if a.Gt(b) { + b.SetOne() } else { - b.Set(zero) + b.SetUint64(0) } } func opSlt(c *state) { - a := to256(c.pop()) - b := to256(c.top()) + a := c.pop() + b := c.top() - if a.Cmp(b) < 0 { - b.Set(one) + if a.Slt(b) { + b.SetOne() } else { - b.Set(zero) + b.SetUint64(0) } } func opSgt(c *state) { - a := to256(c.pop()) - b := to256(c.top()) + a := c.pop() + b := c.top() - if a.Cmp(b) > 0 { - b.Set(one) + if a.Sgt(b) { + b.SetOne() } else { - b.Set(zero) + b.SetUint64(0) } } @@ -321,33 +294,7 @@ func opSignExtension(c *state) { ext := c.pop() x := c.top() - if ext.Cmp(wordSize) > 0 { - return - } - - if x == nil { - return - } - - bit := uint(ext.Uint64()*8 + 7) - - mask := acquireBig().Set(one) - mask.Lsh(mask, bit) - mask.Sub(mask, one) - - if x.Bit(int(bit)) > 0 { - mask.Not(mask) - x.Or(x, mask) - } else { - x.And(x, mask) - } - - toU256(x) - releaseBig(mask) -} - -func equalOrOverflowsUint256(b *big.Int) bool { - return b.BitLen() > 8 + x.ExtendSign(x, &ext) } func opShl(c *state) { @@ -360,11 +307,10 @@ func opShl(c *state) { shift := c.pop() value := c.top() - if equalOrOverflowsUint256(shift) { - value.Set(zero) - } else { + if shift.LtUint64(256) { value.Lsh(value, uint(shift.Uint64())) - toU256(value) + } else { + value.Clear() } } @@ -378,11 +324,10 @@ func opShr(c *state) { shift := c.pop() value := c.top() - if equalOrOverflowsUint256(shift) { - value.Set(zero) - } else { + if shift.LtUint64(256) { value.Rsh(value, uint(shift.Uint64())) - toU256(value) + } else { + value.Clear() } } @@ -394,88 +339,52 @@ func opSar(c *state) { } shift := c.pop() - value := to256(c.top()) + value := c.top() - if equalOrOverflowsUint256(shift) { + if equalOrOverflows8bits(&shift) { if value.Sign() >= 0 { - value.Set(zero) + value.SetUint64(0) } else { - value.Set(tt256m1) + value.SetAllOne() } } else { - value.Rsh(value, uint(shift.Uint64())) - toU256(value) + value.SRsh(value, uint(shift.Uint64())) } } // memory operations - -var bufPool = sync.Pool{ - New: func() interface{} { - // Store pointer to avoid heap allocation in caller - // Please check SA6002 in StaticCheck for details - buf := make([]byte, 128) - - return &buf - }, -} - func opMLoad(c *state) { - offset := c.pop() + v := c.top() var ok bool - c.tmp, ok = c.get2(c.tmp[:0], offset, wordSize) + c.tmp, ok = c.get2(c.tmp[:0], *v, *wordSize256) + // ### Error handling? if !ok { return } - c.push1().SetBytes(c.tmp) + v.SetBytes(c.tmp) } -var ( - _W = bits.UintSize - _S = _W / 8 -) - func opMStore(c *state) { offset := c.pop() val := c.pop() - if !c.allocateMemory(offset, wordSize) { + if !c.allocateMemory(offset, *wordSize256) { return } o := offset.Uint64() - buf := c.memory[o : o+32] - - i := 32 - - // convert big.int to bytes - // https://golang.org/src/math/big/nat.go#L1284 - for _, d := range val.Bits() { - for j := 0; j < _S; j++ { - i-- - - buf[i] = byte(d) - - d >>= 8 - } - } - - // fill the rest of the slot with zeros - for i > 0 { - i-- - buf[i] = 0 - } + WriteToSlice(val, c.memory[o:o+32]) } func opMStore8(c *state) { offset := c.pop() val := c.pop() - if !c.allocateMemory(offset, one) { + if !c.allocateMemory(offset, *uint256.NewInt(1)) { return } @@ -490,10 +399,11 @@ func opSload(c *state) { var gas uint64 if c.config.Berlin { - if _, slotPresent := c.host.ContainsAccessListSlot(c.msg.Address, bigToHash(loc)); !slotPresent { + storageKey := uint256ToHash(loc) + if _, slotPresent := c.host.ContainsAccessListSlot(c.msg.Address, storageKey); !slotPresent { gas = ColdStorageReadCostEIP2929 - c.host.AddSlotToAccessList(c.msg.Address, bigToHash(loc)) + c.host.AddSlotToAccessList(c.msg.Address, storageKey) } else { gas = WarmStorageReadCostEIP2929 } @@ -510,7 +420,7 @@ func opSload(c *state) { return } - val := c.host.GetStorage(c.msg.Address, bigToHash(loc)) + val := c.host.GetStorage(c.msg.Address, uint256ToHash(loc)) loc.SetBytes(val.Bytes()) } @@ -607,8 +517,9 @@ func opSha3(c *state) { c.tmp = keccak.Keccak256(c.tmp[:0], c.tmp) - v := c.push1() + v := uint256.Int{0} v.SetBytes(c.tmp) + c.push(v) } func opPop(c *state) { @@ -618,7 +529,9 @@ func opPop(c *state) { // context operations func opAddress(c *state) { - c.push1().SetBytes(c.msg.Address.Bytes()) + v := uint256.Int{0} + v.SetBytes(c.msg.Address.Bytes()) + c.push(v) } func opBalance(c *state) { @@ -640,7 +553,14 @@ func opBalance(c *state) { return } - c.push1().Set(c.host.GetBalance(addr)) + balance := c.host.GetBalance(addr) + uintBalance, invalidBalanceValue := uint256.FromBig(balance) + + if invalidBalanceValue { + c.exit(errInvalidBalanceValue) + } + + c.push(*uintBalance) } func opSelfBalance(c *state) { @@ -650,7 +570,14 @@ func opSelfBalance(c *state) { return } - c.push1().Set(c.host.GetBalance(c.msg.Address)) + balance := c.host.GetBalance(c.msg.Address) + uintBalance, invalidBalanceValue := uint256.FromBig(balance) + + if invalidBalanceValue { + c.exit(errInvalidBalanceValue) + } + + c.push(*uintBalance) } func opChainID(c *state) { @@ -660,50 +587,56 @@ func opChainID(c *state) { return } - c.push1().SetUint64(uint64(c.host.GetTxContext().ChainID)) + x := uint256.NewInt(uint64(c.host.GetTxContext().ChainID)) + + c.push(*x) } func opOrigin(c *state) { - c.push1().SetBytes(c.host.GetTxContext().Origin.Bytes()) + x := uint256.Int{0} + x.SetBytes(c.host.GetTxContext().Origin.Bytes()) + + c.push(x) } func opCaller(c *state) { - c.push1().SetBytes(c.msg.Caller.Bytes()) + x := uint256.Int{0} + x.SetBytes(c.msg.Caller.Bytes()) + + c.push(x) } func opCallValue(c *state) { - v := c.push1() if value := c.msg.Value; value != nil { - v.Set(value) + uintValue, invalidMessageValue := uint256.FromBig(value) + if invalidMessageValue { + c.exit(errInvalidMessageValue) + } + + c.push(*uintValue) } else { - v.Set(zero) + c.push(uint256.Int{0}) } } -func min(i, j uint64) uint64 { - if i < j { - return i - } - - return j -} - func opCallDataLoad(c *state) { offset := c.top() bufPtr := bufPool.Get().(*[]byte) buf := *bufPtr - c.setBytes(buf[:32], c.msg.Input, 32, offset) + c.setBytes(buf[:32], c.msg.Input, 32, *offset) offset.SetBytes(buf[:32]) bufPool.Put(bufPtr) } func opCallDataSize(c *state) { - c.push1().SetUint64(uint64(len(c.msg.Input))) + x := uint256.NewInt(uint64(len(c.msg.Input))) + c.push(*x) } func opCodeSize(c *state) { - c.push1().SetUint64(uint64(len(c.code))) + x := uint256.NewInt(uint64(len(c.code))) + c.push(*x) } func opExtCodeSize(c *state) { @@ -722,18 +655,22 @@ func opExtCodeSize(c *state) { return } - c.push1().SetUint64(uint64(c.host.GetCodeSize(addr))) + x := uint256.NewInt(uint64(c.host.GetCodeSize(addr))) + c.push(*x) } func opGasPrice(c *state) { - c.push1().SetBytes(c.host.GetTxContext().GasPrice.Bytes()) + x := uint256.Int{0} + x.SetBytes(c.host.GetTxContext().GasPrice.Bytes()) + c.push(x) } func opReturnDataSize(c *state) { if !c.config.Byzantium { c.exit(errOpCodeNotFound) } else { - c.push1().SetUint64(uint64(len(c.returnData))) + x := uint256.NewInt(uint64(len(c.returnData))) + c.push(*x) } } @@ -759,27 +696,27 @@ func opExtCodeHash(c *state) { return } - v := c.push1() - if c.host.Empty(address) { - v.Set(zero) - } else { + v := uint256.Int{0} + if !c.host.Empty(address) { v.SetBytes(c.host.GetCodeHash(address).Bytes()) } + + c.push(v) } func opPC(c *state) { - c.push1().SetUint64(uint64(c.ip)) + c.push(*uint256.NewInt(uint64(c.ip))) } func opMSize(c *state) { - c.push1().SetUint64(uint64(len(c.memory))) + c.push(*uint256.NewInt(uint64(len(c.memory)))) } func opGas(c *state) { - c.push1().SetUint64(c.gas) + c.push(*uint256.NewInt(c.gas)) } -func (c *state) setBytes(dst, input []byte, size uint64, dataOffset *big.Int) { +func (c *state) setBytes(dst, input []byte, size uint64, dataOffset uint256.Int) { if !dataOffset.IsUint64() { // overflow, copy 'size' 0 bytes to dst for i := uint64(0); i < size; i++ { @@ -877,7 +814,8 @@ func opReturnDataCopy(c *state) { // 1. the dataOffset is uint64 (overflow check) // 2. the sum of dataOffset and length overflows uint64 // 3. the length of return data has enough space to receive offset + length bytes - end := new(big.Int).Add(dataOffset, length) + end := dataOffset.Clone() + end.Add(end, &length) endAddress := end.Uint64() if !dataOffset.IsUint64() || @@ -936,40 +874,47 @@ func opCodeCopy(c *state) { func opBlockHash(c *state) { num := c.top() - if !num.IsInt64() { - num.Set(zero) + num64, overflow := num.Uint64WithOverflow() + + if overflow { + num.SetUint64(0) return } - n := num.Int64() + n := int64(num64) lastBlock := c.host.GetTxContext().Number if lastBlock-257 < n && n < lastBlock { num.SetBytes(c.host.GetBlockHash(n).Bytes()) } else { - num.Set(zero) + num.SetUint64(0) } } func opCoinbase(c *state) { - c.push1().SetBytes(c.host.GetTxContext().Coinbase.Bytes()) + v := new(uint256.Int).SetBytes20(c.host.GetTxContext().Coinbase.Bytes()) + c.push(*v) } func opTimestamp(c *state) { - c.push1().SetInt64(c.host.GetTxContext().Timestamp) + v := new(uint256.Int).SetUint64(uint64(c.host.GetTxContext().Timestamp)) + c.push(*v) } func opNumber(c *state) { - c.push1().SetInt64(c.host.GetTxContext().Number) + v := new(uint256.Int).SetUint64((uint64)(c.host.GetTxContext().Number)) + c.push(*v) } func opDifficulty(c *state) { - c.push1().SetBytes(c.host.GetTxContext().Difficulty.Bytes()) + v := new(uint256.Int).SetBytes(c.host.GetTxContext().Difficulty.Bytes()) + c.push(*v) } func opGasLimit(c *state) { - c.push1().SetInt64(c.host.GetTxContext().GasLimit) + v := new(uint256.Int).SetUint64((uint64)(c.host.GetTxContext().GasLimit)) + c.push(*v) } func opBaseFee(c *state) { @@ -979,7 +924,7 @@ func opBaseFee(c *state) { return } - c.push(c.host.GetTxContext().BaseFee) + c.push(*uint256.MustFromBig(c.host.GetTxContext().BaseFee)) } func opSelfDestruct(c *state) { @@ -1024,7 +969,8 @@ func opSelfDestruct(c *state) { } func opJump(c *state) { - if dest := c.pop(); c.validJumpdest(dest) { + dest := c.pop() + if c.validJumpdest(dest) { c.ip = int(dest.Uint64() - 1) } else { c.exit(errInvalidJump) @@ -1054,7 +1000,7 @@ func opPush0(c *state) { return } - c.push(zero) + c.push(uint256.Int{0}) } func opPush(n int) instruction { @@ -1062,13 +1008,15 @@ func opPush(n int) instruction { ins := c.code ip := c.ip - v := c.push1() + d := uint256.Int{0} if ip+1+n > len(ins) { - v.SetBytes(append(ins[ip+1:], make([]byte, n)...)) + d.SetBytes(append(ins[ip+1:], make([]byte, n)...)) } else { - v.SetBytes(ins[ip+1 : ip+1+n]) + d.SetBytes(ins[ip+1 : ip+1+n]) } + c.push(d) + c.ip += n } } @@ -1076,10 +1024,10 @@ func opPush(n int) instruction { func opDup(n int) instruction { return func(c *state) { if !c.stackAtLeast(n) { - c.exit(&runtime.StackUnderflowError{StackLen: c.sp, Required: n}) + c.exit(&runtime.StackUnderflowError{StackLen: c.stack.sp, Required: n}) } else { val := c.peekAt(n) - c.push1().Set(val) + c.push(val) } } } @@ -1087,7 +1035,7 @@ func opDup(n int) instruction { func opSwap(n int) instruction { return func(c *state) { if !c.stackAtLeast(n + 1) { - c.exit(&runtime.StackUnderflowError{StackLen: c.sp, Required: n + 1}) + c.exit(&runtime.StackUnderflowError{StackLen: c.stack.sp, Required: n + 1}) } else { c.swap(n) } @@ -1105,7 +1053,7 @@ func opLog(size int) instruction { } if !c.stackAtLeast(2 + size) { - c.exit(&runtime.StackUnderflowError{StackLen: c.sp, Required: 2 + size}) + c.exit(&runtime.StackUnderflowError{StackLen: c.stack.sp, Required: 2 + size}) return } @@ -1115,7 +1063,8 @@ func opLog(size int) instruction { topics := make([]types.Hash, size) for i := 0; i < size; i++ { - topics[i] = bigToHash(c.pop()) + v := c.pop() + topics[i] = uint256ToHash(&v) } var ok bool @@ -1162,7 +1111,7 @@ func opCreate(op OpCode) instruction { contract, err := c.buildCreateContract(op) if err != nil { - c.push1().Set(zero) + c.push(uint256.Int{0}) if contract != nil { c.gas += contract.Gas @@ -1180,17 +1129,18 @@ func opCreate(op OpCode) instruction { // Correct call result := c.host.Callx(contract, c.host) - v := c.push1() + v := uint256.Int{0} if op == CREATE && c.config.Homestead && errors.Is(result.Err, runtime.ErrCodeStoreOutOfGas) { - v.Set(zero) + v.SetUint64(0) } else if op == CREATE && result.Failed() && !errors.Is(result.Err, runtime.ErrCodeStoreOutOfGas) { - v.Set(zero) + v.SetUint64(0) } else if op == CREATE2 && result.Failed() { - v.Set(zero) + v.SetUint64(0) } else { v.SetBytes(contract.Address.Bytes()) } + c.push(v) c.gas += result.GasLeft if result.Reverted() { @@ -1204,7 +1154,7 @@ func opCall(op OpCode) instruction { c.resetReturnData() if op == CALL && c.inStaticCall() { - if val := c.peekAt(3); val != nil && val.BitLen() > 0 { + if val := c.peekAt(3); val.BitLen() > 0 { c.exit(errWriteProtection) return @@ -1244,7 +1194,7 @@ func opCall(op OpCode) instruction { contract, offset, size, err := c.buildCallContract(op) if err != nil { - c.push1().Set(zero) + c.push(uint256.Int{0}) if contract != nil { c.gas += contract.Gas @@ -1261,11 +1211,10 @@ func opCall(op OpCode) instruction { result := c.host.Callx(contract, c.host) - v := c.push1() if result.Succeeded() { - v.Set(one) + c.push(*uint256.NewInt(1)) } else { - v.Set(zero) + c.push(uint256.Int{0}) } if result.Succeeded() || result.Reverted() { @@ -1284,7 +1233,7 @@ func (c *state) buildCallContract(op OpCode) (*runtime.Contract, uint64, uint64, initialGas := c.pop() addr, _ := c.popAddr() - var value *big.Int + var value uint256.Int if op == CALL || op == CALLCODE { value = c.pop() } @@ -1316,7 +1265,7 @@ func (c *state) buildCallContract(op OpCode) (*runtime.Contract, uint64, uint64, gasCost = 40 } - transfersValue := (op == CALL || op == CALLCODE) && value != nil && value.Sign() != 0 + transfersValue := (op == CALL || op == CALLCODE) && value.Sign() != 0 if op == CALL { if c.config.EIP158 { @@ -1351,7 +1300,6 @@ func (c *state) buildCallContract(op OpCode) (*runtime.Contract, uint64, uint64, return nil, 0, 0, nil } - gas = initialGas.Uint64() } @@ -1380,7 +1328,7 @@ func (c *state) buildCallContract(op OpCode) (*runtime.Contract, uint64, uint64, parent.msg.Origin, parent.msg.Address, addr, - value, + value.ToBig(), gas, c.host.GetCode(addr), args, @@ -1399,7 +1347,7 @@ func (c *state) buildCallContract(op OpCode) (*runtime.Contract, uint64, uint64, } if transfersValue { - if c.host.GetBalance(c.msg.Address).Cmp(value) < 0 { + if c.host.GetBalance(c.msg.Address).Cmp(value.ToBig()) < 0 { return contract, 0, 0, types.ErrInsufficientFunds } } @@ -1413,13 +1361,13 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { offset := c.pop() length := c.pop() - var salt *big.Int + var salt uint256.Int if op == CREATE2 { salt = c.pop() } // check if the value can be transferred - hasTransfer := value != nil && value.Sign() != 0 + hasTransfer := value.Sign() != 0 // Calculate and consume gas cost @@ -1442,7 +1390,7 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { } if hasTransfer { - if c.host.GetBalance(c.msg.Address).Cmp(value) < 0 { + if c.host.GetBalance(c.msg.Address).Cmp(value.ToBig()) < 0 { return nil, types.ErrInsufficientFunds } } @@ -1464,7 +1412,7 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { if op == CREATE { address = crypto.CreateAddress(c.msg.Address, c.host.GetNonce(c.msg.Address)) } else { - address = crypto.CreateAddress2(c.msg.Address, bigToHash(salt), input) + address = crypto.CreateAddress2(c.msg.Address, uint256ToHash(&salt), input) } contract := runtime.NewContractCreation( @@ -1472,7 +1420,7 @@ func (c *state) buildCreateContract(op OpCode) (*runtime.Contract, error) { c.msg.Origin, c.msg.Address, address, - value, + value.ToBig(), gas, input, ) @@ -1505,24 +1453,3 @@ func opHalt(op OpCode) instruction { } } } - -var ( - tt256 = new(big.Int).Lsh(big.NewInt(1), 256) // 2 ** 256 - tt256m1 = new(big.Int).Sub(tt256, big.NewInt(1)) // 2 ** 256 - 1 -) - -func toU256(x *big.Int) *big.Int { - if x.Sign() < 0 || x.BitLen() > 256 { - x.And(x, tt256m1) - } - - return x -} - -func to256(x *big.Int) *big.Int { - if x.BitLen() > 255 { - x.Sub(x, tt256) - } - - return x -} diff --git a/state/runtime/evm/instructions_benchmark_test.go b/state/runtime/evm/instructions_benchmark_test.go new file mode 100644 index 0000000000..d0bbf29fbb --- /dev/null +++ b/state/runtime/evm/instructions_benchmark_test.go @@ -0,0 +1,281 @@ +package evm + +import ( + "math/big" + "testing" + + "github.com/0xPolygon/polygon-edge/chain" + "github.com/holiman/uint256" +) + +type ( + instructionOperation func(c *state) +) + +func BenchmarkStack(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + op1 := uint256.NewInt(1) + op2 := uint256.NewInt(2) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + s.push(*op1) + s.push(*op2) + s.pop() + s.pop() + } + + b.StopTimer() +} + +func operationBenchmark(b *testing.B, s *state, op instructionOperation, operands ...uint256.Int) { + b.Helper() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, op := range operands { + s.push(op) + } + + op(s) + s.pop() + } + + b.StopTimer() +} + +func BenchmarkInstruction_opAdd(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opAdd, *op1, *op2) +} + +func BenchmarkInstruction_opMul(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opMul, *op1, *op2) +} + +func BenchmarkInstruction_opSub(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opSub, *op1, *op2) +} + +func BenchmarkInstruction_opDiv(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opDiv, *op1, *op2) +} + +func BenchmarkInstruction_opSDiv(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opSDiv, *op1, *op2) +} + +func BenchmarkInstruction_opMod(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opMod, *op1, *op2) +} + +func BenchmarkInstruction_opSMod(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opSMod, *op1, *op2) +} + +func BenchmarkInstruction_opExp(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opExp, *op1, *op2) +} + +func BenchmarkInstruction_opAnd(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opAnd, *op1, *op2) +} + +func BenchmarkInstruction_opOr(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opOr, *op1, *op2) +} + +func BenchmarkInstruction_opXor(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opXor, *op1, *op2) +} + +func BenchmarkInstruction_opByte(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opByte, *op1, *op2) +} + +func BenchmarkInstruction_opEq(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opEq, *op1, *op2) +} + +func BenchmarkInstruction_opLt(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opLt, *op1, *op2) +} + +func BenchmarkInstruction_opGt(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opGt, *op1, *op2) +} + +func BenchmarkInstruction_opSlt(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opSlt, *op1, *op2) +} + +func BenchmarkInstruction_opSgt(b *testing.B) { + s, closeFn := getState(&chain.ForksInTime{}) + defer closeFn() + + s.gas = 9223372036854775807 + op1 := uint256.NewInt(9223372036854775807) + op2 := uint256.NewInt(9223372036854775807) + + operationBenchmark(b, s, opSgt, *op1, *op2) +} + +func getLarge256bitUint() uint256.Int { + hexStr := "0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F" + + bigInt := new(big.Int) + bigInt.SetString(hexStr, 16) + + return *uint256.MustFromBig(bigInt) +} + +func BenchmarkUint256WriteToSlice(b *testing.B) { + value := getLarge256bitUint() + + var destination [32]byte + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + value.WriteToSlice(destination[:]) + } +} + +func BenchmarkStaticUnrolledWriteToSlice(b *testing.B) { + value := getLarge256bitUint() + + var destination [32]byte + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + WriteToSlice32(value, destination[:]) + } +} + +func BenchmarkGenericStaticUnrolledWriteToSlice(b *testing.B) { + value := getLarge256bitUint() + + var destination [32]byte + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + WriteToSlice(value, destination[:]) + } +} diff --git a/state/runtime/evm/instructions_test.go b/state/runtime/evm/instructions_test.go index 74fa195204..d58b548d69 100644 --- a/state/runtime/evm/instructions_test.go +++ b/state/runtime/evm/instructions_test.go @@ -10,12 +10,15 @@ import ( "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" + "github.com/holiman/uint256" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) var ( + zero = big.NewInt(0) + one = big.NewInt(1) two = big.NewInt(2) three = big.NewInt(3) four = big.NewInt(4) @@ -24,24 +27,35 @@ var ( allEnabledForks = chain.AllForksEnabled.At(0) ) +var ( + zero256 = *uint256.NewInt(0) + one256 = *uint256.NewInt(1) +) + type OperandsLogical struct { operands []*big.Int expectedResult bool } +func bigToHash(b *big.Int) types.Hash { + return types.BytesToHash(b.Bytes()) +} + func testLogicalOperation(t *testing.T, f instruction, test OperandsLogical, s *state) { t.Helper() for _, operand := range test.operands { - s.push(operand) + op, _ := uint256.FromBig(operand) + s.push(*op) } f(s) + r := s.pop() if test.expectedResult { - assert.Equal(t, one.Uint64(), s.pop().Uint64()) + assert.Equal(t, one.Uint64(), r.Uint64()) } else { - assert.Equal(t, zero.Uint64(), s.pop().Uint64()) + assert.Equal(t, zero.Uint64(), r.Uint64()) } } @@ -54,12 +68,14 @@ func testArithmeticOperation(t *testing.T, f instruction, test OperandsArithmeti t.Helper() for _, operand := range test.operands { - s.push(operand) + op, _ := uint256.FromBig(operand) + s.push(*op) } f(s) - assert.Equal(t, test.expectedResult.Uint64(), s.pop().Uint64()) + r := s.pop() + assert.Equal(t, test.expectedResult.Uint64(), r.Uint64()) } func TestAdd(t *testing.T) { @@ -357,7 +373,8 @@ func TestPush0(t *testing.T) { defer closeFn() opPush0(s) - require.Equal(t, zero.Uint64(), s.pop().Uint64()) + v := s.pop() + require.Equal(t, zero.Uint64(), v.Uint64()) }) t.Run("single push0 (EIP-3855 disabled)", func(t *testing.T) { @@ -380,7 +397,8 @@ func TestPush0(t *testing.T) { } for i := 0; i < stackSize; i++ { - require.Equal(t, zero.Uint64(), s.pop().Uint64()) + v := s.pop() + require.Equal(t, zero.Uint64(), v.Uint64()) } }) } @@ -503,6 +521,9 @@ func TestNot(t *testing.T) { s, closeFn := getState(&chain.ForksInTime{}) defer closeFn() + tt256 := new(big.Int).Lsh(big.NewInt(1), 256) // 2 ** 256 + tt256m1 := new(big.Int).Sub(tt256, big.NewInt(1)) // 2 ** 256 - 1 + testOperands := []OperandsArithmetic{ {[]*big.Int{big.NewInt(-1)}, zero}, {[]*big.Int{zero}, tt256m1}, @@ -531,40 +552,42 @@ func TestIsZero(t *testing.T) { } func TestMStore(t *testing.T) { - offset := big.NewInt(62) + offset := uint256.NewInt(62) s, closeFn := getState(&chain.ForksInTime{}) defer closeFn() - s.push(one) // value - s.push(offset) // offset + s.push(one256) // value + s.push(*offset) // offset opMStore(s) - s.push(offset) + s.push(*offset) opMLoad(s) - assert.Equal(t, one, s.pop()) + v := s.pop() + assert.Equal(t, one, v.ToBig()) } func TestMStore8(t *testing.T) { - offsetStore := big.NewInt(62) - offsetLoad := big.NewInt(31) + offsetStore := uint256.NewInt(62) + offsetLoad := uint256.NewInt(31) s, closeFn := getState(&chain.ForksInTime{}) defer closeFn() - s.push(one) //value - s.push(offsetStore) //offset + s.push(one256) // value + s.push(*offsetStore) // offset opMStore8(s) - s.push(offsetLoad) + s.push(*offsetLoad) opMLoad(s) - assert.Equal(t, one, s.pop()) + v := s.pop() + assert.Equal(t, one, v.ToBig()) } func TestSload(t *testing.T) { @@ -576,11 +599,12 @@ func TestSload(t *testing.T) { mockHost.On("GetStorage", mock.Anything, mock.Anything).Return(bigToHash(one)).Once() s.host = mockHost - s.push(one) + s.push(one256) opSload(s) assert.Equal(t, uint64(200), s.gas) - assert.Equal(t, bigToHash(one), bigToHash(s.pop())) + v := s.pop() + assert.Equal(t, bigToHash(one), bigToHash(v.ToBig())) }) t.Run("EIP150", func(t *testing.T) { @@ -591,11 +615,12 @@ func TestSload(t *testing.T) { mockHost.On("GetStorage", mock.Anything, mock.Anything).Return(bigToHash(one)).Once() s.host = mockHost - s.push(one) + s.push(one256) opSload(s) assert.Equal(t, uint64(800), s.gas) - assert.Equal(t, bigToHash(one), bigToHash(s.pop())) + v := s.pop() + assert.Equal(t, bigToHash(one), bigToHash(v.ToBig())) }) t.Run("NoForks", func(t *testing.T) { @@ -606,11 +631,12 @@ func TestSload(t *testing.T) { mockHost.On("GetStorage", mock.Anything, mock.Anything).Return(bigToHash(one)).Once() s.host = mockHost - s.push(one) + s.push(one256) opSload(s) assert.Equal(t, uint64(950), s.gas) - assert.Equal(t, bigToHash(one), bigToHash(s.pop())) + v := s.pop() + assert.Equal(t, bigToHash(one), bigToHash(v.ToBig())) }) } @@ -621,7 +647,7 @@ func TestSStore(t *testing.T) { }) defer closeFn() - s.push(one) + s.push(one256) opSStore(s) assert.True(t, s.stop) @@ -642,8 +668,8 @@ func TestSStore(t *testing.T) { s.host = mockHost - s.push(one) - s.push(zero) + s.push(one256) + s.push(zero256) opSStore(s) assert.Equal(t, uint64(9200), s.gas) @@ -663,8 +689,8 @@ func TestSStore(t *testing.T) { s.host = mockHost - s.push(one) - s.push(zero) + s.push(one256) + s.push(zero256) opSStore(s) assert.Equal(t, uint64(5000), s.gas) @@ -681,8 +707,8 @@ func TestSStore(t *testing.T) { s.host = mockHost - s.push(one) - s.push(zero) + s.push(one256) + s.push(zero256) opSStore(s) assert.Equal(t, uint64(5000), s.gas) @@ -702,8 +728,8 @@ func TestSStore(t *testing.T) { s.host = mockHost - s.push(one) - s.push(zero) + s.push(one256) + s.push(zero256) opSStore(s) assert.Equal(t, uint64(5000), s.gas) @@ -730,7 +756,8 @@ func TestBalance(t *testing.T) { opBalance(s) - assert.Equal(t, balance, s.pop()) + v := s.pop() + assert.Equal(t, balance, v.ToBig()) assert.Equal(t, gasLeft, s.gas) }) @@ -744,7 +771,8 @@ func TestBalance(t *testing.T) { opBalance(s) - assert.Equal(t, big.NewInt(100), s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(100), v.ToBig()) assert.Equal(t, gasLeft, s.gas) }) @@ -758,7 +786,8 @@ func TestBalance(t *testing.T) { opBalance(s) - assert.Equal(t, balance, s.pop()) + v := s.pop() + assert.Equal(t, balance, v.ToBig()) assert.Equal(t, gasLeft, s.gas) }) } @@ -776,7 +805,8 @@ func TestSelfBalance(t *testing.T) { opSelfBalance(s) - assert.Equal(t, big.NewInt(100), s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(100), v.ToBig()) }) t.Run("NoForkErrorExpected", func(t *testing.T) { @@ -807,7 +837,8 @@ func TestChainID(t *testing.T) { opChainID(s) - assert.Equal(t, big.NewInt(chainID), s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(chainID), v.ToBig()) }) t.Run("NoForksErrorExpected", func(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) @@ -863,7 +894,8 @@ func TestCallValue(t *testing.T) { s.msg.Value = value opCallValue(s) - assert.Equal(t, value.Uint64(), s.pop().Uint64()) + v := s.pop() + assert.Equal(t, value.Uint64(), v.Uint64()) }) t.Run("Msg Value nil", func(t *testing.T) { @@ -871,7 +903,8 @@ func TestCallValue(t *testing.T) { defer cancelFn() opCallValue(s) - assert.Equal(t, zero.Uint64(), s.pop().Uint64()) + v := s.pop() + assert.Equal(t, zero.Uint64(), v.Uint64()) }) } @@ -879,12 +912,13 @@ func TestCallDataLoad(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) defer cancelFn() - s.push(one) + s.push(one256) s.msg = &runtime.Contract{Input: big.NewInt(7).Bytes()} opCallDataLoad(s) - assert.Equal(t, zero.Uint64(), s.pop().Uint64()) + v := s.pop() + assert.Equal(t, zero.Uint64(), v.Uint64()) } func TestCallDataSize(t *testing.T) { @@ -894,7 +928,8 @@ func TestCallDataSize(t *testing.T) { s.msg.Input = make([]byte, 10) opCallDataSize(s) - assert.Equal(t, big.NewInt(10), s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(10), v.ToBig()) } func TestCodeSize(t *testing.T) { @@ -904,7 +939,8 @@ func TestCodeSize(t *testing.T) { s.code = make([]byte, 10) opCodeSize(s) - assert.Equal(t, big.NewInt(10), s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(10), v.ToBig()) } func TestExtCodeSize(t *testing.T) { @@ -915,7 +951,7 @@ func TestExtCodeSize(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{EIP150: true}) defer cancelFn() - s.push(one) + s.push(one256) mockHost := &mockHost{} mockHost.On("GetCodeSize", types.StringToAddress("0x1")).Return(codeSize).Once() @@ -924,7 +960,8 @@ func TestExtCodeSize(t *testing.T) { opExtCodeSize(s) assert.Equal(t, gasLeft, s.gas) - assert.Equal(t, big.NewInt(int64(codeSize)), s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(int64(codeSize)), v.ToBig()) }) t.Run("NoForks", func(t *testing.T) { gasLeft := uint64(980) @@ -932,7 +969,7 @@ func TestExtCodeSize(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) defer cancelFn() - s.push(one) + s.push(one256) mockHost := &mockHost{} mockHost.On("GetCodeSize", types.StringToAddress("0x1")).Return(codeSize).Once() @@ -941,7 +978,8 @@ func TestExtCodeSize(t *testing.T) { opExtCodeSize(s) assert.Equal(t, gasLeft, s.gas) - assert.Equal(t, big.NewInt(int64(codeSize)), s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(int64(codeSize)), v.ToBig()) }) } @@ -971,7 +1009,8 @@ func TestReturnDataSize(t *testing.T) { opReturnDataSize(s) - assert.Equal(t, big.NewInt(dataSize), s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(dataSize), v.ToBig()) }) t.Run("NoForks", func(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) @@ -996,7 +1035,7 @@ func TestExtCodeHash(t *testing.T) { }) defer cancelFn() - s.push(one) + s.push(one256) mockHost := &mockHost{} mockHost.On("Empty", types.StringToAddress("0x1")).Return(false).Once() @@ -1006,7 +1045,8 @@ func TestExtCodeHash(t *testing.T) { opExtCodeHash(s) assert.Equal(t, s.gas, gasLeft) - assert.Equal(t, one.Uint64(), s.pop().Uint64()) + v := s.pop() + assert.Equal(t, one.Uint64(), v.Uint64()) }) t.Run("NonIstanbul", func(t *testing.T) { @@ -1017,7 +1057,7 @@ func TestExtCodeHash(t *testing.T) { }) defer cancelFn() - s.push(one) + s.push(one256) mockHost := &mockHost{} mockHost.On("Empty", mock.Anything).Return(true).Once() @@ -1025,14 +1065,15 @@ func TestExtCodeHash(t *testing.T) { opExtCodeHash(s) assert.Equal(t, gasLeft, s.gas) - assert.Equal(t, zero.Uint64(), s.pop().Uint64()) + v := s.pop() + assert.Equal(t, zero.Uint64(), v.Uint64()) }) t.Run("NoForks", func(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) defer cancelFn() - s.push(one) + s.push(one256) mockHost := &mockHost{} mockHost.On("Empty", mock.Anything).Return(true).Once() @@ -1055,7 +1096,8 @@ func TestPCMSizeGas(t *testing.T) { s.ip = 1 opPC(s) - assert.Equal(t, one, s.pop()) + v := s.pop() + assert.Equal(t, one, v.ToBig()) }) t.Run("MSize", func(t *testing.T) { @@ -1063,13 +1105,15 @@ func TestPCMSizeGas(t *testing.T) { opMSize(s) - assert.Equal(t, new(big.Int).SetUint64(memorySize), s.pop()) + v := s.pop() + assert.Equal(t, new(big.Int).SetUint64(memorySize), v.ToBig()) }) t.Run("Gas", func(t *testing.T) { opGas(s) - assert.Equal(t, new(big.Int).SetUint64(gasLeft), s.pop()) + v := s.pop() + assert.Equal(t, new(big.Int).SetUint64(gasLeft), v.ToBig()) }) } @@ -1084,10 +1128,10 @@ func TestExtCodeCopy(t *testing.T) { mockHost.On("GetCode", mock.Anything).Return("0x1").Once() s.host = mockHost - s.push(one) - s.push(zero) - s.push(big.NewInt(31)) - s.push(big.NewInt(32)) + s.push(one256) + s.push(zero256) + s.push(*uint256.NewInt(31)) + s.push(*uint256.NewInt(32)) opExtCodeCopy(s) @@ -1105,10 +1149,10 @@ func TestExtCodeCopy(t *testing.T) { mockHost.On("GetCode", mock.Anything).Return("0x1").Once() s.host = mockHost - s.push(one) - s.push(zero) - s.push(big.NewInt(31)) - s.push(big.NewInt(32)) + s.push(one256) + s.push(zero256) + s.push(*uint256.NewInt(31)) + s.push(*uint256.NewInt(32)) opExtCodeCopy(s) @@ -1125,9 +1169,9 @@ func TestCallDataCopy(t *testing.T) { s.msg.Input = one.Bytes() - s.push(big.NewInt(1)) - s.push(zero) - s.push(big.NewInt(31)) + s.push(one256) + s.push(zero256) + s.push(*uint256.NewInt(31)) opCallDataCopy(s) @@ -1141,9 +1185,9 @@ func TestCodeCopyLenZero(t *testing.T) { var expectedGas = s.gas - s.push(big.NewInt(0)) //length - s.push(big.NewInt(0)) //dataOffset - s.push(big.NewInt(0)) //memOffset + s.push(zero256) // length + s.push(zero256) // dataOffset + s.push(zero256) // memOffset opCodeCopy(s) @@ -1156,9 +1200,9 @@ func TestCodeCopy(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) defer cancelFn() - s.push(big.NewInt(1)) //length - s.push(zero) //dataOffset - s.push(big.NewInt(31)) //memOffset + s.push(one256) // length + s.push(zero256) // dataOffset + s.push(*uint256.NewInt(31)) // memOffset s.code = one.Bytes() @@ -1170,7 +1214,7 @@ func TestBlockHash(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) defer cancelFn() - s.push(three) + s.push(*uint256.NewInt(3)) mockHost := &mockHost{} mockHost.On("GetTxContext").Return(runtime.TxContext{Number: 5}).Once() @@ -1179,7 +1223,8 @@ func TestBlockHash(t *testing.T) { opBlockHash(s) - assert.Equal(t, bigToHash(three), bigToHash(s.pop())) + v := s.pop() + assert.Equal(t, bigToHash(three), bigToHash(v.ToBig())) } func TestCoinBase(t *testing.T) { @@ -1192,7 +1237,10 @@ func TestCoinBase(t *testing.T) { opCoinbase(s) - assert.Equal(t, types.StringToAddress("0x1").Bytes(), s.pop().FillBytes(make([]byte, 20))) + v := s.pop() + b := v.ToBig().FillBytes(make([]byte, 20)) + a := types.StringToAddress("0x1").Bytes() + assert.Equal(t, a, b) } func TestTimeStamp(t *testing.T) { @@ -1205,7 +1253,8 @@ func TestTimeStamp(t *testing.T) { opTimestamp(s) - assert.Equal(t, big.NewInt(335), s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(335), v.ToBig()) } func TestNumber(t *testing.T) { @@ -1218,7 +1267,8 @@ func TestNumber(t *testing.T) { opNumber(s) - assert.Equal(t, five, s.pop()) + v := s.pop() + assert.Equal(t, five, v.ToBig()) } func TestDifficulty(t *testing.T) { @@ -1231,7 +1281,8 @@ func TestDifficulty(t *testing.T) { opDifficulty(s) - assert.Equal(t, bigToHash(five), bigToHash(s.pop())) + v := s.pop() + assert.Equal(t, bigToHash(five), bigToHash(v.ToBig())) } func TestGasLimit(t *testing.T) { @@ -1259,7 +1310,8 @@ func TestGasLimit(t *testing.T) { opBaseFee(s) - assert.Equal(t, new(big.Int).SetUint64(baseFee), s.pop()) + v := s.pop() + assert.Equal(t, new(big.Int).SetUint64(baseFee), v.ToBig()) }) } @@ -1274,7 +1326,7 @@ func TestSelfDestruct(t *testing.T) { s.msg.Address = types.StringToAddress("0x2") s.gas = 100000 - s.push(one) + s.push(one256) mockHost := &mockHost{} mockHost.On("Empty", addr).Return(true).Once() @@ -1294,7 +1346,7 @@ func TestJump(t *testing.T) { s.code = make([]byte, 10) s.bitmap = bitmap{big.NewInt(255).Bytes()} - s.push(five) + s.push(*uint256.NewInt(5)) opJump(s) @@ -1307,8 +1359,8 @@ func TestJumpI(t *testing.T) { s.code = make([]byte, 10) s.bitmap = bitmap{big.NewInt(255).Bytes()} - s.push(one) - s.push(five) + s.push(one256) + s.push(*uint256.NewInt(5)) opJumpi(s) @@ -1319,33 +1371,30 @@ func TestDup(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) defer cancelFn() - s.sp = 6 - for i := 0; i < 10; i++ { - s.stack = append(s.stack, big.NewInt(int64(i))) + s.push(*uint256.NewInt(uint64(i))) } instr := opDup(4) instr(s) - assert.Equal(t, two, s.pop()) + v := s.pop() + assert.Equal(t, big.NewInt(6), v.ToBig()) } func TestSwap(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) defer cancelFn() - s.sp = 6 - for i := 0; i < 10; i++ { - s.stack = append(s.stack, big.NewInt(int64(i))) + s.push(*uint256.NewInt(uint64(i))) } instr := opSwap(4) instr(s) - assert.Equal(t, five, s.stack[1]) - assert.Equal(t, one, s.stack[6-1]) + assert.Equal(t, *uint256.NewInt(5), s.stack.data[9]) + assert.Equal(t, *uint256.NewInt(9), s.stack.data[5]) } func TestLog(t *testing.T) { @@ -1354,13 +1403,13 @@ func TestLog(t *testing.T) { defer cancelFn() s.msg.Static = true - s.sp = 1 + s.stack.sp = 1 - s.push(big.NewInt(3)) - s.push(big.NewInt(20)) + s.push(*uint256.NewInt(3)) + s.push(*uint256.NewInt(20)) for i := 0; i < 20; i++ { - s.push(big.NewInt(int64(i))) + s.push(*uint256.NewInt(uint64(i))) } instr := opLog(10) @@ -1373,13 +1422,13 @@ func TestLog(t *testing.T) { s, cancelFn := getState(&chain.ForksInTime{}) defer cancelFn() - s.sp = 1 + s.stack.sp = 1 - s.push(big.NewInt(3)) - s.push(big.NewInt(20)) + s.push(*uint256.NewInt(3)) + s.push(*uint256.NewInt(20)) for i := 0; i < 20; i++ { - s.push(big.NewInt(int64(i))) + s.push(*uint256.NewInt(uint64(i))) } instr := opLog(35) @@ -1394,15 +1443,15 @@ func TestLog(t *testing.T) { s.gas = 25000 - s.push(big.NewInt(3)) - s.push(big.NewInt(20)) + s.push(*uint256.NewInt(3)) + s.push(*uint256.NewInt(20)) mockHost := &mockHost{} mockHost.On("EmitLog", mock.Anything, mock.Anything, mock.Anything).Once() s.host = mockHost for i := 0; i < 20; i++ { - s.push(big.NewInt(int64(i))) + s.push(*uint256.NewInt(uint64(i))) } instr := opLog(10) @@ -1451,6 +1500,25 @@ var ( addr1 = types.StringToAddress("1") ) +func convertBigIntSliceToUint256(bigInts []*big.Int) []uint256.Int { + var uint256s = make([]uint256.Int, 0, len(bigInts)) + + for _, bi := range bigInts { + if bi.Sign() < 0 { + return nil + } + + ui, overflow := uint256.FromBig(bi) + if overflow { + return nil + } + + uint256s = append(uint256s, *ui) + } + + return uint256s +} + func Test_opSload(t *testing.T) { t.Parallel() @@ -1623,15 +1691,15 @@ func Test_opSload(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - t.Parallel() + // t.Parallel() s, closeFn := getState(tt.config) defer closeFn() s.msg = tt.contract s.gas = tt.initState.gas - s.sp = tt.initState.sp - s.stack = tt.initState.stack + s.stack.sp = tt.initState.sp + s.stack.data = convertBigIntSliceToUint256(tt.initState.stack) s.memory = tt.initState.memory s.config = tt.config tt.mockHost.accessList = tt.initState.accessList @@ -1640,8 +1708,8 @@ func Test_opSload(t *testing.T) { opSload(s) assert.Equal(t, tt.resultState.gas, s.gas, "gas in state after execution is not correct") - assert.Equal(t, tt.resultState.sp, s.sp, "sp in state after execution is not correct") - assert.Equal(t, tt.resultState.stack, s.stack, "stack in state after execution is not correct") + assert.Equal(t, tt.resultState.sp, s.stack.sp, "sp in state after execution is not correct") + assert.Equal(t, convertBigIntSliceToUint256(tt.resultState.stack), s.stack.data, "stack in state after execution is not correct") assert.Equal(t, tt.resultState.memory, s.memory, "memory in state after execution is not correct") assert.Equal(t, tt.resultState.accessList, tt.mockHost.accessList, "accesslist in state after execution is not correct") assert.Equal(t, tt.resultState.stop, s.stop, "stop in state after execution is not correct") @@ -1698,8 +1766,6 @@ func TestCreate(t *testing.T) { sp: 1, stack: []*big.Int{ addressToBigInt(crypto.CreateAddress(addr1, 0)), // contract address - big.NewInt(0x00), - big.NewInt(0x00), }, memory: []byte{ byte(REVERT), @@ -1830,8 +1896,6 @@ func TestCreate(t *testing.T) { sp: 1, stack: []*big.Int{ // need to init with 0x01 to add abs field in big.Int - big.NewInt(0x01).SetInt64(0x00), - big.NewInt(0x00), big.NewInt(0x00), }, memory: []byte{ @@ -1882,8 +1946,6 @@ func TestCreate(t *testing.T) { stack: []*big.Int{ // need to init with 0x01 to add abs field in big.Int big.NewInt(0x01).SetInt64(0x00), - big.NewInt(0x00), - big.NewInt(0x00), }, memory: []byte{ byte(REVERT), @@ -1933,10 +1995,7 @@ func TestCreate(t *testing.T) { gas: 15, sp: 1, stack: []*big.Int{ - big.NewInt(0x01).SetInt64(0x00), - big.NewInt(0x01), - big.NewInt(0x00), - big.NewInt(0x00), + big.NewInt(0x0), }, memory: []byte{ byte(REVERT), @@ -1965,8 +2024,8 @@ func TestCreate(t *testing.T) { s.msg = tt.contract s.gas = tt.initState.gas - s.sp = tt.initState.sp - s.stack = tt.initState.stack + s.stack.sp = tt.initState.sp + s.stack.data = convertBigIntSliceToUint256(tt.initState.stack) s.memory = tt.initState.memory s.config = tt.config s.host = tt.mockHost @@ -1974,8 +2033,8 @@ func TestCreate(t *testing.T) { opCreate(tt.op)(s) assert.Equal(t, tt.resultState.gas, s.gas, "gas in state after execution is not correct") - assert.Equal(t, tt.resultState.sp, s.sp, "sp in state after execution is not correct") - assert.Equal(t, tt.resultState.stack, s.stack, "stack in state after execution is not correct") + assert.Equal(t, tt.resultState.sp, s.stack.sp, "sp in state after execution is not correct") + assert.Equal(t, convertBigIntSliceToUint256(tt.resultState.stack), s.stack.data, "stack in state after execution is not correct") assert.Equal(t, tt.resultState.memory, s.memory, "memory in state after execution is not correct") assert.Equal(t, tt.resultState.stop, s.stop, "stop in state after execution is not correct") assert.Equal(t, tt.resultState.err, s.err, "err in state after execution is not correct") @@ -2016,103 +2075,28 @@ func Test_opReturnDataCopy(t *testing.T) { err: errOpCodeNotFound, }, }, - { - name: "should return error if memOffset is negative", - config: &allEnabledForks, - initState: &state{ - stack: []*big.Int{ - big.NewInt(1), // length - big.NewInt(0), // dataOffset - big.NewInt(-1), // memOffset - }, - sp: 3, - returnData: []byte{0xff}, - }, - resultState: &state{ - config: &allEnabledForks, - stack: []*big.Int{ - big.NewInt(1), - big.NewInt(0), - big.NewInt(-1), - }, - sp: 0, - returnData: []byte{0xff}, - stop: true, - err: errReturnDataOutOfBounds, - }, - }, - { - name: "should return error if dataOffset is negative", - config: &allEnabledForks, - initState: &state{ - stack: []*big.Int{ - big.NewInt(1), // length - big.NewInt(-1), // dataOffset - big.NewInt(0), // memOffset - }, - sp: 3, - memory: make([]byte, 1), - }, - resultState: &state{ - config: &allEnabledForks, - stack: []*big.Int{ - big.NewInt(1), - big.NewInt(-1), - big.NewInt(0), - }, - sp: 0, - memory: make([]byte, 1), - stop: true, - err: errReturnDataOutOfBounds, - }, - }, - { - name: "should return error if length is negative", - config: &allEnabledForks, - initState: &state{ - stack: []*big.Int{ - big.NewInt(-1), // length - big.NewInt(2), // dataOffset - big.NewInt(0), // memOffset - }, - sp: 3, - returnData: []byte{0xff}, - }, - resultState: &state{ - config: &allEnabledForks, - stack: []*big.Int{ - big.NewInt(-1), - big.NewInt(2), - big.NewInt(0), - }, - sp: 0, - returnData: []byte{0xff}, - stop: true, - err: errReturnDataOutOfBounds, - }, - }, { name: "should copy data from returnData to memory", config: &allEnabledForks, initState: &state{ - stack: []*big.Int{ - big.NewInt(1), // length - big.NewInt(0), // dataOffset - big.NewInt(0), // memOffset + stack: OptimizedStack{ + data: []uint256.Int{ + one256, // length + zero256, // dataOffset + zero256, // memOffset + }, + sp: 3, }, - sp: 3, returnData: []byte{0xff}, memory: []byte{0x0}, gas: 10, }, resultState: &state{ config: &allEnabledForks, - stack: []*big.Int{ - big.NewInt(1), - big.NewInt(0), - big.NewInt(0), + stack: OptimizedStack{ + data: []uint256.Int{}, + sp: 0, }, - sp: 0, returnData: []byte{0xff}, memory: []byte{0xff}, gas: 7, @@ -2127,96 +2111,50 @@ func Test_opReturnDataCopy(t *testing.T) { name: "should not copy data if length is zero", config: &allEnabledForks, initState: &state{ - stack: []*big.Int{ - big.NewInt(0), // length - big.NewInt(0), // dataOffset - big.NewInt(4), // memOffset + stack: OptimizedStack{ + data: []uint256.Int{ + zero256, // length + zero256, // dataOffset + *uint256.NewInt(4), // memOffset + }, + sp: 3, }, - sp: 3, returnData: []byte{0x01}, memory: []byte{0x02}, }, resultState: &state{ config: &allEnabledForks, - stack: []*big.Int{ - big.NewInt(0), - big.NewInt(0), - big.NewInt(4), + stack: OptimizedStack{ + data: []uint256.Int{}, + sp: 0, }, - sp: 0, returnData: []byte{0x01}, memory: []byte{0x02}, stop: false, err: nil, }, }, - { - name: "should return error if data offset overflows uint64", - config: &allEnabledForks, - initState: &state{ - stack: []*big.Int{ - big.NewInt(1), // length - bigIntValue, // dataOffset - big.NewInt(-1), // memOffset - }, - sp: 3, - }, - resultState: &state{ - config: &allEnabledForks, - stack: []*big.Int{ - big.NewInt(1), - bigIntValue, - big.NewInt(-1), - }, - sp: 0, - stop: true, - err: errReturnDataOutOfBounds, - }, - }, - { - name: "should return error if sum of data offset and length overflows uint64", - config: &allEnabledForks, - initState: &state{ - stack: []*big.Int{ - bigIntValue2, // length - bigIntValue2, // dataOffset - big.NewInt(-1), // memOffset - }, - sp: 3, - }, - resultState: &state{ - config: &allEnabledForks, - stack: []*big.Int{ - bigIntValue2, - bigIntValue2, - big.NewInt(-1), - }, - sp: 0, - stop: true, - err: errReturnDataOutOfBounds, - }, - }, { name: "should return error if the length of return data does not have enough space to receive offset + length bytes", config: &allEnabledForks, initState: &state{ - stack: []*big.Int{ - big.NewInt(2), // length - big.NewInt(0), // dataOffset - big.NewInt(0), // memOffset + stack: OptimizedStack{ + data: []uint256.Int{ + *uint256.NewInt(2), // length + zero256, // dataOffset + zero256, // memOffset + }, + sp: 3, }, - sp: 3, returnData: []byte{0xff}, memory: []byte{0x0}, }, resultState: &state{ config: &allEnabledForks, - stack: []*big.Int{ - big.NewInt(2), - big.NewInt(0), - big.NewInt(0), + stack: OptimizedStack{ + data: []uint256.Int{}, + sp: 0, }, - sp: 0, returnData: []byte{0xff}, memory: []byte{0x0}, stop: true, @@ -2227,24 +2165,24 @@ func Test_opReturnDataCopy(t *testing.T) { name: "should return error if there is no gas", config: &allEnabledForks, initState: &state{ - stack: []*big.Int{ - big.NewInt(1), // length - big.NewInt(0), // dataOffset - big.NewInt(0), // memOffset + stack: OptimizedStack{ + data: []uint256.Int{ + one256, // length + zero256, // dataOffset + zero256, // memOffset + }, + sp: 3, }, - sp: 3, returnData: []byte{0xff}, memory: []byte{0x0}, gas: 0, }, resultState: &state{ config: &allEnabledForks, - stack: []*big.Int{ - big.NewInt(1), - big.NewInt(0), - big.NewInt(0), + stack: OptimizedStack{ + data: []uint256.Int{}, + sp: 0, }, - sp: 0, returnData: []byte{0xff}, memory: []byte{0x0}, gas: 0, @@ -2265,7 +2203,7 @@ func Test_opReturnDataCopy(t *testing.T) { defer closeFn() state.gas = test.initState.gas - state.sp = test.initState.sp + state.stack.sp = test.initState.stack.sp state.stack = test.initState.stack state.memory = test.initState.memory state.returnData = test.initState.returnData @@ -2282,39 +2220,11 @@ func Test_opReturnDataCopy(t *testing.T) { opReturnDataCopy(state) - assert.True(t, CompareStates(test.resultState, state)) + assert.True(t, compareStates(test.resultState, state)) }) } } -// Since the state is complex structure, here is the specialized comparison -// function that checks significant fields. This function should be updated -// to suite future needs. -func CompareStates(a *state, b *state) bool { - // Compare simple fields - if a.ip != b.ip || a.lastGasCost != b.lastGasCost || a.sp != b.sp || !errors.Is(a.err, b.err) || a.stop != b.stop || a.gas != b.gas { - return false - } - - // Deep compare slices - if !reflect.DeepEqual(a.code, b.code) || !reflect.DeepEqual(a.tmp, b.tmp) || !reflect.DeepEqual(a.returnData, b.returnData) || !reflect.DeepEqual(a.memory, b.memory) { - return false - } - - // Deep comparison of stacks - if len(a.stack) != len(b.stack) { - return false - } - - for i := range a.stack { - if a.stack[i].Cmp(b.stack[i]) != 0 { - return false - } - } - - return true -} - func Test_opCall(t *testing.T) { t.Parallel() @@ -2337,15 +2247,16 @@ func Test_opCall(t *testing.T) { config: allEnabledForks, initState: &state{ gas: 2600, - sp: 6, - stack: []*big.Int{ - big.NewInt(0x00), // outSize - big.NewInt(0x02), // outOffset - big.NewInt(0x00), // inSize - big.NewInt(0x00), // inOffset - big.NewInt(0x00), // address - big.NewInt(0x00), // initialGas - }, + stack: OptimizedStack{ + data: []uint256.Int{ + *uint256.NewInt(0x00), // outSize + *uint256.NewInt(0x02), // outOffset + *uint256.NewInt(0x00), // inSize + *uint256.NewInt(0x00), // inOffset + *uint256.NewInt(0x00), // address + *uint256.NewInt(0x00), // initialGas + }, + sp: 6}, memory: []byte{0x01}, }, resultState: &state{ @@ -2362,74 +2273,6 @@ func Test_opCall(t *testing.T) { }, }, }, - // { - // name: "call cost overflow (EIP150 fork disabled)", - // op: CALLCODE, - // contract: &runtime.Contract{ - // Static: false, - // }, - // config: chain.AllForksEnabled.RemoveFork(chain.EIP150).At(0), - // initState: &state{ - // gas: 6640, - // sp: 7, - // stack: []*big.Int{ - // big.NewInt(0x00), // outSize - // big.NewInt(0x00), // outOffset - // big.NewInt(0x00), // inSize - // big.NewInt(0x00), // inOffset - // big.NewInt(0x01), // value - // big.NewInt(0x03), // address - // big.NewInt(0).SetUint64(math.MaxUint64), // initialGas - // }, - // memory: []byte{0x01}, - // accessList: runtime.NewAccessList(), - // }, - // resultState: &state{ - // memory: []byte{0x01}, - // stop: true, - // err: errGasUintOverflow, - // gas: 6640, - // }, - // mockHost: &mockHostForInstructions{ - // callxResult: &runtime.ExecutionResult{ - // ReturnValue: []byte{0x03}, - // }, - // }, - // }, - // { - // name: "available gas underflow", - // op: CALLCODE, - // contract: &runtime.Contract{ - // Static: false, - // }, - // config: allEnabledForks, - // initState: &state{ - // gas: 6640, - // sp: 7, - // stack: []*big.Int{ - // big.NewInt(0x00), // outSize - // big.NewInt(0x00), // outOffset - // big.NewInt(0x00), // inSize - // big.NewInt(0x00), // inOffset - // big.NewInt(0x01), // value - // big.NewInt(0x03), // address - // big.NewInt(0).SetUint64(math.MaxUint64), // initialGas - // }, - // memory: []byte{0x01}, - // accessList: runtime.NewAccessList(), - // }, - // resultState: &state{ - // memory: []byte{0x01}, - // stop: true, - // err: errOutOfGas, - // gas: 6640, - // }, - // mockHost: &mockHostForInstructions{ - // callxResult: &runtime.ExecutionResult{ - // ReturnValue: []byte{0x03}, - // }, - // }, - // }, } for _, tt := range tests { @@ -2442,7 +2285,7 @@ func Test_opCall(t *testing.T) { state.gas = test.initState.gas state.msg = test.contract - state.sp = test.initState.sp + state.stack.sp = test.initState.stack.sp state.stack = test.initState.stack state.memory = test.initState.memory state.config = &test.config @@ -2457,3 +2300,55 @@ func Test_opCall(t *testing.T) { }) } } + +func TestGenericWriteToSlice32(t *testing.T) { + expectedDestinationSlice := [32]uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} + + var destination [32]byte + + value := getLarge256bitUint() + + WriteToSlice32(value, destination[:]) + + assert.Equal(t, expectedDestinationSlice, destination) +} + +func TestGenericWriteToSlice(t *testing.T) { + expectedDestinationSlice := [32]uint8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} + + var destination [32]byte + + value := getLarge256bitUint() + + WriteToSlice(value, destination[:]) + + assert.Equal(t, expectedDestinationSlice, destination) +} + +// Since the state is complex structure, here is the specialized comparison +// function that checks significant fields. This function should be updated +// to suite future needs. +func compareStates(a *state, b *state) bool { + // Compare simple fields + if a.ip != b.ip || a.lastGasCost != b.lastGasCost || a.stack.sp != b.stack.sp || !errors.Is(a.err, b.err) || a.stop != b.stop || a.gas != b.gas { + return false + } + + // Deep compare slices + if !reflect.DeepEqual(a.code, b.code) || !reflect.DeepEqual(a.tmp, b.tmp) || !reflect.DeepEqual(a.returnData, b.returnData) || !reflect.DeepEqual(a.memory, b.memory) { + return false + } + + // Deep comparison of stacks + if len(a.stack.data) != len(b.stack.data) { + return false + } + + for i := range a.stack.data { + if a.stack.data[i] != b.stack.data[i] { + return false + } + } + + return true +} diff --git a/state/runtime/evm/optimized_stack.go b/state/runtime/evm/optimized_stack.go new file mode 100644 index 0000000000..1a41badf75 --- /dev/null +++ b/state/runtime/evm/optimized_stack.go @@ -0,0 +1,80 @@ +package evm + +import ( + "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/holiman/uint256" +) + +// OptimizedStack represents a stack data structure for uint256 integers. +// It utilizes a dynamic array (slice) to store the data and an integer (sp) +// to keep track of the current stack pointer. +// OptimizedStack uint256 integers for improved operations on values on the +// stack and minimizes heap allocations. +type OptimizedStack struct { + sp int // Stack pointer to track the top of the stack + data []uint256.Int // Slice to store the stack's elements +} + +// NewOptimizedStack creates a new instance of OptimizedStack with an initialized +// internal stack slice to avoid unnecessary reallocations. +func NewOptimizedStack(capacity int32) *OptimizedStack { + return &OptimizedStack{ + sp: 0, + data: make([]uint256.Int, 0, capacity), // Initialize the slice with provided capacity + } +} + +// reset clears the stack by resetting the stack pointer to 0 and truncating +// the data slice to zero length. +func (s *OptimizedStack) reset() { + s.sp = 0 + s.data = s.data[:0] // Efficiently clears the slice without allocating new memory +} + +// push adds a new element of type uint256.Int to the top of the stack. +// It appends the element to the data slice and increments the stack pointer. +func (s *OptimizedStack) push(val uint256.Int) { + s.data = append(s.data, val) + s.sp++ +} + +// pop removes and returns the top element of the stack. +// If the stack is empty, it returns a zero value of uint256.Int and an error. +func (s *OptimizedStack) pop() (uint256.Int, error) { + if s.sp == 0 { + // The stack is empty, return a zero value and an underflow error + return uint256.Int{0}, &runtime.StackUnderflowError{} + } + + o := s.data[s.sp-1] // Get the top element + s.sp-- // Decrement the stack pointer + s.data = s.data[:s.sp] // Truncate the slice to remove the top element + + return o, nil +} + +// top returns the top element of the stack without removing it. If the stack +// is empty, it returns nil and an error. +func (s *OptimizedStack) top() (*uint256.Int, error) { + if s.sp == 0 { + // The stack is empty, return nil and an underflow error + return nil, &runtime.StackUnderflowError{} + } + + topIndex := len(s.data) - 1 // Calculate the index of the top element + + return &s.data[topIndex], nil // Return a pointer to the top element +} + +// peekAt returns the element at the nth position from the top of the stack, +// without modifying the stack. It does not perform bounds checking and it +// returns the value of the element, not the reference. +func (s *OptimizedStack) peekAt(n int) uint256.Int { + return s.data[s.sp-n] +} + +// swap exchanges the top element of the stack with the element at the n-th position +// from the top. It does not perform bounds checking and assumes valid input. +func (s *OptimizedStack) swap(n int) { + s.data[s.sp-1], s.data[s.sp-n-1] = s.data[s.sp-n-1], s.data[s.sp-1] +} diff --git a/state/runtime/evm/optimized_stack_benchmark_test.go b/state/runtime/evm/optimized_stack_benchmark_test.go new file mode 100644 index 0000000000..a05b6a1b89 --- /dev/null +++ b/state/runtime/evm/optimized_stack_benchmark_test.go @@ -0,0 +1,84 @@ +package evm + +import ( + "testing" + + "github.com/holiman/uint256" +) + +func BenchmarkOptimizedStack_Push(b *testing.B) { + stack := NewOptimizedStack(int32(b.N)) + val := uint256.NewInt(42) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + stack.push(*val) + } +} + +func BenchmarkOptimizedStack_PushPop(b *testing.B) { + stackSize := 10 + stack := NewOptimizedStack(int32(stackSize)) + val := uint256.NewInt(42) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for j := 0; j < stackSize; j++ { + stack.push(*val) + } + + for j := 0; j < stackSize; j++ { + _, _ = stack.pop() + } + } +} + +func BenchmarkOptimizedStack_Top(b *testing.B) { + stackSize := 100 + stack := NewOptimizedStack(int32(stackSize)) + val := uint256.NewInt(42) + + for i := 0; i < stackSize; i++ { + stack.push(*val) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, _ = stack.top() + } +} + +func BenchmarkOptimizedStack_PeekAt(b *testing.B) { + stackSize := 100 + stack := NewOptimizedStack(int32(stackSize)) + val := uint256.NewInt(42) + + for i := 0; i < stackSize; i++ { + stack.push(*val) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = stack.peekAt(50) + } +} + +func BenchmarkOptimizedStack_Swap(b *testing.B) { + stackSize := 100 + stack := NewOptimizedStack(int32(stackSize)) + val := uint256.NewInt(42) + + for i := 0; i < stackSize; i++ { + stack.push(*val) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + stack.swap(50) + } +} diff --git a/state/runtime/evm/optimized_stack_test.go b/state/runtime/evm/optimized_stack_test.go new file mode 100644 index 0000000000..1c47d03fed --- /dev/null +++ b/state/runtime/evm/optimized_stack_test.go @@ -0,0 +1,102 @@ +package evm + +import ( + "testing" + + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +// TestPushPop tests the push and pop operations of the stack. +func TestOptimizedStackPushPop(t *testing.T) { + var stack OptimizedStack + + value := uint256.NewInt(10) + + stack.push(*value) + + if stack.sp != 1 { + t.Errorf("Expected stack pointer to be 1, got %d", stack.sp) + } + + poppedValue, err := stack.pop() + + require.NoError(t, err) + + require.Equal(t, poppedValue, *value) + + require.Zero(t, stack.sp, "Expected stack pointer to be 0 after pop.") +} + +// TestUnderflow tests the underflow condition when popping from an empty stack. +func TestOptimizedStackUnderflow(t *testing.T) { + var stack OptimizedStack + + _, err := stack.pop() + + require.Error(t, err, "Expected an underflow error when popping from an empty stack, got nil") +} + +// TestTop tests the top function without modifying the stack. +func TestOptimizedStackTop(t *testing.T) { + var stack OptimizedStack + + value := uint256.NewInt(10) + + stack.push(*value) + + topValue, err := stack.top() + + require.NoError(t, err) + + require.Equal(t, *topValue, *value) + + require.Equal(t, stack.sp, 1, "Expected stack pointer to remain 1 after top.") +} + +// TestReset tests the reset function to ensure it clears the stack. +func TestOptimizedStackReset(t *testing.T) { + var stack OptimizedStack + + stack.push(uint256.Int{0}) + stack.reset() + + require.Zero(t, stack.sp, "Expected stack to be empty after reset") + require.Zero(t, len(stack.data), "Expected stack to be empty after reset") +} + +// TestPeekAt tests the peekAt function for retrieving elements without modifying the stack. +func TestOptimizedStackPeekAt(t *testing.T) { + var stack OptimizedStack + + value1 := uint256.NewInt(1) + value2 := uint256.NewInt(2) + + stack.push(*value1) + stack.push(*value2) + + peekedValue := stack.peekAt(2) + + require.Equal(t, peekedValue, *value1) + + require.Equal(t, stack.sp, 2) +} + +// TestSwap tests the swap function to ensure it correctly swaps elements in the stack. +func TestOptimizedStackSwap(t *testing.T) { + var stack OptimizedStack + + value1 := uint256.NewInt(1) + value2 := uint256.NewInt(2) + + // Push two distinct values onto the stack + stack.push(*value1) + stack.push(*value2) + + // Swap the top two elements + stack.swap(1) + + // Verify swap operation + require.Equal(t, stack.data[stack.sp-1], *value1) + require.Equal(t, stack.data[stack.sp-2], *value2) +} diff --git a/state/runtime/evm/state.go b/state/runtime/evm/state.go index 30011e40ae..08c546cfec 100644 --- a/state/runtime/evm/state.go +++ b/state/runtime/evm/state.go @@ -2,7 +2,6 @@ package evm import ( "errors" - "math/big" "strings" "sync" @@ -12,6 +11,7 @@ import ( "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" + "github.com/holiman/uint256" ) var statePool = sync.Pool{ @@ -44,10 +44,10 @@ var ( errInvalidJump = errors.New("invalid jump destination") errOpCodeNotFound = errors.New("opcode not found") errReturnDataOutOfBounds = errors.New("return data out of bounds") + errInvalidBalanceValue = errors.New("invalid balance value") + errInvalidMessageValue = errors.New("invalid message value") ) -// Instructions is the code of instructions - type state struct { ip int code []byte @@ -62,8 +62,7 @@ type state struct { lastGasCost uint64 // stack - stack []*big.Int - sp int + stack OptimizedStack err error stop bool @@ -76,12 +75,9 @@ type state struct { returnData []byte ret []byte - - unsafepool common.UnsafePool[*big.Int] } func (c *state) reset() { - c.sp = 0 c.ip = 0 c.gas = 0 c.currentConsumedGas = 0 @@ -97,15 +93,7 @@ func (c *state) reset() { c.memory[i] = 0 } - // Before stack cleanup, return instances of big.Int to the pool - // for the future usage - for i := range c.stack { - c.unsafepool.Put(func(x *big.Int) *big.Int { - return x.SetInt64(0) - }, c.stack[i]) - } - - c.stack = c.stack[:0] + c.stack.reset() c.tmp = c.tmp[:0] c.ret = c.ret[:0] c.code = c.code[:0] @@ -113,9 +101,10 @@ func (c *state) reset() { c.memory = c.memory[:0] } -func (c *state) validJumpdest(dest *big.Int) bool { - udest := dest.Uint64() - if dest.BitLen() >= 63 || udest >= uint64(len(c.code)) { +func (c *state) validJumpdest(dest uint256.Int) bool { + udest, overflow := dest.Uint64WithOverflow() + + if overflow || udest >= uint64(len(c.code)) { return false } @@ -135,38 +124,23 @@ func (c *state) exit(err error) { c.err = err } -func (c *state) push(val *big.Int) { - c.push1().Set(val) -} - -func (c *state) push1() *big.Int { - if len(c.stack) > c.sp { - c.sp++ - - return c.stack[c.sp-1] - } - - v := c.unsafepool.Get(func() *big.Int { - return big.NewInt(0) - }) - - c.stack = append(c.stack, v) - c.sp++ - - return v +func (c *state) push(val uint256.Int) { + c.stack.push(val) } func (c *state) stackAtLeast(n int) bool { - return c.sp >= n + return c.stack.sp >= n } func (c *state) popHash() types.Hash { - return types.BytesToHash(c.pop().Bytes()) + v := c.pop() + + return types.BytesToHash(v.Bytes()) } func (c *state) popAddr() (types.Address, bool) { - b := c.pop() - if b == nil { + b, err := c.stack.pop() + if err != nil { return types.Address{}, false } @@ -174,34 +148,27 @@ func (c *state) popAddr() (types.Address, bool) { } func (c *state) stackSize() int { - return c.sp + return c.stack.sp } -func (c *state) top() *big.Int { - if c.sp == 0 { - return nil - } +func (c *state) top() *uint256.Int { + v, _ := c.stack.top() - return c.stack[c.sp-1] + return v } -func (c *state) pop() *big.Int { - if c.sp == 0 { - return nil - } - - o := c.stack[c.sp-1] - c.sp-- +func (c *state) pop() uint256.Int { + v, _ := c.stack.pop() - return o + return v } -func (c *state) peekAt(n int) *big.Int { - return c.stack[c.sp-n] +func (c *state) peekAt(n int) uint256.Int { + return c.stack.peekAt(n) } func (c *state) swap(n int) { - c.stack[c.sp-1], c.stack[c.sp-n-1] = c.stack[c.sp-n-1], c.stack[c.sp-1] + c.stack.swap(n) } func (c *state) consumeGas(gas uint64) bool { @@ -231,11 +198,15 @@ func (c *state) Run() ([]byte, error) { ok bool ) + tracer := c.host.GetTracer() + for !c.stop { op, ok = c.CurrentOpCode() gasCopy, ipCopy := c.gas, uint64(c.ip) - c.captureState(int(op)) + if tracer != nil { + c.captureState(int(op)) + } if !ok { c.Halt() @@ -252,8 +223,8 @@ func (c *state) Run() ([]byte, error) { } // check if the depth of the stack is enough for the instruction - if c.sp < inst.stack { - c.exit(&runtime.StackUnderflowError{StackLen: c.sp, Required: inst.stack}) + if c.stack.sp < inst.stack { + c.exit(&runtime.StackUnderflowError{StackLen: c.stack.sp, Required: inst.stack}) c.captureExecution(op.String(), uint64(c.ip), gasCopy, inst.gas) break @@ -269,12 +240,12 @@ func (c *state) Run() ([]byte, error) { // execute the instruction inst.inst(c) - if c.host.GetTracer() != nil { + if tracer != nil { c.captureExecution(op.String(), ipCopy, gasCopy, gasCopy-c.gas) } // check if stack size exceeds the max size - if c.sp > stackSize { - c.exit(&runtime.StackOverflowError{StackLen: c.sp, Limit: stackSize}) + if c.stack.sp > stackSize { + c.exit(&runtime.StackOverflowError{StackLen: c.stack.sp, Limit: stackSize}) break } @@ -293,7 +264,7 @@ func (c *state) inStaticCall() bool { return c.msg.Static } -func bigToHash(b *big.Int) types.Hash { +func uint256ToHash(b *uint256.Int) types.Hash { return types.BytesToHash(b.Bytes()) } @@ -304,7 +275,7 @@ func (c *state) Len() int { // allocateMemory allocates memory to enable accessing in the range of [offset, offset+size] // throws error if the given offset and size are negative // consumes gas if memory needs to be expanded -func (c *state) allocateMemory(offset, size *big.Int) bool { +func (c *state) allocateMemory(offset, size uint256.Int) bool { if !offset.IsUint64() || !size.IsUint64() { c.exit(errReturnDataOutOfBounds) @@ -341,7 +312,7 @@ func (c *state) allocateMemory(offset, size *big.Int) bool { return true } -func (c *state) get2(dst []byte, offset, length *big.Int) ([]byte, bool) { +func (c *state) get2(dst []byte, offset, length uint256.Int) ([]byte, bool) { if length.Sign() == 0 { return nil, true } @@ -389,10 +360,10 @@ func (c *state) captureState(opCode int) { tracer.CaptureState( c.memory, - c.stack, + c.stack.data, opCode, c.msg.Address, - c.sp, + c.stack.sp, c.host, c, ) diff --git a/state/runtime/evm/state_test.go b/state/runtime/evm/state_test.go index 5a5892238d..320b34d5fc 100644 --- a/state/runtime/evm/state_test.go +++ b/state/runtime/evm/state_test.go @@ -5,6 +5,7 @@ import ( "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/state/runtime" + "github.com/holiman/uint256" "github.com/stretchr/testify/assert" ) @@ -47,10 +48,10 @@ func TestStackTop(t *testing.T) { s, closeFn := getState(&chain.ForksInTime{}) defer closeFn() - s.push(one) - s.push(two) + s.push(*uint256.NewInt(1)) + s.push(*uint256.NewInt(2)) - assert.Equal(t, two, s.top()) + assert.Equal(t, *uint256.NewInt(2), *s.top()) assert.Equal(t, s.stackSize(), 2) } diff --git a/state/runtime/runtime.go b/state/runtime/runtime.go index 809b9564d9..54fe71e7e8 100644 --- a/state/runtime/runtime.go +++ b/state/runtime/runtime.go @@ -8,6 +8,7 @@ import ( "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/state/runtime/tracer" "github.com/0xPolygon/polygon-edge/types" + "github.com/holiman/uint256" ) // TxContext is the context of the transaction @@ -91,7 +92,7 @@ type Host interface { type VMTracer interface { CaptureState( memory []byte, - stack []*big.Int, + stack []uint256.Int, opCode int, contractAddress types.Address, sp int, diff --git a/state/runtime/tracer/calltracer/call_tracer.go b/state/runtime/tracer/calltracer/call_tracer.go index 23e6e1adc9..f1dd6a5cc9 100644 --- a/state/runtime/tracer/calltracer/call_tracer.go +++ b/state/runtime/tracer/calltracer/call_tracer.go @@ -7,6 +7,7 @@ import ( "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/state/runtime/tracer" "github.com/0xPolygon/polygon-edge/types" + "github.com/holiman/uint256" ) var ( @@ -143,7 +144,7 @@ func (c *CallTracer) CallEnd(depth int, output []byte, err error) { } } -func (c *CallTracer) CaptureState(memory []byte, stack []*big.Int, opCode int, +func (c *CallTracer) CaptureState(memory []byte, stack []uint256.Int, opCode int, contractAddress types.Address, sp int, host tracer.RuntimeHost, state tracer.VMState) { if c.cancelled() { state.Halt() diff --git a/state/runtime/tracer/structtracer/tracer.go b/state/runtime/tracer/structtracer/tracer.go index 259eaf5567..9f24e96c0e 100644 --- a/state/runtime/tracer/structtracer/tracer.go +++ b/state/runtime/tracer/structtracer/tracer.go @@ -11,6 +11,7 @@ import ( "github.com/0xPolygon/polygon-edge/state/runtime/evm" "github.com/0xPolygon/polygon-edge/state/runtime/tracer" "github.com/0xPolygon/polygon-edge/types" + "github.com/holiman/uint256" ) type Config struct { @@ -50,7 +51,7 @@ type StructTracer struct { storage []map[types.Address]map[types.Hash]types.Hash currentMemory [][]byte - currentStack [][]*big.Int + currentStack [][]uint256.Int } func NewStructTracer(config Config) *StructTracer { @@ -61,7 +62,7 @@ func NewStructTracer(config Config) *StructTracer { {}, }, currentMemory: make([][]byte, 1), - currentStack: make([][]*big.Int, 1), + currentStack: make([][]uint256.Int, 1), } } @@ -95,7 +96,7 @@ func (t *StructTracer) Clear() { {}, } t.currentMemory = make([][]byte, 1) - t.currentStack = make([][]*big.Int, 1) + t.currentStack = make([][]uint256.Int, 1) } func (t *StructTracer) TxStart(gasLimit uint64) { @@ -129,7 +130,7 @@ func (t *StructTracer) CallEnd( func (t *StructTracer) CaptureState( memory []byte, - stack []*big.Int, + stack []uint256.Int, opCode int, contractAddress types.Address, sp int, @@ -172,8 +173,20 @@ func (t *StructTracer) captureMemory( } } +func copyUint256Array(original []uint256.Int) []uint256.Int { + // Create a new array with the same length as the original + copied := make([]uint256.Int, len(original)) + + // Copy each element individually + for i := range original { + copied[i].Set(&original[i]) + } + + return copied +} + func (t *StructTracer) captureStack( - stack []*big.Int, + stack []uint256.Int, sp int, opCode int, ) { @@ -181,13 +194,7 @@ func (t *StructTracer) captureStack( return } - currentStack := make([]*big.Int, sp) - - for i, v := range stack[:sp] { - currentStack[i] = new(big.Int).Set(v) - } - - t.currentStack[len(t.currentStack)-1] = currentStack + t.currentStack[len(t.currentStack)-1] = copyUint256Array(stack) if opCode == evm.CALL || opCode == evm.STATICCALL { t.currentStack = append(t.currentStack, nil) @@ -195,7 +202,7 @@ func (t *StructTracer) captureStack( } func (t *StructTracer) captureStorage( - stack []*big.Int, + stack []uint256.Int, opCode int, contractAddress types.Address, sp int, @@ -280,7 +287,7 @@ func (t *StructTracer) ExecuteState( stack = make([]string, len(currStack)) for i, v := range currStack { - stack[i] = hex.EncodeBig(v) + stack[i] = v.Hex() } } diff --git a/state/runtime/tracer/structtracer/tracer_test.go b/state/runtime/tracer/structtracer/tracer_test.go index 07b1d2ad5f..a0bdeaab7e 100644 --- a/state/runtime/tracer/structtracer/tracer_test.go +++ b/state/runtime/tracer/structtracer/tracer_test.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + "github.com/holiman/uint256" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -140,9 +141,9 @@ func TestStructTracerClear(t *testing.T) { }, }, currentMemory: [][]byte{[]byte("memory example")}, - currentStack: []([]*big.Int){[]*big.Int{ - new(big.Int).SetUint64(1), - new(big.Int).SetUint64(2), + currentStack: []([]uint256.Int){[]uint256.Int{ + *uint256.NewInt(1), + *uint256.NewInt(2), }}, } @@ -169,7 +170,7 @@ func TestStructTracerClear(t *testing.T) { make(map[types.Address]map[types.Hash]types.Hash), }, currentMemory: make([]([]byte), 1), - currentStack: make([]([]*big.Int), 1), + currentStack: make([]([]uint256.Int), 1), }, tracer, ) @@ -195,7 +196,7 @@ func TestStructTracerTxStart(t *testing.T) { }, gasLimit: gasLimit, currentMemory: make([]([]byte), 1), - currentStack: make([]([]*big.Int), 1), + currentStack: make([]([]uint256.Int), 1), }, tracer, ) @@ -224,7 +225,7 @@ func TestStructTracerTxEnd(t *testing.T) { gasLimit: gasLimit, consumedGas: gasLimit - gasLeft, currentMemory: make([]([]byte), 1), - currentStack: make([]([]*big.Int), 1), + currentStack: make([]([]uint256.Int), 1), }, tracer, ) @@ -281,7 +282,7 @@ func TestStructTracerCallEnd(t *testing.T) { output: output, err: err, currentMemory: make([]([]byte), 1), - currentStack: make([]([]*big.Int), 1), + currentStack: make([]([]uint256.Int), 1), }, }, { @@ -295,7 +296,7 @@ func TestStructTracerCallEnd(t *testing.T) { make(map[types.Address]map[types.Hash]types.Hash), }, currentMemory: make([]([]byte), 1), - currentStack: make([]([]*big.Int), 1), + currentStack: make([]([]uint256.Int), 1), }, }, } @@ -324,9 +325,9 @@ func TestStructTracerCaptureState(t *testing.T) { var ( memory = [][]byte{[]byte("memory")} - stack = []([]*big.Int){[]*big.Int{ - big.NewInt(1), /* value */ - big.NewInt(2), /* key */ + stack = []([]uint256.Int){[]uint256.Int{ + *uint256.NewInt(1), /* value */ + *uint256.NewInt(2), /* key */ }} contractAddress = types.StringToAddress("3") storageValue = types.StringToHash("4") @@ -340,7 +341,7 @@ func TestStructTracerCaptureState(t *testing.T) { // input memory [][]byte - stack [][]*big.Int + stack [][]uint256.Int opCode int contractAddress types.Address sp int @@ -385,7 +386,7 @@ func TestStructTracerCaptureState(t *testing.T) { EnableStack: true, EnableStructLogs: true, }, - currentStack: make([]([]*big.Int), 1), + currentStack: make([]([]uint256.Int), 1), }, memory: memory, stack: stack, @@ -754,9 +755,9 @@ func TestStructTracerExecuteState(t *testing.T) { EnableStack: true, EnableStructLogs: true, }, - currentStack: [][]*big.Int{{ - big.NewInt(1), - big.NewInt(2), + currentStack: [][]uint256.Int{{ + *uint256.NewInt(1), + *uint256.NewInt(2), }}, }, contractAddress: contractAddress, diff --git a/state/runtime/tracer/types.go b/state/runtime/tracer/types.go index 902d8d8156..5625770f1e 100644 --- a/state/runtime/tracer/types.go +++ b/state/runtime/tracer/types.go @@ -4,6 +4,7 @@ import ( "math/big" "github.com/0xPolygon/polygon-edge/types" + "github.com/holiman/uint256" ) // RuntimeHost is the interface defining the methods for accessing state by tracer @@ -49,7 +50,7 @@ type Tracer interface { // Op-level CaptureState( memory []byte, - stack []*big.Int, + stack []uint256.Int, opCode int, contractAddress types.Address, sp int,