Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: eth: encode eth tx input as solidity ABI #11402

Merged
merged 9 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
- feat: Added instructions on how to setup Prometheus/Grafana for monitoring a local Lotus node [filecoin-project/lotus#11276](https://github.com/filecoin-project/lotus/pull/11276)
- fix: Exclude reverted events in `eth_getLogs` results [filecoin-project/lotus#11318](https://github.com/filecoin-project/lotus/pull/11318)
- fix: The Ethereum API will now use the correct state-tree when resolving "native" addresses into masked ID addresses. Additionally, pending messages from native account types won't be visible in the Ethereum API because there is no "correct" state-tree to pick in this case. However, pending _Ethereum_ transactions and native messages that have landed on-chain will still be visible through the Ethereum API.
- feat: Unambiguously translate native messages to Ethereum transactions by:
- Detecting native messages that "look" like Ethereum transactions (creating smart contracts, invoking a smart contract, etc.), and decoding them as such.
- Otherwise, ABI-encoding the inputs as if they were calls to a `handle_filecoin_method` Solidity method.

## New features
- feat: Add move-partition command ([filecoin-project/lotus#11290](https://github.com/filecoin-project/lotus/pull/11290))
Expand Down
24 changes: 24 additions & 0 deletions itests/eth_hash_lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,18 @@ func TestTransactionHashLookupBlsFilecoinMessage(t *testing.T) {
toEth, err := client.FilecoinAddressToEthAddress(ctx, toId)
require.NoError(t, err)
require.Equal(t, &toEth, chainTx.To)

const expectedHex = "868e10c4" +
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000060" +
"0000000000000000000000000000000000000000000000000000000000000000"

// verify that the params are correctly encoded.
expected, err := hex.DecodeString(expectedHex)
require.NoError(t, err)

require.Equal(t, ethtypes.EthBytes(expected), chainTx.Input)
}

// TestTransactionHashLookupSecpFilecoinMessage tests to see if lotus can find a Secp Filecoin Message using the transaction hash
Expand Down Expand Up @@ -266,6 +278,18 @@ func TestTransactionHashLookupSecpFilecoinMessage(t *testing.T) {
toEth, err := client.FilecoinAddressToEthAddress(ctx, toId)
require.NoError(t, err)
require.Equal(t, &toEth, chainTx.To)

const expectedHex = "868e10c4" +
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000060" +
"0000000000000000000000000000000000000000000000000000000000000000"

// verify that the params are correctly encoded.
expected, err := hex.DecodeString(expectedHex)
require.NoError(t, err)

require.Equal(t, ethtypes.EthBytes(expected), chainTx.Input)
}

// TestTransactionHashLookupSecpFilecoinMessage tests to see if lotus can find a Secp Filecoin Message using the transaction hash
Expand Down
126 changes: 110 additions & 16 deletions itests/eth_transactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import (

"github.com/stretchr/testify/require"

"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
builtin2 "github.com/filecoin-project/go-state-types/builtin"
"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/actors"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
Expand Down Expand Up @@ -78,7 +80,7 @@ func TestValueTransferValidSignature(t *testing.T) {
client.EVM().SignTransaction(&tx, key.PrivateKey)
hash := client.EVM().SubmitTransaction(ctx, &tx)

receipt, err := waitForEthTxReceipt(ctx, client, hash)
receipt, err := client.EVM().WaitTransaction(ctx, hash)
require.NoError(t, err)
require.NotNil(t, receipt)
require.EqualValues(t, ethAddr, receipt.From)
Expand Down Expand Up @@ -166,7 +168,7 @@ func TestContractDeploymentValidSignature(t *testing.T) {
client.EVM().SignTransaction(tx, key.PrivateKey)
hash := client.EVM().SubmitTransaction(ctx, tx)

receipt, err := waitForEthTxReceipt(ctx, client, hash)
receipt, err := client.EVM().WaitTransaction(ctx, hash)
require.NoError(t, err)
require.NotNil(t, receipt)

Expand Down Expand Up @@ -213,7 +215,7 @@ func TestContractInvocation(t *testing.T) {
client.EVM().SignTransaction(tx, key.PrivateKey)
hash := client.EVM().SubmitTransaction(ctx, tx)

receipt, err := waitForEthTxReceipt(ctx, client, hash)
receipt, err := client.EVM().WaitTransaction(ctx, hash)
require.NoError(t, err)
require.NotNil(t, receipt)
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
Expand Down Expand Up @@ -267,7 +269,7 @@ func TestContractInvocation(t *testing.T) {
client.EVM().SignTransaction(&invokeTx, key.PrivateKey)
hash = client.EVM().SubmitTransaction(ctx, &invokeTx)

receipt, err = waitForEthTxReceipt(ctx, client, hash)
receipt, err = client.EVM().WaitTransaction(ctx, hash)
require.NoError(t, err)
require.NotNil(t, receipt)

Expand Down Expand Up @@ -376,16 +378,108 @@ func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr eth
}, nil
}

func waitForEthTxReceipt(ctx context.Context, client *kit.TestFullNode, hash ethtypes.EthHash) (*api.EthTxReceipt, error) {
var receipt *api.EthTxReceipt
var err error
for i := 0; i < 10000000000; i++ {
receipt, err = client.EthGetTransactionReceipt(ctx, hash)
if err != nil || receipt == nil {
time.Sleep(500 * time.Millisecond)
continue
}
break
func TestEthTxFromNativeAccount(t *testing.T) {
blockTime := 10 * time.Millisecond
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())

ens.InterconnectAll().BeginMining(blockTime)

ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()

msg := &types.Message{
From: client.DefaultKey.Address,
To: client.DefaultKey.Address,
Value: abi.TokenAmount(types.MustParseFIL("100")),
Method: builtin2.MethodsEVM.InvokeContract,
}
return receipt, err

// Send a message with no input.

sMsg, err := client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err)
client.WaitMsg(ctx, sMsg.Cid())

hash, err := client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
require.NoError(t, err)
tx, err := client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)

// Expect empty input params given that we "invoked" the contract (well, invoked ourselves).
require.Equal(t, ethtypes.EthBytes{}, tx.Input)

// Send a message with some input.

input := abi.CborBytes([]byte{0x1, 0x2, 0x3, 0x4})
msg.Params, err = actors.SerializeParams(&input)
require.NoError(t, err)

sMsg, err = client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err)
client.WaitMsg(ctx, sMsg.Cid())
hash, err = client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
require.NoError(t, err)
tx, err = client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)

// Expect the decoded input.
require.EqualValues(t, input, tx.Input)

// Invoke the contract, but with incorrectly encoded input. We expect this to be abi-encoded
// as if it were any other method call.

msg.Params = input
require.NoError(t, err)

sMsg, err = client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err)
client.WaitMsg(ctx, sMsg.Cid())
hash, err = client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
require.NoError(t, err)
tx, err = client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)

const expectedHex1 = "868e10c4" + // "handle filecoin method" function selector
// InvokeEVM method number
"00000000000000000000000000000000000000000000000000000000e525aa15" +
// CBOR multicodec (0x51)
"0000000000000000000000000000000000000000000000000000000000000051" +
// Offset
"0000000000000000000000000000000000000000000000000000000000000060" +
// Number of bytes in the input (4)
"0000000000000000000000000000000000000000000000000000000000000004" +
// Input: 1, 2, 3, 4
"0102030400000000000000000000000000000000000000000000000000000000"

input, err = hex.DecodeString(expectedHex1)
require.NoError(t, err)
require.EqualValues(t, input, tx.Input)

// Invoke a random method with the same input. We expect the same result as above, but with
// a different method number.

msg.Method++

sMsg, err = client.MpoolPushMessage(ctx, msg, nil)
require.NoError(t, err)
client.WaitMsg(ctx, sMsg.Cid())
hash, err = client.EthGetTransactionHashByCid(ctx, sMsg.Cid())
require.NoError(t, err)
tx, err = client.EthGetTransactionByHash(ctx, hash)
require.NoError(t, err)

const expectedHex2 = "868e10c4" + // "handle filecoin method" function selector
// InvokeEVM+1
"00000000000000000000000000000000000000000000000000000000e525aa16" +
// CBOR multicodec (0x51)
"0000000000000000000000000000000000000000000000000000000000000051" +
// Offset
"0000000000000000000000000000000000000000000000000000000000000060" +
// Number of bytes in the input (4)
"0000000000000000000000000000000000000000000000000000000000000004" +
// Input: 1, 2, 3, 4
"0102030400000000000000000000000000000000000000000000000000000000"
input, err = hex.DecodeString(expectedHex2)
require.NoError(t, err)
require.EqualValues(t, input, tx.Input)
}
42 changes: 15 additions & 27 deletions itests/fevm_address_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package itests

import (
"bytes"
"context"
"encoding/hex"
"os"
Expand All @@ -14,8 +13,6 @@ import (
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
builtintypes "github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
"github.com/filecoin-project/go-state-types/exitcode"

"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/chain/actors"
Expand Down Expand Up @@ -43,7 +40,7 @@ func effectiveEthAddressForCreate(t *testing.T, sender address.Address) ethtypes
panic("unreachable")
}

func createAndDeploy(ctx context.Context, t *testing.T, client *kit.TestFullNode, fromAddr address.Address, contract []byte) *api.MsgLookup {
func createAndDeploy(ctx context.Context, t *testing.T, client *kit.TestFullNode, fromAddr address.Address, contract []byte) *api.EthTxReceipt {
// Create and deploy evm actor

method := builtintypes.MethodsEAM.CreateExternal
Expand All @@ -61,21 +58,13 @@ func createAndDeploy(ctx context.Context, t *testing.T, client *kit.TestFullNode
smsg, err := client.MpoolPushMessage(ctx, createMsg, nil)
require.NoError(t, err)

wait, err := client.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false)
require.NoError(t, err)
require.Equal(t, exitcode.Ok, wait.Receipt.ExitCode)
return wait
}

func getEthAddressTX(ctx context.Context, t *testing.T, client *kit.TestFullNode, wait *api.MsgLookup, ethAddr ethtypes.EthAddress) ethtypes.EthAddress {
// Check if eth address returned from CreateExternal is the same as eth address predicted at the start
var createExternalReturn eam.CreateExternalReturn
err := createExternalReturn.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return))
txHash, err := client.EthGetTransactionHashByCid(ctx, smsg.Cid())
require.NoError(t, err)

createdEthAddr, err := ethtypes.CastEthAddress(createExternalReturn.EthAddress[:])
receipt, err := client.EVM().WaitTransaction(ctx, *txHash)
require.NoError(t, err)
return createdEthAddr
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
return receipt
}

func TestAddressCreationBeforeDeploy(t *testing.T) {
Expand Down Expand Up @@ -112,11 +101,11 @@ func TestAddressCreationBeforeDeploy(t *testing.T) {
require.True(t, builtin.IsPlaceholderActor(actor.Code))

// Create and deploy evm actor
wait := createAndDeploy(ctx, t, client, fromAddr, contract)
receipt := createAndDeploy(ctx, t, client, fromAddr, contract)

// Check if eth address returned from CreateExternal is the same as eth address predicted at the start
createdEthAddr := getEthAddressTX(ctx, t, client, wait, ethAddr)
require.Equal(t, ethAddr, createdEthAddr)
// Check if eth address returned from CreateExternal is the same as eth address predicted at
// the start
require.Equal(t, &ethAddr, receipt.ContractAddress)

// Check if newly deployed actor still has funds
actorPostCreate, err := client.StateGetActor(ctx, contractFilAddr, types.EmptyTSK)
Expand Down Expand Up @@ -158,11 +147,11 @@ func TestDeployAddressMultipleTimes(t *testing.T) {
require.True(t, builtin.IsPlaceholderActor(actor.Code))

// Create and deploy evm actor
wait := createAndDeploy(ctx, t, client, fromAddr, contract)
receipt := createAndDeploy(ctx, t, client, fromAddr, contract)

// Check if eth address returned from CreateExternal is the same as eth address predicted at the start
createdEthAddr := getEthAddressTX(ctx, t, client, wait, ethAddr)
require.Equal(t, ethAddr, createdEthAddr)
// Check if eth address returned from CreateExternal is the same as eth address predicted at
// the start
require.Equal(t, &ethAddr, receipt.ContractAddress)

// Check if newly deployed actor still has funds
actorPostCreate, err := client.StateGetActor(ctx, contractFilAddr, types.EmptyTSK)
Expand All @@ -171,10 +160,9 @@ func TestDeployAddressMultipleTimes(t *testing.T) {
require.True(t, builtin.IsEvmActor(actorPostCreate.Code))

// Create and deploy evm actor
wait = createAndDeploy(ctx, t, client, fromAddr, contract)
receipt = createAndDeploy(ctx, t, client, fromAddr, contract)

// Check that this time eth address returned from CreateExternal is not the same as eth address predicted at the start
createdEthAddr = getEthAddressTX(ctx, t, client, wait, ethAddr)
require.NotEqual(t, ethAddr, createdEthAddr)
require.NotEqual(t, &ethAddr, receipt.ContractAddress)

}
12 changes: 12 additions & 0 deletions itests/kit/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/stretchr/testify/require"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/crypto/sha3"
"golang.org/x/xerrors"

"github.com/filecoin-project/go-address"
amt4 "github.com/filecoin-project/go-amt-ipld/v4"
Expand Down Expand Up @@ -291,6 +292,17 @@ func (e *EVM) InvokeContractByFuncNameExpectExit(ctx context.Context, fromAddr a
require.Equal(e.t, exit, wait.Receipt.ExitCode)
}

func (e *EVM) WaitTransaction(ctx context.Context, hash ethtypes.EthHash) (*api.EthTxReceipt, error) {
if mcid, err := e.EthGetMessageCidByTransactionHash(ctx, &hash); err != nil {
return nil, err
} else if mcid == nil {
return nil, xerrors.Errorf("couldn't find message CID for txn hash: %s", hash)
} else {
e.WaitMsg(ctx, *mcid)
return e.EthGetTransactionReceipt(ctx, hash)
}
}

// function signatures are the first 4 bytes of the hash of the function name and types
func CalcFuncSignature(funcName string) []byte {
hasher := sha3.NewLegacyKeccak256()
Expand Down
Loading