From 37e1ac5d93bf7d4bf6a7f74bac42c577eb668dab Mon Sep 17 00:00:00 2001 From: raulk Date: Sun, 12 Feb 2023 21:08:42 +0000 Subject: [PATCH] eth: FIP-0055: implement final version of transitory delegated signature. (#10239) --- chain/types/ethtypes/eth_transactions.go | 35 ++++----------- itests/eth_account_abstraction_test.go | 35 ++++++++------- itests/fevm_test.go | 57 ++++++++++++++++++++++++ itests/kit/evm.go | 3 +- node/impl/full/eth.go | 5 +-- 5 files changed, 89 insertions(+), 46 deletions(-) diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go index 5c2283d2b..7c065928e 100644 --- a/chain/types/ethtypes/eth_transactions.go +++ b/chain/types/ethtypes/eth_transactions.go @@ -117,41 +117,29 @@ func EthTxArgsFromUnsignedEthMessage(msg *types.Message) (EthTxArgs, error) { default: return EthTxArgs{}, fmt.Errorf("unsupported EAM method") } - } else { + } else if msg.Method == builtintypes.MethodsEVM.InvokeContract { addr, err := EthAddressFromFilecoinAddress(msg.To) if err != nil { return EthTxArgs{}, err } to = &addr - if len(msg.Params) == 0 { - if msg.Method != builtintypes.MethodSend { - return EthTxArgs{}, xerrors.Errorf("cannot invoke method %d on non-EAM actor without params", msg.Method) - } - } else { - if msg.Method != builtintypes.MethodsEVM.InvokeContract { - return EthTxArgs{}, - xerrors.Errorf("invalid methodnum %d: only allowed non-send method is InvokeContract(%d)", - msg.Method, - builtintypes.MethodsEVM.InvokeContract) - } - + if len(msg.Params) > 0 { params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params))) if err != nil { return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err) } } + } else { + return EthTxArgs{}, + xerrors.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d)", + msg.Method, builtintypes.MethodsEVM.InvokeContract) } if paramsReader.Len() != 0 { return EthTxArgs{}, xerrors.Errorf("extra data found in params") } - if len(params) == 0 && msg.Method != builtintypes.MethodSend { - // Otherwise, we don't get a guaranteed round-trip. - return EthTxArgs{}, xerrors.Errorf("msgs with empty parameters from an eth-account must be Sends (MethodNum: %d)", msg.Method) - } - return EthTxArgs{ ChainID: build.Eip155ChainId, Nonce: int(msg.Nonce), @@ -170,9 +158,9 @@ func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, er } var err error - method := builtintypes.MethodSend var params []byte var to address.Address + method := builtintypes.MethodsEVM.InvokeContract // nil indicates the EAM, only CreateExternal is allowed if tx.To == nil { to = builtintypes.EthereumAddressManagerActorAddr @@ -192,18 +180,11 @@ func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, er if err != nil { return nil, xerrors.Errorf("failed to convert To into filecoin addr: %w", err) } - if len(tx.Input) == 0 { - // Yes, this is redundant, but let's be sure what we're doing - method = builtintypes.MethodSend - params = make([]byte, 0) - } else { - // must be InvokeContract - method = builtintypes.MethodsEVM.InvokeContract + if len(tx.Input) > 0 { buf := new(bytes.Buffer) if err = cbg.WriteByteArray(buf, tx.Input); err != nil { return nil, xerrors.Errorf("failed to write input args: %w", err) } - params = buf.Bytes() } } diff --git a/itests/eth_account_abstraction_test.go b/itests/eth_account_abstraction_test.go index e6d9723bf..8d92d0a04 100644 --- a/itests/eth_account_abstraction_test.go +++ b/itests/eth_account_abstraction_test.go @@ -24,7 +24,7 @@ import ( "github.com/filecoin-project/lotus/itests/kit" ) -// TestEthAccountAbstraction goes over the account abstraction workflow: +// TestEthAccountAbstraction goes over the placeholder creation and promotion workflow: // - an placeholder is created when it receives a message // - the placeholder turns into an EOA when it sends a message func TestEthAccountAbstraction(t *testing.T) { @@ -67,7 +67,8 @@ func TestEthAccountAbstraction(t *testing.T) { msgFromPlaceholder := &types.Message{ From: placeholderAddress, // self-send because an "eth tx payload" can't be to a filecoin address? - To: placeholderAddress, + To: placeholderAddress, + Method: builtin2.MethodsEVM.InvokeContract, } msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) @@ -100,9 +101,10 @@ func TestEthAccountAbstraction(t *testing.T) { // Send another message, it should succeed without any code CID changes msgFromPlaceholder = &types.Message{ - From: placeholderAddress, - To: placeholderAddress, - Nonce: 1, + From: placeholderAddress, + To: placeholderAddress, + Method: builtin2.MethodsEVM.InvokeContract, + Nonce: 1, } msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) @@ -152,9 +154,10 @@ func TestEthAccountAbstractionFailure(t *testing.T) { // create a placeholder actor at the target address msgCreatePlaceholder := &types.Message{ - From: client.DefaultKey.Address, - To: placeholderAddress, - Value: abi.TokenAmount(types.MustParseFIL("100")), + From: client.DefaultKey.Address, + To: placeholderAddress, + Value: abi.TokenAmount(types.MustParseFIL("100")), + Method: builtin2.MethodsEVM.InvokeContract, } smCreatePlaceholder, err := client.MpoolPushMessage(ctx, msgCreatePlaceholder, nil) require.NoError(t, err) @@ -172,9 +175,10 @@ func TestEthAccountAbstractionFailure(t *testing.T) { // send a message from the placeholder address msgFromPlaceholder := &types.Message{ - From: placeholderAddress, - To: placeholderAddress, - Value: abi.TokenAmount(types.MustParseFIL("20")), + From: placeholderAddress, + To: placeholderAddress, + Value: abi.TokenAmount(types.MustParseFIL("20")), + Method: builtin2.MethodsEVM.InvokeContract, } msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) require.NoError(t, err) @@ -209,10 +213,11 @@ func TestEthAccountAbstractionFailure(t *testing.T) { // Send a valid message now, it should succeed without any code CID changes msgFromPlaceholder = &types.Message{ - From: placeholderAddress, - To: placeholderAddress, - Nonce: 1, - Value: abi.NewTokenAmount(1), + From: placeholderAddress, + To: placeholderAddress, + Nonce: 1, + Value: abi.NewTokenAmount(1), + Method: builtin2.MethodsEVM.InvokeContract, } msgFromPlaceholder, err = client.GasEstimateMessageGas(ctx, msgFromPlaceholder, nil, types.EmptyTSK) diff --git a/itests/fevm_test.go b/itests/fevm_test.go index 6cdd440dd..2b2a3a6d3 100644 --- a/itests/fevm_test.go +++ b/itests/fevm_test.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "fmt" "testing" + "time" "github.com/stretchr/testify/require" @@ -17,6 +18,7 @@ import ( "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" @@ -786,3 +788,58 @@ func TestFEVMDestroyCreate2(t *testing.T) { require.Equal(t, ethFromAddr, senderSecondCall) } + +func TestFEVMBareTransferTriggersSmartContractLogic(t *testing.T) { + ctx, cancel, client := kit.SetupFEVMTest(t) + defer cancel() + + // This contract emits an event on receiving value. + filename := "contracts/ValueSender.hex" + _, contractAddr := client.EVM().DeployContractFromFilename(ctx, filename) + + accctKey, accntEth, accntFil := client.EVM().NewAccount() + kit.SendFunds(ctx, t, client, accntFil, types.FromFil(10)) + + contractEth, err := ethtypes.EthAddressFromFilecoinAddress(contractAddr) + require.NoError(t, err) + + gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{ + From: &accntEth, + To: &contractEth, + Value: ethtypes.EthBigInt(big.NewInt(100)), + }) + require.NoError(t, err) + + maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx) + require.NoError(t, err) + + tx := ethtypes.EthTxArgs{ + ChainID: build.Eip155ChainId, + Value: big.NewInt(100), + Nonce: 0, + To: &contractEth, + MaxFeePerGas: types.NanoFil, + MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas), + GasLimit: int(gaslimit), + V: big.Zero(), + R: big.Zero(), + S: big.Zero(), + } + + client.EVM().SignTransaction(&tx, accctKey.PrivateKey) + + hash := client.EVM().SubmitTransaction(ctx, &tx) + + var receipt *api.EthTxReceipt + for i := 0; i < 1000; i++ { + receipt, err = client.EthGetTransactionReceipt(ctx, hash) + require.NoError(t, err) + if receipt != nil { + break + } + time.Sleep(500 * time.Millisecond) + } + + // The receive() function emits one log, that's how we know we hit it. + require.Len(t, receipt.Logs, 1) +} diff --git a/itests/kit/evm.go b/itests/kit/evm.go index 8a764793c..216a7f1f3 100644 --- a/itests/kit/evm.go +++ b/itests/kit/evm.go @@ -354,7 +354,7 @@ func SetupFEVMTest(t *testing.T) (context.Context, context.CancelFunc, *TestFull return ctx, cancel, client } -func (e *EVM) TransferValueOrFail(ctx context.Context, fromAddr address.Address, toAddr address.Address, sendAmount big.Int) { +func (e *EVM) TransferValueOrFail(ctx context.Context, fromAddr address.Address, toAddr address.Address, sendAmount big.Int) *api.MsgLookup { sendMsg := &types.Message{ From: fromAddr, To: toAddr, @@ -365,6 +365,7 @@ func (e *EVM) TransferValueOrFail(ctx context.Context, fromAddr address.Address, mLookup, err := e.StateWaitMsg(ctx, signedMsg.Cid(), 3, api.LookbackNoLimit, true) require.NoError(e.t, err) require.Equal(e.t, exitcode.Ok, mLookup.Receipt.ExitCode) + return mLookup } func NewEthFilterBuilder() *EthFilterBuilder { diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 2672894ba..8401d7d51 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -765,10 +765,9 @@ func (a *EthModule) ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.Et return nil, fmt.Errorf("failed to encode tx input into a cbor byte-string") } params = buf.Bytes() - method = builtintypes.MethodsEVM.InvokeContract - } else { - method = builtintypes.MethodSend } + + method = builtintypes.MethodsEVM.InvokeContract } return &types.Message{