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
This commit is contained in:
Dror Tirosh 2024-06-03 19:24:21 +03:00 committed by Dror Tirosh
parent 960cd7f64e
commit 507f4724a7
4 changed files with 263 additions and 2 deletions

View file

@ -2,9 +2,9 @@ name: i386 linux tests
on: on:
push: push:
branches: [ master ] branches: [ disabled ]
pull_request: pull_request:
branches: [ master ] branches: [ disabled ]
workflow_dispatch: workflow_dispatch:
jobs: jobs:

34
.github/workflows/rip7560test.yml vendored Normal file
View file

@ -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

View file

@ -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()
}

View file

@ -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)