lotus/itests/eth_deploy_test.go
2023-11-29 10:26:22 +00:00

262 lines
8.1 KiB
Go

package itests
import (
"context"
"encoding/hex"
"encoding/json"
"os"
"reflect"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/sha3"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/manifest"
gstStore "github.com/filecoin-project/go-state-types/store"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/actors/builtin/evm"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
"github.com/filecoin-project/lotus/itests/kit"
)
// TestDeployment smoke tests the deployment of a contract via the
// Ethereum JSON-RPC endpoint, from an EEOA.
func TestDeployment(t *testing.T) {
blockTime := 100 * time.Millisecond
client, _, ens := kit.EnsembleMinimal(
t,
kit.MockProofs(),
kit.ThroughRPC())
miners := ens.InterconnectAll().BeginMining(blockTime)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
// install contract
contractHex, err := os.ReadFile("./contracts/SimpleCoin.hex")
require.NoError(t, err)
contract, err := hex.DecodeString(string(contractHex))
require.NoError(t, err)
// create a new Ethereum account
key, ethAddr, deployer := client.EVM().NewAccount()
// send some funds to the f410 address
kit.SendFunds(ctx, t, client, deployer, types.FromFil(10))
// verify balances.
bal := client.EVM().AssertAddressBalanceConsistent(ctx, deployer)
require.Equal(t, types.FromFil(10), bal)
// verify the deployer address is an Placeholder.
client.AssertActorType(ctx, deployer, manifest.PlaceholderKey)
gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{
From: &ethAddr,
Data: contract,
}})
require.NoError(t, err)
gaslimit, err := client.EthEstimateGas(ctx, gasParams)
require.NoError(t, err)
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
require.NoError(t, err)
// now deploy a contract from the placeholder, and validate it went well
tx := ethtypes.EthTxArgs{
ChainID: build.Eip155ChainId,
Value: big.Zero(),
Nonce: 0,
MaxFeePerGas: types.NanoFil,
MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas),
GasLimit: int(gaslimit),
Input: contract,
V: big.Zero(),
R: big.Zero(),
S: big.Zero(),
}
client.EVM().SignTransaction(&tx, key.PrivateKey)
pendingFilter, err := client.EthNewPendingTransactionFilter(ctx)
require.NoError(t, err)
// Pause so we can test that everything works while the message is in the message pool.
for _, miner := range miners {
miner.Pause()
}
hash := client.EVM().SubmitTransaction(ctx, &tx)
mpoolTx, err := client.EthGetTransactionByHash(ctx, &hash)
require.NoError(t, err)
require.NotNil(t, mpoolTx)
// require that the hashes are identical
require.Equal(t, hash, mpoolTx.Hash)
// these fields should be nil because the tx hasn't landed on chain.
require.Nil(t, mpoolTx.BlockNumber)
require.Nil(t, mpoolTx.BlockHash)
require.Nil(t, mpoolTx.TransactionIndex)
// We should be able to get the message CID immediately.
mCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash)
require.NoError(t, err)
require.NotNil(t, mCid)
// ... and it should map back to the transaction hash.
mHash, err := client.EthGetTransactionHashByCid(ctx, *mCid)
require.NoError(t, err)
require.NotNil(t, mHash)
require.Equal(t, hash, *mHash)
changes, err := client.EthGetFilterChanges(ctx, pendingFilter)
require.NoError(t, err)
require.Len(t, changes.Results, 1)
require.Equal(t, hash.String(), changes.Results[0])
// Unpause mining.
for _, miner := range miners {
miner.Restart()
}
// Wait for the message to land.
_, err = client.StateWaitMsg(ctx, *mCid, 3, api.LookbackNoLimit, false)
require.NoError(t, err)
// Then lookup the receipt.
receipt, err := client.EthGetTransactionReceipt(ctx, hash)
require.NoError(t, err)
require.NotNil(t, receipt)
// logs must be an empty array, not a nil value, to avoid tooling compatibility issues
require.Empty(t, receipt.Logs)
// a correctly formed logs bloom, albeit empty, has 256 zeroes
require.Len(t, receipt.LogsBloom, 256)
require.Equal(t, ethtypes.EthBytes(make([]byte, 256)), receipt.LogsBloom)
// Success.
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
// Verify that the chain transaction now has new fields set.
chainTx, err := client.EthGetTransactionByHash(ctx, &hash)
require.NoError(t, err)
// require that the hashes are identical
require.Equal(t, hash, chainTx.Hash)
require.NotNil(t, chainTx.BlockNumber)
require.Greater(t, uint64(*chainTx.BlockNumber), uint64(0))
require.NotNil(t, chainTx.BlockHash)
require.NotEmpty(t, *chainTx.BlockHash)
require.NotNil(t, chainTx.TransactionIndex)
require.Equal(t, uint64(*chainTx.TransactionIndex), uint64(0)) // only transaction
// should return error with non-existent block hash
nonExistentHash, err := ethtypes.ParseEthHash("0x62a80aa9262a3e1d3db0706af41c8535257b6275a283174cabf9d108d8946059")
require.Nil(t, err)
_, err = client.EthGetBlockByHash(ctx, nonExistentHash, false)
require.NotNil(t, err)
// verify block information
block1, err := client.EthGetBlockByHash(ctx, *chainTx.BlockHash, false)
require.Nil(t, err)
require.Equal(t, block1.Hash, *chainTx.BlockHash)
require.Equal(t, block1.Number, *chainTx.BlockNumber)
for _, tx := range block1.Transactions {
_, ok := tx.(string)
require.True(t, ok)
}
require.Contains(t, block1.Transactions, hash.String())
// make sure the block got from EthGetBlockByNumber is the same
blkNum := strconv.FormatInt(int64(*chainTx.BlockNumber), 10)
block2, err := client.EthGetBlockByNumber(ctx, blkNum, false)
require.Nil(t, err)
require.True(t, reflect.DeepEqual(block1, block2))
// verify that the block contains full tx objects
block3, err := client.EthGetBlockByHash(ctx, *chainTx.BlockHash, true)
require.Nil(t, err)
require.Equal(t, block3.Hash, *chainTx.BlockHash)
require.Equal(t, block3.Number, *chainTx.BlockNumber)
// the call went through json-rpc and the response was unmarshaled
// into map[string]interface{}, so it has to be converted into ethtypes.EthTx
var foundTx *ethtypes.EthTx
for _, obj := range block3.Transactions {
j, err := json.Marshal(obj)
require.Nil(t, err)
var tx ethtypes.EthTx
err = json.Unmarshal(j, &tx)
require.Nil(t, err)
if tx.Hash == chainTx.Hash {
foundTx = &tx
}
}
require.NotNil(t, foundTx)
require.True(t, reflect.DeepEqual(*foundTx, *chainTx))
// make sure the _full_ block got from EthGetBlockByNumber is the same
block4, err := client.EthGetBlockByNumber(ctx, blkNum, true)
require.Nil(t, err)
require.True(t, reflect.DeepEqual(block3, block4))
// Verify that the deployer is now an account.
client.AssertActorType(ctx, deployer, manifest.EthAccountKey)
// Verify that the nonce was incremented.
nonce, err := client.MpoolGetNonce(ctx, deployer)
require.NoError(t, err)
require.EqualValues(t, 1, nonce)
// Verify that the deployer is now an account.
client.AssertActorType(ctx, deployer, manifest.EthAccountKey)
// Get contract address.
contractAddr, err := client.EVM().ComputeContractAddress(ethAddr, 0).ToFilecoinAddress()
require.NoError(t, err)
client.AssertActorType(ctx, contractAddr, "evm")
// Check bytecode and bytecode hash match.
contractAct, err := client.StateGetActor(ctx, contractAddr, types.EmptyTSK)
require.NoError(t, err)
bs := blockstore.NewAPIBlockstore(client)
ctxStore := gstStore.WrapBlockStore(ctx, bs)
evmSt, err := evm.Load(ctxStore, contractAct)
require.NoError(t, err)
byteCodeCid, err := evmSt.GetBytecodeCID()
require.NoError(t, err)
byteCode, err := bs.Get(ctx, byteCodeCid)
require.NoError(t, err)
byteCodeHashChain, err := evmSt.GetBytecodeHash()
require.NoError(t, err)
hasher := sha3.NewLegacyKeccak256()
hasher.Write(byteCode.RawData())
byteCodeHash := hasher.Sum(nil)
require.Equal(t, byteCodeHashChain[:], byteCodeHash)
byteCodeSt, err := evmSt.GetBytecode()
require.NoError(t, err)
require.Equal(t, byteCode.RawData(), byteCodeSt)
}