diff --git a/core/state_processor_rip7560.go b/core/state_processor_rip7560.go index 599ee35682..13d4558ba2 100644 --- a/core/state_processor_rip7560.go +++ b/core/state_processor_rip7560.go @@ -177,8 +177,9 @@ func CheckNonceRip7560(tx *types.Rip7560AccountAbstractionTx, st *state.StateDB) 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) + sender := tx.Rip7560TransactionData().Sender txContext := vm.TxContext{ - Origin: *tx.Rip7560TransactionData().Sender, + Origin: *sender, GasPrice: tx.GasFeeCap(), } evm := vm.NewEVM(blockContext, txContext, statedb, chainConfig, cfg) @@ -186,16 +187,25 @@ func ApplyRip7560ValidationPhases(chainConfig *params.ChainConfig, bc ChainConte deployerMsg := prepareDeployerMessage(tx, chainConfig) var deploymentUsedGas uint64 if deployerMsg != nil { - resultDeployer, err := ApplyMessage(evm, deployerMsg, gp) + var err error + var resultDeployer *ExecutionResult + if statedb.GetCodeSize(*sender) != 0 { + err = errors.New("sender already deployed") + } else { + resultDeployer, err = ApplyMessage(evm, deployerMsg, gp) + } + if err == nil && resultDeployer != nil { + err = resultDeployer.Err + deploymentUsedGas = resultDeployer.UsedGas + } + if err == nil && statedb.GetCodeSize(*sender) == 0 { + err = errors.New("sender not deployed") + } if err != nil { - return nil, err + // TODO: bubble up the inner error message to the user, if possible + return nil, fmt.Errorf("account deployment failed: %v", err) } statedb.IntermediateRoot(true) - if resultDeployer.Failed() { - // TODO: bubble up the inner error message to the user, if possible - return nil, errors.New("account deployment failed - invalid transaction") - } - deploymentUsedGas = resultDeployer.UsedGas } /*** Account Validation Frame ***/ @@ -338,19 +348,18 @@ func ApplyRip7560ExecutionPhase(config *params.ChainConfig, vpr *ValidationPhase func prepareDeployerMessage(baseTx *types.Transaction, config *params.ChainConfig) *Message { tx := baseTx.Rip7560TransactionData() - if len(tx.DeployerData) < 20 { + if tx.Deployer == nil { return nil } - var deployerAddress common.Address = [20]byte(tx.DeployerData[0:20]) return &Message{ From: config.DeployerCallerAddress, - To: &deployerAddress, + To: tx.Deployer, Value: big.NewInt(0), GasLimit: tx.ValidationGas, GasPrice: tx.GasFeeCap, GasFeeCap: tx.GasFeeCap, GasTipCap: tx.GasTipCap, - Data: tx.DeployerData[20:], + Data: tx.DeployerData, AccessList: make(types.AccessList, 0), SkipAccountChecks: true, IsRip7560Frame: true, diff --git a/tests/rip7560/deployer_test.go b/tests/rip7560/deployer_test.go new file mode 100644 index 0000000000..e07ea49ef1 --- /dev/null +++ b/tests/rip7560/deployer_test.go @@ -0,0 +1,88 @@ +package rip7560 + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "math/big" + "testing" +) + +var DEPLOYER = common.HexToAddress("0xddddddddddeeeeeeeeeeddddddddddeeeeeeeeee") + +func TestValidationFailure_deployerRevert(t *testing.T) { + handleTransaction(newTestContextBuilder(t). + withCode(DEFAULT_SENDER, []byte{}, DEFAULT_BALANCE). + withCode(DEPLOYER.Hex(), revertWithData([]byte{}), 0), + types.Rip7560AccountAbstractionTx{ + Deployer: &DEPLOYER, + ValidationGas: 1000000000, + GasFeeCap: big.NewInt(1000000000), + }, "account deployment failed: execution reverted") +} + +func TestValidationFailure_deployerOOG(t *testing.T) { + handleTransaction(newTestContextBuilder(t). + withCode(DEFAULT_SENDER, []byte{}, DEFAULT_BALANCE). + withCode(DEPLOYER.Hex(), revertWithData([]byte{}), 0), + types.Rip7560AccountAbstractionTx{ + Deployer: &DEPLOYER, + ValidationGas: 1, + GasFeeCap: big.NewInt(1000000000), + }, "account deployment failed: out of gas") +} + +func TestValidationFailure_senderNotDeployed(t *testing.T) { + handleTransaction(newTestContextBuilder(t). + withCode(DEFAULT_SENDER, []byte{}, DEFAULT_BALANCE). + withCode(DEPLOYER.Hex(), returnWithData([]byte{}), 0), + types.Rip7560AccountAbstractionTx{ + Deployer: &DEPLOYER, + ValidationGas: 1000000000, + GasFeeCap: big.NewInt(1000000000), + }, "account deployment failed: sender not deployed") +} + +func TestValidationFailure_senderAlreadyDeployed(t *testing.T) { + accountCode := revertWithData([]byte{}) + deployerCode := create2(accountCode) + sender := create2_addr(DEPLOYER, accountCode) + handleTransaction(newTestContextBuilder(t). + withCode(sender.Hex(), accountCode, DEFAULT_BALANCE). + withCode(DEPLOYER.Hex(), deployerCode, 0), + types.Rip7560AccountAbstractionTx{ + Sender: &sender, + Deployer: &DEPLOYER, + ValidationGas: 1000000000, + GasFeeCap: big.NewInt(1000000000), + }, "account deployment failed: sender already deployed") +} + +func TestValidationFailure_senderReverts(t *testing.T) { + accountCode := revertWithData([]byte{}) + deployerCode := createCode(create2(accountCode), returnWithData([]byte{})) + sender := create2_addr(DEPLOYER, accountCode) + handleTransaction(newTestContextBuilder(t). + withCode(sender.Hex(), []byte{}, DEFAULT_BALANCE). + withCode(DEPLOYER.Hex(), deployerCode, 0), + types.Rip7560AccountAbstractionTx{ + Sender: &sender, + Deployer: &DEPLOYER, + ValidationGas: 1000000000, + GasFeeCap: big.NewInt(1000000000), + }, "execution reverted") +} + +func TestValidation_deployer_ok(t *testing.T) { + accountCode := createAccountCode() + deployerCode := createCode(create2(accountCode), returnWithData([]byte{})) + sender := create2_addr(DEPLOYER, accountCode) + handleTransaction(newTestContextBuilder(t). + withCode(sender.Hex(), []byte{}, DEFAULT_BALANCE). + withCode(DEPLOYER.Hex(), deployerCode, 0), + types.Rip7560AccountAbstractionTx{ + Sender: &sender, + Deployer: &DEPLOYER, + ValidationGas: 1000000000, + GasFeeCap: big.NewInt(1000000000), + }, "ok") +} diff --git a/tests/rip7560/rip7560TestUtils.go b/tests/rip7560/rip7560TestUtils.go index 82c46650dd..5ebf610d00 100644 --- a/tests/rip7560/rip7560TestUtils.go +++ b/tests/rip7560/rip7560TestUtils.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/status-im/keycard-go/hexutils" "math/big" @@ -45,6 +46,31 @@ func newTestContextBuilder(t *testing.T) *testContextBuilder { } } +// return a contract code that will deploy the given code +func create2_contract(deployedCode []byte) []byte { + + return returnWithData(deployedCode) +} + +// return the generated address when deploying the given code +func create2_addr(deployer common.Address, deployedCode []byte) common.Address { + + contractCode := create2_contract(deployedCode) + data := createCode(0xff, deployer.Bytes(), common.Hash{}, crypto.Keccak256(contractCode)) + return common.BytesToAddress(crypto.Keccak256(data)) +} + +// generate code to call create2 +// note: parameter is the deployed code, not the full contract code +// always use zero value and zero salt. +func create2(deployedCode []byte) []byte { + contractCode := create2_contract(deployedCode) + return createCode( + copyToMemory(contractCode, 0), + push(0), push(len(contractCode)), push(0), push(0), vm.CREATE2, + ) +} + func (tb *testContextBuilder) build() *testContext { genesis := core.DeveloperGenesisBlock(10_000_000, &common.Address{}) genesis.Timestamp = 100 @@ -69,7 +95,6 @@ func (tt *testContextBuilder) withAccount(addr string, balance int64) *testConte tt.genesisAlloc[common.HexToAddress(addr)] = types.Account{Balance: big.NewInt(balance)} return tt } - func (tt *testContextBuilder) withCode(addr string, code []byte, balance int64) *testContextBuilder { if len(code) == 0 { tt.genesisAlloc[common.HexToAddress(addr)] = types.Account{ @@ -113,8 +138,9 @@ func copyToMemory(data []byte, offset uint) []byte { } if len(data) > 0 { - PUSHn := byte(int(vm.PUSH0) + len(data)) - ret = append(ret, createCode(PUSHn, data, vm.PUSH2, uint16(offset), vm.MSTORE)...) + //push data up, as EVM is big-endian + v := common.RightPadBytes(data, 32) + ret = append(ret, createCode(vm.PUSH32, v, vm.PUSH2, uint16(offset), vm.MSTORE)...) } return ret } @@ -160,6 +186,10 @@ func createCode(items ...interface{}) []byte { panic(fmt.Errorf("int defaults to int8 (byte). use int16, etc: %v", v)) } buffer.WriteByte(byte(v)) + case common.Hash: + buffer.Write(v.Bytes()) + case common.Address: + buffer.Write(v.Bytes()) default: // should be a compile-time error... panic(fmt.Errorf("unsupported type: %T", v))