mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
Merge pull request #12 from eth-infinitism/test-flows
AA-343 Test account flows, align with RIP
This commit is contained in:
commit
1fc0bd420e
5 changed files with 179 additions and 67 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ func (tx *Rip7560AccountAbstractionTx) copy() TxData {
|
|||
cpy := &Rip7560AccountAbstractionTx{
|
||||
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)),
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue