diff --git a/core/state_processor_rip7560.go b/core/state_processor_rip7560.go index d05efdc9a5..208e4609de 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 @@ -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 { diff --git a/core/types/tx_rip7560.go b/core/types/tx_rip7560.go index 96855be3a4..76443ac078 100644 --- a/core/types/tx_rip7560.go +++ b/core/types/tx_rip7560.go @@ -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), 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..1e50d9b7fb 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 @@ -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 diff --git a/tests/rip7560/validation_test.go b/tests/rip7560/validation_test.go index 4a0b47f6b8..cdbd5d3c24 100644 --- a/tests/rip7560/validation_test.go +++ b/tests/rip7560/validation_test.go @@ -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() }