diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index 006ffffdc0a..ddda91ea0a7 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -295,6 +295,19 @@ func EthAddressFromPubKey(pubk []byte) ([]byte, error) { return ethAddr, nil } +func IsEthAddress(addr address.Address) bool { + if addr.Protocol() != address.Delegated { + return false + } + payload := addr.Payload() + namespace, _, err := varint.FromUvarint(payload) + if err != nil { + return false + } + + return namespace == builtintypes.EthereumAddressManagerActorID +} + func EthAddressFromFilecoinAddress(addr address.Address) (EthAddress, error) { switch addr.Protocol() { case address.ID: diff --git a/cli/send.go b/cli/send.go index e18aa544e13..cfa2515c07b 100644 --- a/cli/send.go +++ b/cli/send.go @@ -1,15 +1,18 @@ package cli import ( + "bytes" "encoding/hex" "fmt" "strings" "github.com/urfave/cli/v2" + cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" @@ -117,15 +120,51 @@ var sendCmd = &cli.Command{ params.From = faddr } - if params.From.Protocol() == address.Delegated { + if cctx.IsSet("params-hex") { + decparams, err := hex.DecodeString(cctx.String("params-hex")) + if err != nil { + return fmt.Errorf("failed to decode hex params: %w", err) + } + params.Params = decparams + } + + if ethtypes.IsEthAddress(params.From) { + // Method numbers don't make sense from eth accounts. + if cctx.IsSet("method") { + return xerrors.Errorf("messages from f410f addresses may not specify a method number") + } + + // Now, figure out the correct method number from the recipient. + if params.To == builtintypes.EthereumAddressManagerActorAddr { + params.Method = builtintypes.MethodsEAM.CreateExternal + } else { + params.Method = builtintypes.MethodsEVM.InvokeContract + } + + if cctx.IsSet("params-json") { + return xerrors.Errorf("may not call with json parameters from an eth account") + } + + // And format the parameters, if present. + if len(params.Params) > 0 { + var buf bytes.Buffer + if err := cbg.WriteByteArray(&buf, params.Params); err != nil { + return xerrors.Errorf("failed to marshal EVM parameters") + } + params.Params = buf.Bytes() + } + + // We can only send to an f410f or f0 address. if !(params.To.Protocol() == address.ID || params.To.Protocol() == address.Delegated) { api := srv.FullNodeAPI() // Resolve id addr if possible. params.To, err = api.StateLookupID(ctx, params.To, types.EmptyTSK) if err != nil { - return xerrors.Errorf("f4 addresses can only send to other f4 or id addresses. could not find id address for %s", params.To.String()) + return xerrors.Errorf("addresses starting with f410f can only send to other addresses starting with f410f, or id addresses. could not find id address for %s", params.To.String()) } } + } else { + params.Method = abi.MethodNum(cctx.Uint64("method")) } if cctx.IsSet("gas-premium") { @@ -149,22 +188,13 @@ var sendCmd = &cli.Command{ params.GasLimit = &limit } - params.Method = abi.MethodNum(cctx.Uint64("method")) - if cctx.IsSet("params-json") { - decparams, err := srv.DecodeTypedParamsFromJSON(ctx, params.To, params.Method, cctx.String("params-json")) - if err != nil { - return fmt.Errorf("failed to decode json params: %w", err) - } - params.Params = decparams - } - if cctx.IsSet("params-hex") { if params.Params != nil { return fmt.Errorf("can only specify one of 'params-json' and 'params-hex'") } - decparams, err := hex.DecodeString(cctx.String("params-hex")) + decparams, err := srv.DecodeTypedParamsFromJSON(ctx, params.To, params.Method, cctx.String("params-json")) if err != nil { - return fmt.Errorf("failed to decode hex params: %w", err) + return fmt.Errorf("failed to decode json params: %w", err) } params.Params = decparams } diff --git a/cli/send_test.go b/cli/send_test.go index ec858774ce2..2c59a9641f6 100644 --- a/cli/send_test.go +++ b/cli/send_test.go @@ -7,14 +7,16 @@ import ( "testing" "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ucli "github.com/urfave/cli/v2" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" ) func mustAddr(a address.Address, err error) address.Address { @@ -65,7 +67,51 @@ func TestSendCLI(t *testing.T) { mockSrvcs.EXPECT().Close(), ) err := app.Run([]string{"lotus", "send", "t01", "1"}) - assert.NoError(t, err) - assert.EqualValues(t, sigMsg.Cid().String()+"\n", buf.String()) + require.NoError(t, err) + require.EqualValues(t, sigMsg.Cid().String()+"\n", buf.String()) + }) +} + +func TestSendEthereum(t *testing.T) { + oneFil := abi.TokenAmount(types.MustParseFIL("1")) + + t.Run("simple", func(t *testing.T) { + app, mockSrvcs, buf, done := newMockApp(t, sendCmd) + defer done() + + testEthAddr, err := ethtypes.CastEthAddress(make([]byte, 20)) + require.NoError(t, err) + testAddr := mustAddr(testEthAddr.ToFilecoinAddress()) + + params := abi.CborBytes([]byte{1, 2, 3, 4}) + var paramsBuf bytes.Buffer + require.NoError(t, params.MarshalCBOR(¶msBuf)) + + arbtProto := &api.MessagePrototype{ + Message: types.Message{ + From: testAddr, + To: mustAddr(address.NewIDAddress(1)), + Value: oneFil, + Method: builtin.MethodsEVM.InvokeContract, + Params: paramsBuf.Bytes(), + }, + } + sigMsg := fakeSign(&arbtProto.Message) + + gomock.InOrder( + mockSrvcs.EXPECT().MessageForSend(gomock.Any(), SendParams{ + From: testAddr, + To: mustAddr(address.NewIDAddress(1)), + Val: oneFil, + Method: builtin.MethodsEVM.InvokeContract, + Params: paramsBuf.Bytes(), + }).Return(arbtProto, nil), + mockSrvcs.EXPECT().PublishMessage(gomock.Any(), arbtProto, false). + Return(sigMsg, nil, nil), + mockSrvcs.EXPECT().Close(), + ) + err = app.Run([]string{"lotus", "send", "--from-eth-addr", testEthAddr.String(), "--params-hex", "01020304", "f01", "1"}) + require.NoError(t, err) + require.EqualValues(t, sigMsg.Cid().String()+"\n", buf.String()) }) }