From 507f4724a75071cb57332317c42d489e9cc72fd0 Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Mon, 3 Jun 2024 19:24:21 +0300 Subject: [PATCH] Test ApplyRip7560ValidationPhases (#5) * initial UT for ApplyRip7560ValidationPhases test ./tests/rip7560/ * refactor test context * add some error tests * returnData helper * github actions for rip7560 tests * update path * refactor test utils * refactor textContextBuilder * remove prestate json. * pr comments --- .github/workflows/go.yml | 4 +- .github/workflows/rip7560test.yml | 34 ++++++++ tests/rip7560/rip7560TestUtils.go | 140 ++++++++++++++++++++++++++++++ tests/rip7560/validation_test.go | 87 +++++++++++++++++++ 4 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/rip7560test.yml create mode 100644 tests/rip7560/rip7560TestUtils.go create mode 100644 tests/rip7560/validation_test.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0c673d15f1..93ec823623 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -2,9 +2,9 @@ name: i386 linux tests on: push: - branches: [ master ] + branches: [ disabled ] pull_request: - branches: [ master ] + branches: [ disabled ] workflow_dispatch: jobs: diff --git a/.github/workflows/rip7560test.yml b/.github/workflows/rip7560test.yml new file mode 100644 index 0000000000..8d1b43f4e8 --- /dev/null +++ b/.github/workflows/rip7560test.yml @@ -0,0 +1,34 @@ +name: rip7560-test + +on: + pull_request: + branches: + +jobs: + + build: + name: Run RIP-7560 tests + runs-on: ubuntu-latest + steps: + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.22.1 + + - name: Cache Go dependencies + uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Get dependencies + run: go mod download + + - name: Test + run: go test -v ./tests/rip7560 diff --git a/tests/rip7560/rip7560TestUtils.go b/tests/rip7560/rip7560TestUtils.go new file mode 100644 index 0000000000..9a330cbfc3 --- /dev/null +++ b/tests/rip7560/rip7560TestUtils.go @@ -0,0 +1,140 @@ +package rip7560 + +import ( + "bytes" + "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/params" + "github.com/status-im/keycard-go/hexutils" + "math/big" + "testing" +) + +const DEFAULT_SENDER = "0x1111111111222222222233333333334444444444" + +type testContext struct { + genesisAlloc types.GenesisAlloc + t *testing.T + chainContext *ethapi.ChainContext + chainConfig *params.ChainConfig + gaspool *core.GasPool + genesis *core.Genesis + genesisBlock *types.Block +} + +func newTestContext(t *testing.T) *testContext { + return newTestContextBuilder(t).build() +} + +type testContextBuilder struct { + t *testing.T + chainConfig *params.ChainConfig + genesisAlloc types.GenesisAlloc +} + +func newTestContextBuilder(t *testing.T) *testContextBuilder { + genesisAlloc := types.GenesisAlloc{} + + chainConfig := params.AllDevChainProtocolChanges + // probably bug in geth.. + chainConfig.PragueTime = chainConfig.CancunTime + + return &testContextBuilder{ + t: t, + chainConfig: chainConfig, + genesisAlloc: genesisAlloc, + } +} + +func (tb *testContextBuilder) build() *testContext { + genesis := &core.Genesis{ + Config: params.AllDevChainProtocolChanges, + Alloc: tb.genesisAlloc, + } + genesisBlock := genesis.ToBlock() + gaspool := new(core.GasPool).AddGas(genesisBlock.GasLimit()) + + //TODO: fill some mock backend... + var backend ethapi.Backend + + return &testContext{ + t: tb.t, + genesisAlloc: tb.genesisAlloc, + chainContext: ethapi.NewChainContext(context.TODO(), backend), + chainConfig: tb.chainConfig, + genesis: genesis, + genesisBlock: genesisBlock, + gaspool: gaspool, + } +} + +// add EOA account with balance +func (tt *testContextBuilder) withAccount(addr string, balance int64) *testContextBuilder { + tt.genesisAlloc[common.HexToAddress(addr)] = types.Account{Balance: big.NewInt(balance)} + return tt +} + +func (tt *testContextBuilder) withCode(addr string, code []byte, balance int64) *testContextBuilder { + if len(code) == 0 { + tt.genesisAlloc[common.HexToAddress(addr)] = types.Account{ + Balance: big.NewInt(balance), + } + } else { + tt.genesisAlloc[common.HexToAddress(addr)] = types.Account{ + Code: code, + Balance: big.NewInt(balance), + } + } + return tt +} + +// generate the code to return the given byte array (up to 32 bytes) +func returnData(data []byte) []byte { + //couldn't get geth to support PUSH0 ... + datalen := len(data) + if datalen == 0 { + data = []byte{0} + } + if datalen > 32 { + panic(fmt.Errorf("data length is too big %v", data)) + } + + PUSHn := byte(int(vm.PUSH0) + datalen) + ret := createCode(PUSHn, data, vm.PUSH1, 0, vm.MSTORE, vm.PUSH1, 32, vm.PUSH1, 0, vm.RETURN) + return ret +} + +// create EVM code from OpCode, byte and []bytes +func createCode(items ...interface{}) []byte { + var buffer bytes.Buffer + + for _, item := range items { + switch v := item.(type) { + case string: + buffer.Write(hexutils.HexToBytes(v)) + case vm.OpCode: + buffer.WriteByte(byte(v)) + case byte: + buffer.WriteByte(v) + case []byte: + buffer.Write(v) + case int8: + buffer.WriteByte(byte(v)) + case int: + if v >= 256 { + panic(fmt.Errorf("int defaults to int8 (byte). int16, etc: %v", v)) + } + buffer.WriteByte(byte(v)) + default: + // should be a compile-time error... + panic(fmt.Errorf("unsupported type: %T", v)) + } + } + + return buffer.Bytes() +} diff --git a/tests/rip7560/validation_test.go b/tests/rip7560/validation_test.go new file mode 100644 index 0000000000..2ab9ca6490 --- /dev/null +++ b/tests/rip7560/validation_test.go @@ -0,0 +1,87 @@ +package rip7560 + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/tests" + "github.com/stretchr/testify/assert" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +func TestValidation_OOG(t *testing.T) { + magic := big.NewInt(0xbf45c166) + magic.Lsh(magic, 256-32) + + validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, returnData(magic.Bytes()), 0), types.Rip7560AccountAbstractionTx{ + ValidationGas: uint64(1), + GasFeeCap: big.NewInt(1000000000), + }, "out of gas") +} + +func TestValidation_ok(t *testing.T) { + magic := big.NewInt(0xbf45c166) + magic.Lsh(magic, 256-32) + + validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, returnData(magic.Bytes()), 0), types.Rip7560AccountAbstractionTx{ + ValidationGas: uint64(1000000000), + GasFeeCap: big.NewInt(1000000000), + }, "") +} + +func TestValidation_account_revert(t *testing.T) { + validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, + createCode(vm.PUSH1, 0, vm.DUP1, vm.REVERT), 0), types.Rip7560AccountAbstractionTx{ + ValidationGas: uint64(1000000000), + GasFeeCap: big.NewInt(1000000000), + }, "execution reverted") +} + +func TestValidation_account_no_return_value(t *testing.T) { + validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, []byte{ + byte(vm.PUSH1), 0, byte(vm.DUP1), byte(vm.RETURN), + }, 0), types.Rip7560AccountAbstractionTx{ + ValidationGas: uint64(1000000000), + GasFeeCap: big.NewInt(1000000000), + }, "invalid account return data length") +} + +func TestValidation_account_wrong_return_value(t *testing.T) { + validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, + returnData(createCode(1)), + 0), types.Rip7560AccountAbstractionTx{ + ValidationGas: uint64(1000000000), + GasFeeCap: big.NewInt(1000000000), + }, "account did not return correct MAGIC_VALUE") +} + +func validatePhase(tb *testContextBuilder, aatx types.Rip7560AccountAbstractionTx, expectedErr string) { + t := tb.build() + if aatx.Sender == nil { + //pre-deployed sender account + Sender := common.HexToAddress(DEFAULT_SENDER) + aatx.Sender = &Sender + } + tx := types.NewTx(&aatx) + + var state = tests.MakePreState(rawdb.NewMemoryDatabase(), t.genesisAlloc, false, rawdb.HashScheme) + defer state.Close() + + _, err := core.ApplyRip7560ValidationPhases(t.chainConfig, t.chainContext, &common.Address{}, t.gaspool, state.StateDB, t.genesisBlock.Header(), tx, vm.Config{}) + // err string or empty if nil + errStr := "" + if err != nil { + errStr = err.Error() + } + assert.Equal(t.t, expectedErr, errStr) +} + +//test failure on non-rip7560 + +//IntrinsicGas: for validation frame, should return the max possible gas. +// - execution should be "free" (and refund the excess) +// geth increment nonce before "call" our validation frame. (in ApplyMessage)