From ca86ce62a7e9db4d24ffa912b9201c34bee16874 Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Sun, 30 Jun 2024 14:38:27 +0300 Subject: [PATCH] update tests, match return values to RIP --- core/state_processor_rip7560.go | 66 +++++++++++-------- tests/rip7560/process_test.go | 4 +- tests/rip7560/rip7560TestUtils.go | 13 +--- tests/rip7560/validation_test.go | 105 ++++++++++++++++++++++++------ 4 files changed, 130 insertions(+), 58 deletions(-) diff --git a/core/state_processor_rip7560.go b/core/state_processor_rip7560.go index d05efdc9a5..23bced3307 100644 --- a/core/state_processor_rip7560.go +++ b/core/state_processor_rip7560.go @@ -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 @@ -399,45 +428,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 { diff --git a/tests/rip7560/process_test.go b/tests/rip7560/process_test.go index 2e83d05861..c3973aaa7b 100644 --- a/tests/rip7560/process_test.go +++ b/tests/rip7560/process_test.go @@ -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. diff --git a/tests/rip7560/rip7560TestUtils.go b/tests/rip7560/rip7560TestUtils.go index 258d8ed609..710fc51897 100644 --- a/tests/rip7560/rip7560TestUtils.go +++ b/tests/rip7560/rip7560TestUtils.go @@ -15,6 +15,7 @@ import ( ) const DEFAULT_SENDER = "0x1111111111222222222233333333334444444444" +const DEFAULT_BALANCE = 1 << 62 type testContext struct { genesisAlloc types.GenesisAlloc @@ -83,26 +84,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 diff --git a/tests/rip7560/validation_test.go b/tests/rip7560/validation_test.go index 4a0b47f6b8..4ccc399676 100644 --- a/tests/rip7560/validation_test.go +++ b/tests/rip7560/validation_test.go @@ -13,51 +13,111 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -func TestValidation_OOG(t *testing.T) { +func TestPackValidationData(t *testing.T) { + //assert.Equal(t, make([]byte, 32), packValidationData(0, 0, 0)) + //assert.Equal(t, new(big.Int).SetInt64(0x1234).Text(16), new(big.Int).SetBytes(packValidationData(0x1234, 0, 0)).Text(16)) + // ------------------------------------ bbbbbbbbbbbb-aaaaaaaaaaa-mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm + packed, _ := new(big.Int).SetString("0000000000020000000000010000000000000000000000000000000000001234", 16) + assert.Equal(t, packed.Text(16), new(big.Int).SetBytes(core.PackValidationData(0x1234, 1, 2)).Text(16)) +} + +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) { magic := big.NewInt(0xbf45c166) magic.Lsh(magic, 256-32) - validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, returnData(magic.Bytes()), 0), types.Rip7560AccountAbstractionTx{ + validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, returnData(magic.Bytes()), 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) { + magic := big.NewInt(0xbf45c166) + magic.Lsh(magic, 256-32) - validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), 0), types.Rip7560AccountAbstractionTx{ - ValidationGas: uint64(1000000000), + validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, returnData(magic.Bytes()), 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) { +func TestValidationFailure_sigerror(t *testing.T) { + validatePhase(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 TestValidation_ok(t *testing.T) { + + validatePhase(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) + validatePhase(tb, aatx, "ok") + + maxCost := new(big.Int).SetUint64(aatx.ValidationGas + aatx.PaymasterGas + aatx.Gas) + maxCost.Mul(maxCost, aatx.GasFeeCap) +} + +func TestValidationFailure_account_revert(t *testing.T) { validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, - createCode(vm.PUSH1, 0, vm.DUP1, vm.REVERT), 0), types.Rip7560AccountAbstractionTx{ + 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) { + validatePhase(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) { + validatePhase(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) { +func TestValidationFailure_account_no_return_value(t *testing.T) { validatePhase(newTestContextBuilder(t).withCode(DEFAULT_SENDER, - returnData(createCode(1)), - 0), types.Rip7560AccountAbstractionTx{ + 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 + validatePhase(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 validatePhase(tb *testContextBuilder, aatx types.Rip7560AccountAbstractionTx, expectedErr string) *core.ValidationPhaseResult { t := tb.build() if aatx.Sender == nil { //pre-deployed sender account @@ -69,13 +129,20 @@ 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.BuyGasRip7560Transaction(&aatx, state.StateDB) + + var res *core.ValidationPhaseResult + if err == nil { + res, 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 := "ok" if err != nil { errStr = err.Error() } assert.Equal(t.t, expectedErr, errStr) + return res } //test failure on non-rip7560