From 6c39787e0a7175becbb820930657d82880121734 Mon Sep 17 00:00:00 2001 From: ninjaahhh <38544039+ninjaahhh@users.noreply.github.com> Date: Sat, 26 Feb 2022 10:11:02 +0800 Subject: [PATCH] w3ip-02 test: withdraw code staking & suicide (#24) * w3ip-02 test: withdraw code staking * check suicide post state * minor * add compilation info --- core/vm/evm_test.go | 128 +++++++++++++++++++++++++++++++++++++- core/vm/gas_table_test.go | 14 +++-- 2 files changed, 135 insertions(+), 7 deletions(-) diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index 54e4c112a51f..025918b331ce 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -17,6 +17,7 @@ package vm import ( + "fmt" "math/big" "testing" @@ -49,7 +50,7 @@ func TestContractCheckStakingW3IP002(t *testing.T) { for i, tt := range contractCheckStakingTests { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.CreateAccount(caddr) - statedb.SetCode(caddr, codegenWithSize(tt.codeSize)) + statedb.SetCode(caddr, codegenWithSize(nil, tt.codeSize)) vmctx := BlockContext{ BlockNumber: big.NewInt(0), @@ -130,3 +131,128 @@ func TestCreateW3IP002(t *testing.T) { } } } + +func canTransfer(db StateDB, addr common.Address, amount *big.Int) bool { + return db.GetBalance(addr).Cmp(amount) >= 0 +} + +func transfer(db StateDB, sender, recipient common.Address, amount *big.Int) { + db.SubBalance(sender, amount) + db.AddBalance(recipient, amount) +} + +var withdrawStakingTests = []struct { + codeSize uint + staked int64 + toWithdraw int64 + failure error +}{ + {params.MaxCodeSizeSoft, 123, 123, nil}, // can withdraw all + {params.MaxCodeSizeSoft, 123, 124, ErrExecutionReverted}, // withdraw more than balance + {params.MaxCodeSizeSoft + 1, 123, 0, ErrCodeInsufficientStake}, // can't withdraw because staking is required + {params.MaxCodeSizeSoft + 1, 123, 1, ErrCodeInsufficientStake}, // can't withdraw because staking is required + {params.MaxCodeSizeSoft + 1, int64(params.CodeStakingPerChunk), 1, ErrCodeInsufficientStake}, + {params.MaxCodeSizeSoft + 1, int64(params.CodeStakingPerChunk) + 5, 5, nil}, // can withdraw extra +} + +func TestWithdrawStakingW3IP002(t *testing.T) { + addr := common.BytesToAddress([]byte("addr")) + calls := []string{"call", "callCode", "delegateCall"} + // compiler: 0.8.7, no optimization + // contract Contract { + // function withdraw(uint256 amount, address payable to) external payable { + // to.transfer(amount); + // } + // } + initCode := hexutil.MustDecode("0x60806040526004361061001d5760003560e01c8062f714ce14610022575b600080fd5b61003c60048036038101906100379190610122565b61003e565b005b8073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050158015610084573d6000803e3d6000fd5b505050565b600080fd5b6000819050919050565b6100a18161008e565b81146100ac57600080fd5b50565b6000813590506100be81610098565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100ef826100c4565b9050919050565b6100ff816100e4565b811461010a57600080fd5b50565b60008135905061011c816100f6565b92915050565b6000806040838503121561013957610138610089565b5b6000610147858286016100af565b92505060206101588582860161010d565b915050925092905056fea264697066735822122087ac4dd4a397d6abb35fd02d3945c392429ab58f1bcf76e724e6e8534373e84d64736f6c634300080c0033") + for _, callMethod := range calls { + for i, tt := range withdrawStakingTests { + code := codegenWithSize(initCode, tt.codeSize) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.CreateAccount(addr) + statedb.SetCode(addr, code) + statedb.SetBalance(addr, big.NewInt(tt.staked)) + + vmctx := BlockContext{BlockNumber: big.NewInt(0), CanTransfer: canTransfer, Transfer: transfer} + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + // func selector + uint256 amount + to address 0xffff + funcCall := hexutil.MustDecode(fmt.Sprintf("0x00f714ce%064x%064x", tt.toWithdraw, 0xffff)) + + var err error + // withdraw + if callMethod == "call" { + _, _, err = vmenv.Call(AccountRef(common.Address{}), addr, funcCall, math.MaxUint64, new(big.Int)) + } else if callMethod == "callCode" { // can't withdraw stakes under `addr` + _, _, err = vmenv.CallCode(AccountRef(addr), addr, funcCall, math.MaxUint64, new(big.Int)) + } else if callMethod == "delegateCall" { // can't withdraw stakes under `addr` + caller := NewContract(AccountRef(addr), AccountRef(addr), big.NewInt(0), 0) + _, _, err = vmenv.DelegateCall(caller, addr, funcCall, math.MaxUint64) + } else { + panic("unrecognized call method") + } + if err != tt.failure { + t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) + } + } + } +} + +var selfDestructTests = []struct { + codeSize uint + staked int64 + failure error +}{ + {params.MaxCodeSizeSoft, 123, nil}, // happy path + {params.MaxCodeSizeSoft + 1, 123, nil}, // can transfer staked funds out + {params.MaxCodeSizeSoft + 1, int64(params.CodeStakingPerChunk), nil}, // can transfer staked funds out +} + +func TestSelfDestructW3IP002(t *testing.T) { + addr := common.BytesToAddress([]byte("addr")) + calls := []string{"call", "callCode", "delegateCall"} + // compiler: 0.8.7, no optimization + // contract Contract { + // function die(address payable to) external { + // selfdestruct(to); + // } + // } + initCode := hexutil.MustDecode("0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063c9353cb514602d575b600080fd5b60436004803603810190603f919060ba565b6045565b005b8073ffffffffffffffffffffffffffffffffffffffff16ff5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000608c826063565b9050919050565b609a816083565b811460a457600080fd5b50565b60008135905060b4816093565b92915050565b60006020828403121560cd5760cc605e565b5b600060d98482850160a7565b9150509291505056fea2646970667358221220880ff39475cde4d997f052a7d0fb15be29d0a473fb55594f46e1a63c847f87f964736f6c634300080c0033") + for _, callMethod := range calls { + for i, tt := range selfDestructTests { + code := codegenWithSize(initCode, tt.codeSize) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + statedb.CreateAccount(addr) + statedb.SetCode(addr, code) + statedb.SetBalance(addr, big.NewInt(tt.staked)) + + vmctx := BlockContext{BlockNumber: big.NewInt(0), CanTransfer: canTransfer, Transfer: transfer} + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.AllEthashProtocolChanges, Config{}) + // func selector + to address 0xffff + funcCall := hexutil.MustDecode(fmt.Sprintf("0xc9353cb5%064x", 0xffff)) + + var err error + // self destruct + if callMethod == "call" { + _, _, err = vmenv.Call(AccountRef(common.Address{}), addr, funcCall, math.MaxUint64, new(big.Int)) + } else if callMethod == "callCode" { + _, _, err = vmenv.CallCode(AccountRef(addr), addr, funcCall, math.MaxUint64, new(big.Int)) + } else if callMethod == "delegateCall" { + caller := NewContract(AccountRef(addr), AccountRef(addr), big.NewInt(0), 0) + _, _, err = vmenv.DelegateCall(caller, addr, funcCall, math.MaxUint64) + } else { + panic("unrecognized call method") + } + if err != tt.failure { + t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) + } + if err == nil { // post check for selfdestruct + if bal := statedb.GetBalance(common.HexToAddress(fmt.Sprintf("0x%064x", 0xffff))); bal.Cmp(big.NewInt(tt.staked)) != 0 { + t.Errorf("test %d: destructed balance mismatch: have %v, want %v", i, bal.Int64(), tt.staked) + } + if bal := statedb.GetBalance(addr); bal.Cmp(big.NewInt(0)) != 0 { + t.Errorf("test %d: contract balance mismatch: have %v, want %v", i, bal.Int64(), 0) + } + } + } + } +} diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 9da7ea72f389..73b255a2fb0c 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -137,14 +137,16 @@ var extraGasCodeTests = []struct { {params.MaxCodeSizeSoft*2 + 1, math.MaxUint64, 2627 + params.CallGasEIP150*2, byte(STATICCALL), nil}, } -func codegenWithSize(sz uint) []byte { - // PUSH1 00, PUSH1 00, RETURN - pushAndReturn := hexutil.MustDecode("0x60006000f3") - ret := make([]byte, sz-5) // first 5 bytes are for early return +func codegenWithSize(code []byte, sz uint) []byte { + if len(code) == 0 { + // PUSH1 00, PUSH1 00, RETURN + code = hexutil.MustDecode("0x60006000f3") + } + ret := make([]byte, sz-uint(len(code))) for i := range ret { ret[i] = 42 // meaning of life, bloating the code size } - return append(pushAndReturn, ret...) + return append(code, ret...) } func TestExtraGasForCallW3IP002(t *testing.T) { @@ -169,7 +171,7 @@ func TestExtraGasForCallW3IP002(t *testing.T) { callerCode = append(callerCode, tt.call) statedb.SetCode(caller, callerCode) - statedb.SetCode(callee, codegenWithSize(tt.codeSize)) + statedb.SetCode(callee, codegenWithSize(nil, tt.codeSize)) vmctx := BlockContext{ BlockNumber: big.NewInt(0),