From 542698e13adca6c40853522af457a70fa1c396f1 Mon Sep 17 00:00:00 2001 From: ninjaahhh Date: Fri, 18 Feb 2022 16:08:29 +0800 Subject: [PATCH 1/5] Reuse code len when read contract stake --- core/vm/evm.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 8af230160af5..f377060082a0 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -285,12 +285,13 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + code := evm.StateDB.GetCode(addrCopy) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas if err == nil { - err = evm.checkContractStaking(contract, 0) + err = evm.checkContractStaking(contract, uint64(len(code))) } } if err != nil { @@ -329,12 +330,13 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by addrCopy := addr // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + code := evm.StateDB.GetCode(addrCopy) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas if err == nil { - err = evm.checkContractStaking(contract, 0) + err = evm.checkContractStaking(contract, uint64(len(code))) } } if err != nil { @@ -403,9 +405,6 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } func (evm *EVM) checkContractStaking(contract *Contract, codeSize uint64) error { - if codeSize == 0 { - codeSize = uint64(evm.StateDB.GetCodeSize(contract.Address())) - } // Check if the remaining balance of the contract can cover the staking requirement if !evm.StateDB.HasSuicided(contract.Address()) && codeSize > params.MaxCodeSizeSoft { staking := big.NewInt(int64((codeSize - 1) / params.ExtcodeCopyChunkSize)) From 944c50994040911d05f19f0870b6f71c39c3b7dd Mon Sep 17 00:00:00 2001 From: ninjaahhh Date: Fri, 18 Feb 2022 17:38:33 +0800 Subject: [PATCH 2/5] w3ip-02 test: evm contract code staking --- core/vm/evm_test.go | 132 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 core/vm/evm_test.go diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go new file mode 100644 index 000000000000..bfb74a65f044 --- /dev/null +++ b/core/vm/evm_test.go @@ -0,0 +1,132 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package vm + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/params" +) + +var contractCheckStakingTests = []struct { + codeSize uint + staked int64 + failure error +}{ + {params.MaxCodeSizeSoft, 0, nil}, // no need to stake + {params.MaxCodeSizeSoft + 1, 0, ErrCodeInsufficientStake}, //reading code size > threshold, need to stake + {params.MaxCodeSizeSoft + 1, int64(params.CodeStakingPerChunk - 1), ErrCodeInsufficientStake}, // not enough staking + {params.MaxCodeSizeSoft + 1, int64(params.CodeStakingPerChunk), nil}, // barely enough staking + {params.MaxCodeSizeSoft * 2, int64(params.CodeStakingPerChunk), nil}, + {params.MaxCodeSizeSoft*2 + 1, int64(params.CodeStakingPerChunk*2 - 1), ErrCodeInsufficientStake}, + {params.MaxCodeSizeSoft*2 + 1, int64(params.CodeStakingPerChunk * 2), nil}, +} + +func TestContractCheckStakingW3IP002(t *testing.T) { + contract := common.BytesToAddress([]byte("contract")) + emptyAcc := AccountRef(common.Address{}) + calls := []string{"call", "callCode", "delegateCall"} + for _, callMethod := range calls { + + for i, tt := range contractCheckStakingTests { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.CreateAccount(contract) + statedb.SetCode(contract, codegenWithSize(tt.codeSize)) + + vmctx := BlockContext{ + BlockNumber: big.NewInt(0), + CanTransfer: func(_ StateDB, _ common.Address, toAmount *big.Int) bool { + return big.NewInt(tt.staked).Cmp(toAmount) >= 0 + }, + Transfer: func(StateDB, common.Address, common.Address, *big.Int) {}, + } + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + + var err error + if callMethod == "call" { + _, _, err = vmenv.Call(emptyAcc, contract, nil, math.MaxUint64, new(big.Int)) + } else if callMethod == "callCode" { + _, _, err = vmenv.CallCode(emptyAcc, contract, nil, math.MaxUint64, new(big.Int)) + } else if callMethod == "delegateCall" { + _, _, err = vmenv.DelegateCall(NewContract(emptyAcc, emptyAcc, big.NewInt(0), 0), contract, nil, math.MaxUint64) + } else { + panic("invalid call method") + } + + if err != tt.failure { + t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) + } + } + } +} + +var createTests = []struct { + pushByte byte + codeSizeHex string + staked int64 + usedGas uint64 + failure error +}{ + {byte(PUSH2), "0x6000", 0, 4918662, nil}, // no need to stake + {byte(PUSH2), "0x6001", 0, math.MaxUint64, ErrCodeInsufficientStake}, // code size > soft limit, have to stake + {byte(PUSH2), "0x6001", int64(params.CodeStakingPerChunk), 4918668, nil}, // staked + {byte(PUSH2), "0xc000", int64(params.CodeStakingPerChunk), 4924422, nil}, // size = soft limit * 2, creation gas capped + {byte(PUSH2), "0xc001", int64(params.CodeStakingPerChunk), math.MaxUint64, ErrCodeInsufficientStake}, + {byte(PUSH2), "0xc001", int64(params.CodeStakingPerChunk*2 - 1), math.MaxUint64, ErrCodeInsufficientStake}, + {byte(PUSH2), "0xc001", int64(params.CodeStakingPerChunk * 2), 4924431, nil}, +} + +func TestCreateW3IP002(t *testing.T) { + addr := common.BytesToAddress([]byte("caller")) + for i, tt := range createTests { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + + // PUSHx , PUSH1 00, RETURN + // to manipulate how much data should be stored as code + code := []byte{tt.pushByte} + code = append(code, hexutil.MustDecode(tt.codeSizeHex)...) + code = append(code, hexutil.MustDecode("0x6000f3")...) // PUSH1 00, RETURN + + vmctx := BlockContext{ + BlockNumber: big.NewInt(0), + CanTransfer: func(_ StateDB, _ common.Address, toAmount *big.Int) bool { + return big.NewInt(tt.staked).Cmp(toAmount) >= 0 + }, + Transfer: func(StateDB, common.Address, common.Address, *big.Int) {}, + } + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + + _, _, leftOverGas, err := vmenv.Create( + AccountRef(addr), + code, + math.MaxUint64, + big.NewInt(0), + ) + if err != tt.failure { + t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) + } + if used := math.MaxUint64 - leftOverGas; used != tt.usedGas { + t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.usedGas) + } + } +} From c1d40c24249c5487e80bdeec2894aa527a068ce3 Mon Sep 17 00:00:00 2001 From: ninjaahhh Date: Fri, 18 Feb 2022 17:43:21 +0800 Subject: [PATCH 3/5] minor --- core/vm/evm_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index bfb74a65f044..081d232141f0 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -47,7 +47,6 @@ func TestContractCheckStakingW3IP002(t *testing.T) { emptyAcc := AccountRef(common.Address{}) calls := []string{"call", "callCode", "delegateCall"} for _, callMethod := range calls { - for i, tt := range contractCheckStakingTests { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.CreateAccount(contract) @@ -87,6 +86,7 @@ var createTests = []struct { usedGas uint64 failure error }{ + {byte(PUSH1), "0xff", 0, 51030, nil}, // no need to stake {byte(PUSH2), "0x6000", 0, 4918662, nil}, // no need to stake {byte(PUSH2), "0x6001", 0, math.MaxUint64, ErrCodeInsufficientStake}, // code size > soft limit, have to stake {byte(PUSH2), "0x6001", int64(params.CodeStakingPerChunk), 4918668, nil}, // staked From 1209a0185daa1b8898677834558b32ef19bfc138 Mon Sep 17 00:00:00 2001 From: ninjaahhh Date: Mon, 21 Feb 2022 15:18:42 +0800 Subject: [PATCH 4/5] Revert "Reuse code len when read contract stake" This reverts commit 542698e13adca6c40853522af457a70fa1c396f1. --- core/vm/evm.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index f377060082a0..8af230160af5 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -285,13 +285,12 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) - code := evm.StateDB.GetCode(addrCopy) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas if err == nil { - err = evm.checkContractStaking(contract, uint64(len(code))) + err = evm.checkContractStaking(contract, 0) } } if err != nil { @@ -330,13 +329,12 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by addrCopy := addr // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() - code := evm.StateDB.GetCode(addrCopy) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas if err == nil { - err = evm.checkContractStaking(contract, uint64(len(code))) + err = evm.checkContractStaking(contract, 0) } } if err != nil { @@ -405,6 +403,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } func (evm *EVM) checkContractStaking(contract *Contract, codeSize uint64) error { + if codeSize == 0 { + codeSize = uint64(evm.StateDB.GetCodeSize(contract.Address())) + } // Check if the remaining balance of the contract can cover the staking requirement if !evm.StateDB.HasSuicided(contract.Address()) && codeSize > params.MaxCodeSizeSoft { staking := big.NewInt(int64((codeSize - 1) / params.ExtcodeCopyChunkSize)) From ff41c3a50b3b5cb9e2c1520b10ac64df089b8526 Mon Sep 17 00:00:00 2001 From: ninjaahhh Date: Mon, 21 Feb 2022 15:22:35 +0800 Subject: [PATCH 5/5] Fix tests --- core/vm/evm_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index 081d232141f0..54e4c112a51f 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -43,14 +43,13 @@ var contractCheckStakingTests = []struct { } func TestContractCheckStakingW3IP002(t *testing.T) { - contract := common.BytesToAddress([]byte("contract")) - emptyAcc := AccountRef(common.Address{}) + caddr := common.BytesToAddress([]byte("contract")) calls := []string{"call", "callCode", "delegateCall"} for _, callMethod := range calls { for i, tt := range contractCheckStakingTests { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - statedb.CreateAccount(contract) - statedb.SetCode(contract, codegenWithSize(tt.codeSize)) + statedb.CreateAccount(caddr) + statedb.SetCode(caddr, codegenWithSize(tt.codeSize)) vmctx := BlockContext{ BlockNumber: big.NewInt(0), @@ -61,13 +60,14 @@ func TestContractCheckStakingW3IP002(t *testing.T) { } vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + caller := AccountRef(caddr) var err error if callMethod == "call" { - _, _, err = vmenv.Call(emptyAcc, contract, nil, math.MaxUint64, new(big.Int)) + _, _, err = vmenv.Call(AccountRef(common.Address{}), caddr, nil, math.MaxUint64, new(big.Int)) } else if callMethod == "callCode" { - _, _, err = vmenv.CallCode(emptyAcc, contract, nil, math.MaxUint64, new(big.Int)) + _, _, err = vmenv.CallCode(caller, caddr, nil, math.MaxUint64, new(big.Int)) } else if callMethod == "delegateCall" { - _, _, err = vmenv.DelegateCall(NewContract(emptyAcc, emptyAcc, big.NewInt(0), 0), contract, nil, math.MaxUint64) + _, _, err = vmenv.DelegateCall(NewContract(caller, caller, big.NewInt(0), 0), caddr, nil, math.MaxUint64) } else { panic("invalid call method") }