Merge pull request #12 from eth-infinitism/test-flows

AA-343 Test account flows, align with RIP
This commit is contained in:
Dror Tirosh 2024-07-07 12:36:33 +03:00 committed by GitHub
commit 1fc0bd420e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 179 additions and 67 deletions

View file

@ -1,7 +1,6 @@
package core
import (
"encoding/binary"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
@ -15,6 +14,36 @@ import (
"strings"
)
const MAGIC_VALUE_SENDER = uint64(0xbf45c166)
const MAGIC_VALUE_PAYMASTER = uint64(0xe0e6183a)
const MAGIC_VALUE_SIGFAIL = uint64(0x31665494)
const PAYMASTER_MAX_CONTEXT_SIZE = 65536
func PackValidationData(authorizerMagic uint64, validUntil, validAfter uint64) []byte {
t := new(big.Int).SetUint64(uint64(validAfter))
t = t.Lsh(t, 48).Add(t, new(big.Int).SetUint64(validUntil&0xffffff))
t = t.Lsh(t, 160).Add(t, new(big.Int).SetUint64(uint64(authorizerMagic)))
return common.LeftPadBytes(t.Bytes(), 32)
}
func UnpackValidationData(validationData []byte) (authorizerMagic uint64, validUntil, validAfter uint64) {
t := new(big.Int).SetBytes(validationData)
authorizerMagic = t.Uint64()
validUntil = t.Rsh(t, 160).Uint64() & 0xffffff
validAfter = t.Rsh(t, 48).Uint64()
return
}
func UnpackPaymasterValidationReturn(paymasterValidationReturn []byte) (validationData, context []byte) {
validationData = paymasterValidationReturn[0:32]
//2nd bytes32 is ignored (its an offset value)
contextLen := new(big.Int).SetBytes(paymasterValidationReturn[64:96])
context = paymasterValidationReturn[96 : 96+contextLen.Uint64()]
return
}
type ValidationPhaseResult struct {
TxIndex int
Tx *types.Transaction
@ -59,11 +88,16 @@ func handleRip7560Transactions(transactions []*types.Transaction, index int, sta
aatx := tx.Rip7560TransactionData()
statedb.SetTxContext(tx.Hash(), index+i)
err := BuyGasRip7560Transaction(aatx, statedb)
var vpr *ValidationPhaseResult
err := CheckNonceRip7560(aatx, statedb)
if err != nil {
return nil, nil, nil, err
}
err = BuyGasRip7560Transaction(aatx, statedb)
if err != nil {
return nil, nil, nil, err
}
var vpr *ValidationPhaseResult
vpr, err = ApplyRip7560ValidationPhases(chainConfig, bc, coinbase, gp, statedb, header, tx, cfg)
if err != nil {
return nil, nil, nil, err
@ -113,6 +147,24 @@ func BuyGasRip7560Transaction(st *types.Rip7560AccountAbstractionTx, state vm.St
return nil
}
// precheck nonce of transaction.
// (standard preCheck function check both nonce and no-code of account)
func CheckNonceRip7560(tx *types.Rip7560AccountAbstractionTx, st *state.StateDB) error {
// Make sure this transaction's nonce is correct.
stNonce := st.GetNonce(*tx.Sender)
if msgNonce := tx.Nonce; stNonce < msgNonce {
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh,
tx.Sender.Hex(), msgNonce, stNonce)
} else if stNonce > msgNonce {
return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow,
tx.Sender.Hex(), msgNonce, stNonce)
} else if stNonce+1 < stNonce {
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
tx.Sender.Hex(), stNonce)
}
return nil
}
func ApplyRip7560ValidationPhases(chainConfig *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, cfg vm.Config) (*ValidationPhaseResult, error) {
blockContext := NewEVMBlockContext(header, bc, author)
txContext := vm.TxContext{
@ -399,45 +451,30 @@ func preparePostOpMessage(vpr *ValidationPhaseResult, chainConfig *params.ChainC
}
func validateAccountReturnData(data []byte) (uint64, uint64, error) {
MAGIC_VALUE_SENDER := uint32(0xbf45c166)
if len(data) != 32 {
return 0, 0, errors.New("invalid account return data length")
}
magicExpected := binary.BigEndian.Uint32(data[:4])
magicExpected, validUntil, validAfter := UnpackValidationData(data)
//todo: we check first 8 bytes of the 20-byte address (the rest is expected to be zeros)
if magicExpected != MAGIC_VALUE_SENDER {
if magicExpected == MAGIC_VALUE_SIGFAIL {
return 0, 0, errors.New("account signature error")
}
return 0, 0, errors.New("account did not return correct MAGIC_VALUE")
}
validAfter := binary.BigEndian.Uint64(data[4:12])
validUntil := binary.BigEndian.Uint64(data[12:20])
return validAfter, validUntil, nil
}
func validatePaymasterReturnData(data []byte) ([]byte, uint64, uint64, error) {
MAGIC_VALUE_PAYMASTER := uint32(0xe0e6183a)
if len(data) < 4 {
func validatePaymasterReturnData(data []byte) (context []byte, validAfter, validUntil uint64, error error) {
if len(data) < 32 {
return nil, 0, 0, errors.New("invalid paymaster return data length")
}
magicExpected := binary.BigEndian.Uint32(data[:4])
validationData, context := UnpackPaymasterValidationReturn(data)
magicExpected, validAfter, validUntil := UnpackValidationData(validationData)
if magicExpected != MAGIC_VALUE_PAYMASTER {
return nil, 0, 0, errors.New("paymaster did not return correct MAGIC_VALUE")
}
jsondata := `[
{"type":"function","name":"validatePaymasterTransaction","outputs": [{"name": "context","type": "bytes"},{"name": "validUntil","type": "uint256"},{"name": "validAfter","type": "uint256"}]}
]`
validatePaymasterTransactionAbi, err := abi.JSON(strings.NewReader(jsondata))
if err != nil {
// todo: wrap error message
return nil, 0, 0, err
}
decodedPmReturnData, err := validatePaymasterTransactionAbi.Unpack("validatePaymasterTransaction", data[4:])
if err != nil {
return nil, 0, 0, err
}
context := decodedPmReturnData[0].([]byte)
validAfter := decodedPmReturnData[1].(*big.Int)
validUntil := decodedPmReturnData[2].(*big.Int)
return context, validAfter.Uint64(), validUntil.Uint64(), nil
return context, validAfter, validUntil, nil
}
func validateValidityTimeRange(time uint64, validAfter uint64, validUntil uint64) error {

View file

@ -55,9 +55,10 @@ type Rip7560AccountAbstractionTx struct {
// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *Rip7560AccountAbstractionTx) copy() TxData {
cpy := &Rip7560AccountAbstractionTx{
To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data),
Gas: tx.Gas,
To: copyAddressPtr(tx.To),
Data: common.CopyBytes(tx.Data),
Nonce: tx.Nonce,
Gas: tx.Gas,
// These are copied below.
AccessList: make(AccessList, len(tx.AccessList)),
Value: new(big.Int),

View file

@ -48,9 +48,7 @@ func TestProcess1(t *testing.T) {
Data: []byte{1, 2, 3},
},
})
if err != nil {
panic(err)
}
assert.NoError(t, err)
}
// run a set of AA transactions, with a legacy TXs before and after.

View file

@ -15,6 +15,7 @@ import (
)
const DEFAULT_SENDER = "0x1111111111222222222233333333334444444444"
const DEFAULT_BALANCE = 1 << 62
type testContext struct {
genesisAlloc types.GenesisAlloc
@ -45,6 +46,7 @@ func newTestContextBuilder(t *testing.T) *testContextBuilder {
func (tb *testContextBuilder) build() *testContext {
genesis := core.DeveloperGenesisBlock(10_000_000, &common.Address{})
genesis.Timestamp = 100
genesisBlock := genesis.ToBlock()
gaspool := new(core.GasPool).AddGas(genesisBlock.GasLimit())
@ -83,26 +85,18 @@ func (tt *testContextBuilder) withCode(addr string, code []byte, balance int64)
// 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)
ret := createCode(PUSHn, data, vm.PUSH0, vm.MSTORE, vm.PUSH1, datalen, vm.PUSH1, 0, vm.RETURN)
return ret
}
// create bytecode for account
func createAccountCode() []byte {
magic := big.NewInt(0xbf45c166)
magic.Lsh(magic, 256-32)
return returnData(magic.Bytes())
return returnData(core.PackValidationData(core.MAGIC_VALUE_SENDER, 0, 0))
}
// create EVM code from OpCode, byte and []bytes

View file

@ -13,51 +13,132 @@ import (
"github.com/ethereum/go-ethereum/core/types"
)
func TestValidation_OOG(t *testing.T) {
magic := big.NewInt(0xbf45c166)
magic.Lsh(magic, 256-32)
func TestPackValidationData(t *testing.T) {
// --------------- after 6bytes before 6 bytes magic 20 bytes
validationData := "000000000002" + "000000000001" + "0000000000000000000000000000000000001234"
packed, _ := new(big.Int).SetString(validationData, 16)
assert.Equal(t, packed.Text(16), new(big.Int).SetBytes(core.PackValidationData(0x1234, 1, 2)).Text(16))
}
validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, returnData(magic.Bytes()), 0), types.Rip7560AccountAbstractionTx{
func TestUnpackValidationData(t *testing.T) {
packed := core.PackValidationData(0xdead, 0xcafe, 0xface)
magic, until, after := core.UnpackValidationData(packed)
assert.Equal(t, []uint64{0xdead, 0xcafe, 0xface}, []uint64{magic, until, after})
}
func TestValidationFailure_OOG(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1),
GasFeeCap: big.NewInt(1000000000),
}, "out of gas")
}
func TestValidation_ok(t *testing.T) {
func TestValidationFailure_no_balance(t *testing.T) {
validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), 0), types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1000000000),
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), 1), types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1),
GasFeeCap: big.NewInt(1000000000),
}, "")
}, "insufficient funds for gas * price + value: address 0x1111111111222222222233333333334444444444 have 1 want 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{
func TestValidationFailure_sigerror(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER, returnData(core.PackValidationData(core.MAGIC_VALUE_SIGFAIL, 0, 0)), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1000000000),
GasFeeCap: big.NewInt(1000000000),
}, "account signature error")
}
func TestValidationFailure_validAfter(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER,
returnData(core.PackValidationData(core.MAGIC_VALUE_SENDER, 300, 200)), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1000000000),
GasFeeCap: big.NewInt(1000000000),
}, "RIP-7560 transaction validity not reached yet")
}
func TestValidationFailure_validUntil(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER,
returnData(core.PackValidationData(core.MAGIC_VALUE_SENDER, 1, 0)), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1000000000),
GasFeeCap: big.NewInt(1000000000),
}, "RIP-7560 transaction validity expired")
}
func TestValidation_ok(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1000000000),
GasFeeCap: big.NewInt(1000000000),
}, "ok")
}
func TestValidation_ok_paid(t *testing.T) {
aatx := types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1000000000),
GasFeeCap: big.NewInt(1000000000),
}
tb := newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), DEFAULT_BALANCE)
handleTransaction(tb, aatx, "ok")
maxCost := new(big.Int).SetUint64(aatx.ValidationGas + aatx.PaymasterGas + aatx.Gas)
maxCost.Mul(maxCost, aatx.GasFeeCap)
}
func TestValidationFailure_account_nonce(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{
Nonce: 1234,
ValidationGas: uint64(1000000000),
GasFeeCap: big.NewInt(1000000000),
}, "nonce too high: address 0x1111111111222222222233333333334444444444, tx: 1234 state: 0")
}
func TestValidationFailure_account_revert(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER,
createCode(vm.PUSH0, vm.DUP1, vm.REVERT), DEFAULT_BALANCE), 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{
func TestValidationFailure_account_out_of_range(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER,
createCode(vm.PUSH0, vm.DUP1, vm.REVERT), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1000000000),
GasFeeCap: big.NewInt(1000000000),
}, "execution reverted")
}
func TestValidationFailure_account_wrong_return_length(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER,
returnData([]byte{1, 2, 3}), DEFAULT_BALANCE), 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{
func TestValidationFailure_account_no_return_value(t *testing.T) {
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER,
returnData([]byte{}), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{
ValidationGas: uint64(1000000000),
GasFeeCap: big.NewInt(1000000000),
}, "invalid account return data length")
}
func TestValidationFailure_account_wrong_return_value(t *testing.T) {
// create buffer of 32 byte array
handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER,
returnData(make([]byte, 32)),
DEFAULT_BALANCE), 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) {
func handleTransaction(tb *testContextBuilder, aatx types.Rip7560AccountAbstractionTx, expectedErr string) {
t := tb.build()
if aatx.Sender == nil {
//pre-deployed sender account
@ -69,9 +150,10 @@ func validatePhase(tb *testContextBuilder, aatx types.Rip7560AccountAbstractionT
var state = tests.MakePreState(rawdb.NewMemoryDatabase(), t.genesisAlloc, false, rawdb.HashScheme)
defer state.Close()
_, err := core.ApplyRip7560ValidationPhases(t.genesis.Config, t.chainContext, &common.Address{}, t.gaspool, state.StateDB, t.genesisBlock.Header(), tx, vm.Config{})
// err string or empty if nil
errStr := ""
state.StateDB.SetTxContext(tx.Hash(), 0)
_, _, _, err := core.HandleRip7560Transactions([]*types.Transaction{tx}, 0, state.StateDB, &common.Address{}, t.genesisBlock.Header(), t.gaspool, t.genesis.Config, t.chainContext, vm.Config{})
errStr := "ok"
if err != nil {
errStr = err.Error()
}