mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
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:
parent
960cd7f64e
commit
507f4724a7
4 changed files with 263 additions and 2 deletions
4
.github/workflows/go.yml
vendored
4
.github/workflows/go.yml
vendored
|
|
@ -2,9 +2,9 @@ name: i386 linux tests
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ disabled ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [ disabled ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
|
|
|||
34
.github/workflows/rip7560test.yml
vendored
Normal file
34
.github/workflows/rip7560test.yml
vendored
Normal 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
|
||||
140
tests/rip7560/rip7560TestUtils.go
Normal file
140
tests/rip7560/rip7560TestUtils.go
Normal 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()
|
||||
}
|
||||
87
tests/rip7560/validation_test.go
Normal file
87
tests/rip7560/validation_test.go
Normal 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)
|
||||
Loading…
Reference in a new issue