From b0b490f350fa95920bbdfc389f6bea7c2f6a2681 Mon Sep 17 00:00:00 2001 From: Dror Tirosh Date: Mon, 1 Jul 2024 20:49:00 +0300 Subject: [PATCH] test paymaster validations --- core/state_processor_rip7560.go | 7 ++-- tests/rip7560/paymaster_test.go | 56 +++++++++++++++++++++++-------- tests/rip7560/rip7560TestUtils.go | 34 +++++++++++++++++++ tests/rip7560/validation_test.go | 4 +-- 4 files changed, 83 insertions(+), 18 deletions(-) diff --git a/core/state_processor_rip7560.go b/core/state_processor_rip7560.go index 0ceb83448e..aa815ad76d 100644 --- a/core/state_processor_rip7560.go +++ b/core/state_processor_rip7560.go @@ -447,10 +447,10 @@ func preparePostOpMessage(vpr *ValidationPhaseResult, chainConfig *params.ChainC if err != nil { return nil, err } - var paymasterAddress common.Address = [20]byte(tx.PaymasterData[0:20]) + var paymasterAddress = tx.Paymaster return &Message{ From: chainConfig.EntryPointAddress, - To: &paymasterAddress, + To: paymasterAddress, Value: big.NewInt(0), GasLimit: tx.PaymasterGas - executionResult.UsedGas, GasPrice: tx.GasFeeCap, @@ -490,6 +490,9 @@ func validatePaymasterReturnData(data []byte) (context []byte, validAfter, valid if magicExpected != MAGIC_VALUE_PAYMASTER { return nil, 0, 0, errors.New("paymaster did not return correct MAGIC_VALUE") } + if len(context) > PAYMASTER_MAX_CONTEXT_SIZE { + return nil, 0, 0, errors.New("paymaster context too large") + } return context, validAfter, validUntil, nil } diff --git a/tests/rip7560/paymaster_test.go b/tests/rip7560/paymaster_test.go index bb2cd6c670..4d9a37ce4e 100644 --- a/tests/rip7560/paymaster_test.go +++ b/tests/rip7560/paymaster_test.go @@ -6,7 +6,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "math/big" - "slices" "testing" ) @@ -42,19 +41,6 @@ func TestPaymasterValidationFailure_revert(t *testing.T) { }, "execution reverted") } -func asBytes32(a int) []byte { - return common.LeftPadBytes(big.NewInt(int64(a)).Bytes(), 32) -} -func paymasterReturnValue(magic, validAfter, validUntil uint64, context []byte) []byte { - validationData := core.PackValidationData(magic, validUntil, validAfter) - //manual encode (bytes32 validationData, bytes context) - return slices.Concat( - common.LeftPadBytes(validationData, 32), - asBytes32(64), - asBytes32(len(context)), - context) -} - func TestPaymasterValidationFailure_unparseable_return_value(t *testing.T) { handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), 0). @@ -65,3 +51,45 @@ func TestPaymasterValidationFailure_unparseable_return_value(t *testing.T) { Paymaster: &DEFAULT_PAYMASTER, }, "invalid paymaster return data") } + +func TestPaymasterValidationFailure_wrong_magic(t *testing.T) { + handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), 0). + withCode(DEFAULT_PAYMASTER.String(), returnWithData(paymasterReturnValue(1, 2, 3, []byte{})), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{ + ValidationGas: 1000000000, + PaymasterGas: 1000000000, + GasFeeCap: big.NewInt(1000000000), + Paymaster: &DEFAULT_PAYMASTER, + }, "paymaster did not return correct MAGIC_VALUE") +} + +func TestPaymasterValidationFailure_contextTooLarge(t *testing.T) { + //paymaster returning huge context. + // first word is magic return value + // 2nd word is offset (fixed 64) + // 3rd word is length of context (max+1) + // then we return the total length of above (context itself is uninitialized string of max+1 zeroes) + pmCode := createCode( + //vm.PUSH1, 1, vm.PUSH0, vm.RETURN, + copyToMemory(core.PackValidationData(core.MAGIC_VALUE_PAYMASTER, 0, 0), 0), + copyToMemory(asBytes32(64), 32), + copyToMemory(asBytes32(core.PAYMASTER_MAX_CONTEXT_SIZE+1), 64), + push(core.PAYMASTER_MAX_CONTEXT_SIZE+96+1), vm.PUSH0, vm.RETURN) + + handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), 0). + withCode(DEFAULT_PAYMASTER.String(), pmCode, DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{ + ValidationGas: 1000000000, + PaymasterGas: 1000000000, + GasFeeCap: big.NewInt(1000000000), + Paymaster: &DEFAULT_PAYMASTER, + }, "paymaster context too large") +} + +func TestPaymasterValidation_ok(t *testing.T) { + handleTransaction(newTestContextBuilder(t).withCode(DEFAULT_SENDER, createAccountCode(), 0). + withCode(DEFAULT_PAYMASTER.String(), returnWithData(paymasterReturnValue(core.MAGIC_VALUE_PAYMASTER, 0, 0, []byte{})), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{ + ValidationGas: 1000000000, + PaymasterGas: 1000000000, + GasFeeCap: big.NewInt(1000000000), + Paymaster: &DEFAULT_PAYMASTER, + }, "ok") +} diff --git a/tests/rip7560/rip7560TestUtils.go b/tests/rip7560/rip7560TestUtils.go index a60b473066..82c46650dd 100644 --- a/tests/rip7560/rip7560TestUtils.go +++ b/tests/rip7560/rip7560TestUtils.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/status-im/keycard-go/hexutils" "math/big" + "slices" "testing" ) @@ -83,6 +84,23 @@ func (tt *testContextBuilder) withCode(addr string, code []byte, balance int64) return tt } +// generate a push opcode and its following constant value +func push(n int) []byte { + if n < 0 { + panic("attempt to push negative") + } + if n < 256 { + return createCode(vm.PUSH1, byte(n)) + } + if n < 65536 { + return createCode(vm.PUSH2, byte(n>>8), byte(n)) + } + if n < 1<<32 { + return createCode(vm.PUSH4, byte(n>>24), byte(n>>16), byte(n>>8), byte(n)) + } + panic("larger number") +} + // create code to copy data into memory at the given offset // NOTE: if data is not in 32-byte multiples, it will override the next bytes // used by RETURN/REVERT @@ -135,6 +153,8 @@ func createCode(items ...interface{}) []byte { buffer.WriteByte(byte(v)) case uint16: buffer.Write([]byte{byte(v >> 8), byte(v)}) + case uint32: + buffer.Write([]byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}) case int: if v >= 256 { panic(fmt.Errorf("int defaults to int8 (byte). use int16, etc: %v", v)) @@ -148,3 +168,17 @@ func createCode(items ...interface{}) []byte { return buffer.Bytes() } + +func asBytes32(a int) []byte { + return common.LeftPadBytes(big.NewInt(int64(a)).Bytes(), 32) +} + +func paymasterReturnValue(magic, validUntil, validAfter uint64, context []byte) []byte { + validationData := core.PackValidationData(magic, validUntil, validAfter) + //manual encode (bytes32 validationData, bytes context) + return slices.Concat( + common.LeftPadBytes(validationData, 32), + asBytes32(64), + asBytes32(len(context)), + context) +} diff --git a/tests/rip7560/validation_test.go b/tests/rip7560/validation_test.go index 1afec6c7f0..c7a75fdc91 100644 --- a/tests/rip7560/validation_test.go +++ b/tests/rip7560/validation_test.go @@ -53,7 +53,7 @@ func TestValidationFailure_sigerror(t *testing.T) { 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{ + returnWithData(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") @@ -62,7 +62,7 @@ func TestValidationFailure_validAfter(t *testing.T) { 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{ + returnWithData(core.PackValidationData(core.MAGIC_VALUE_SENDER, 1, 0)), DEFAULT_BALANCE), types.Rip7560AccountAbstractionTx{ ValidationGas: uint64(1000000000), GasFeeCap: big.NewInt(1000000000), }, "RIP-7560 transaction validity expired")