391 lines
12 KiB
Go
391 lines
12 KiB
Go
package itests
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/filecoin-project/go-state-types/big"
|
|
"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/store"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
|
"github.com/filecoin-project/lotus/itests/kit"
|
|
)
|
|
|
|
func TestValueTransferValidSignature(t *testing.T) {
|
|
blockTime := 100 * 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()
|
|
|
|
// 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()
|
|
_, ethAddr2, _ := client.EVM().NewAccount()
|
|
|
|
kit.SendFunds(ctx, t, client, deployer, types.FromFil(1000))
|
|
|
|
gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{
|
|
From: ðAddr,
|
|
Data: contract,
|
|
})
|
|
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: ðAddr2,
|
|
MaxFeePerGas: types.NanoFil,
|
|
MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas),
|
|
GasLimit: int(gaslimit),
|
|
V: big.Zero(),
|
|
R: big.Zero(),
|
|
S: big.Zero(),
|
|
}
|
|
|
|
client.EVM().SignTransaction(&tx, key.PrivateKey)
|
|
// Mangle signature
|
|
tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int)
|
|
|
|
signed, err := tx.ToRlpSignedMsg()
|
|
require.NoError(t, err)
|
|
// Submit transaction with bad signature
|
|
_, err = client.EVM().EthSendRawTransaction(ctx, signed)
|
|
require.Error(t, err)
|
|
|
|
// Submit transaction with valid signature
|
|
client.EVM().SignTransaction(&tx, key.PrivateKey)
|
|
hash := client.EVM().SubmitTransaction(ctx, &tx)
|
|
|
|
receipt, err := waitForEthTxReceipt(ctx, client, hash)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, receipt)
|
|
require.EqualValues(t, ethAddr, receipt.From)
|
|
require.EqualValues(t, ethAddr2, *receipt.To)
|
|
require.EqualValues(t, hash, receipt.TransactionHash)
|
|
|
|
// Success.
|
|
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
|
|
|
|
// Validate that we sent the expected transaction.
|
|
ethTx, err := client.EthGetTransactionByHash(ctx, &hash)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, ethAddr, ethTx.From)
|
|
require.EqualValues(t, ethAddr2, *ethTx.To)
|
|
require.EqualValues(t, tx.ChainID, ethTx.ChainID)
|
|
require.EqualValues(t, tx.Nonce, ethTx.Nonce)
|
|
require.EqualValues(t, hash, ethTx.Hash)
|
|
require.EqualValues(t, tx.Value, ethTx.Value)
|
|
require.EqualValues(t, 2, ethTx.Type)
|
|
require.EqualValues(t, ethtypes.EthBytes{}, ethTx.Input)
|
|
require.EqualValues(t, tx.GasLimit, ethTx.Gas)
|
|
require.EqualValues(t, tx.MaxFeePerGas, ethTx.MaxFeePerGas)
|
|
require.EqualValues(t, tx.MaxPriorityFeePerGas, ethTx.MaxPriorityFeePerGas)
|
|
require.EqualValues(t, tx.V, ethTx.V)
|
|
require.EqualValues(t, tx.R, ethTx.R)
|
|
require.EqualValues(t, tx.S, ethTx.S)
|
|
}
|
|
|
|
func TestLegacyTransaction(t *testing.T) {
|
|
blockTime := 100 * 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()
|
|
|
|
// This is a legacy style transaction obtained from etherscan
|
|
// Tx details: https://etherscan.io/getRawTx?tx=0x0763262208d89efeeb50c8bb05b50c537903fe9d7bdef3b223fd1f5f69f69b32
|
|
txBytes, err := hex.DecodeString("f86f830131cf8504a817c800825208942cf1e5a8250ded8835694ebeb90cfa0237fcb9b1882ec4a5251d1100008026a0f5f8d2244d619e211eeb634acd1bea0762b7b4c97bba9f01287c82bfab73f911a015be7982898aa7cc6c6f27ff33e999e4119d6cd51330353474b98067ff56d930")
|
|
require.NoError(t, err)
|
|
_, err = client.EVM().EthSendRawTransaction(ctx, txBytes)
|
|
require.ErrorContains(t, err, "legacy transaction is not supported")
|
|
}
|
|
|
|
func TestContractDeploymentValidSignature(t *testing.T) {
|
|
blockTime := 100 * 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()
|
|
|
|
// 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 the deployer address is a placeholder.
|
|
client.AssertActorType(ctx, deployer, manifest.PlaceholderKey)
|
|
|
|
tx, err := deployContractTx(ctx, client, ethAddr, contract)
|
|
require.NoError(t, err)
|
|
|
|
client.EVM().SignTransaction(tx, key.PrivateKey)
|
|
// Mangle signature
|
|
tx.V.Int.Xor(tx.V.Int, big.NewInt(1).Int)
|
|
|
|
signed, err := tx.ToRlpSignedMsg()
|
|
require.NoError(t, err)
|
|
// Submit transaction with bad signature
|
|
_, err = client.EVM().EthSendRawTransaction(ctx, signed)
|
|
require.Error(t, err)
|
|
|
|
// Submit transaction with valid signature
|
|
client.EVM().SignTransaction(tx, key.PrivateKey)
|
|
hash := client.EVM().SubmitTransaction(ctx, tx)
|
|
|
|
receipt, err := waitForEthTxReceipt(ctx, client, hash)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, receipt)
|
|
|
|
// Success.
|
|
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
|
|
|
|
// 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)
|
|
}
|
|
|
|
func TestContractInvocation(t *testing.T) {
|
|
blockTime := 100 * 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()
|
|
|
|
// 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))
|
|
|
|
// DEPLOY CONTRACT
|
|
tx, err := deployContractTx(ctx, client, ethAddr, contract)
|
|
require.NoError(t, err)
|
|
|
|
client.EVM().SignTransaction(tx, key.PrivateKey)
|
|
hash := client.EVM().SubmitTransaction(ctx, tx)
|
|
|
|
receipt, err := waitForEthTxReceipt(ctx, client, hash)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, receipt)
|
|
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
|
|
|
|
// Get contract address.
|
|
contractAddr := client.EVM().ComputeContractAddress(ethAddr, 0)
|
|
|
|
// INVOKE CONTRACT
|
|
|
|
// Params
|
|
// entry point for getBalance - f8b2cb4f
|
|
// address - ff00000000000000000000000000000000000064
|
|
params, err := hex.DecodeString("f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064")
|
|
require.NoError(t, err)
|
|
|
|
gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{
|
|
From: ðAddr,
|
|
To: &contractAddr,
|
|
Data: params,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
|
|
require.NoError(t, err)
|
|
|
|
invokeTx := ethtypes.EthTxArgs{
|
|
ChainID: build.Eip155ChainId,
|
|
To: &contractAddr,
|
|
Value: big.Zero(),
|
|
Nonce: 1,
|
|
MaxFeePerGas: types.NanoFil,
|
|
MaxPriorityFeePerGas: big.Int(maxPriorityFeePerGas),
|
|
GasLimit: int(gaslimit),
|
|
Input: params,
|
|
V: big.Zero(),
|
|
R: big.Zero(),
|
|
S: big.Zero(),
|
|
}
|
|
|
|
client.EVM().SignTransaction(&invokeTx, key.PrivateKey)
|
|
// Mangle signature
|
|
invokeTx.V.Int.Xor(invokeTx.V.Int, big.NewInt(1).Int)
|
|
|
|
signed, err := invokeTx.ToRlpSignedMsg()
|
|
require.NoError(t, err)
|
|
// Submit transaction with bad signature
|
|
_, err = client.EVM().EthSendRawTransaction(ctx, signed)
|
|
require.Error(t, err)
|
|
|
|
// Submit transaction with valid signature
|
|
client.EVM().SignTransaction(&invokeTx, key.PrivateKey)
|
|
hash = client.EVM().SubmitTransaction(ctx, &invokeTx)
|
|
|
|
receipt, err = waitForEthTxReceipt(ctx, client, hash)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, receipt)
|
|
|
|
// Success.
|
|
require.EqualValues(t, ethtypes.EthUint64(0x1), receipt.Status)
|
|
|
|
// Validate that we correctly computed the gas outputs.
|
|
mCid, err := client.EthGetMessageCidByTransactionHash(ctx, &hash)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, mCid)
|
|
|
|
invokResult, err := client.StateReplay(ctx, types.EmptyTSK, *mCid)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, invokResult.GasCost.GasUsed, big.NewInt(int64(receipt.GasUsed)))
|
|
effectiveGasPrice := big.Div(invokResult.GasCost.TotalCost, invokResult.GasCost.GasUsed)
|
|
require.EqualValues(t, effectiveGasPrice, big.Int(receipt.EffectiveGasPrice))
|
|
}
|
|
|
|
func TestGetBlockByNumber(t *testing.T) {
|
|
blockTime := 100 * time.Millisecond
|
|
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
|
|
|
|
bms := ens.InterconnectAll().BeginMining(blockTime)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
|
defer cancel()
|
|
|
|
// create a new Ethereum account
|
|
_, ethAddr, filAddr := client.EVM().NewAccount()
|
|
// send some funds to the f410 address
|
|
kit.SendFunds(ctx, t, client, filAddr, types.FromFil(10))
|
|
|
|
latest, err := client.EthBlockNumber(ctx)
|
|
require.NoError(t, err)
|
|
|
|
// can get the latest block
|
|
_, err = client.EthGetBlockByNumber(ctx, latest.Hex(), true)
|
|
require.NoError(t, err)
|
|
|
|
// fail to get a future block
|
|
_, err = client.EthGetBlockByNumber(ctx, (latest + 10000).Hex(), true)
|
|
require.Error(t, err)
|
|
|
|
// inject 10 null rounds
|
|
bms[0].InjectNulls(10)
|
|
|
|
// wait until we produce blocks again
|
|
tctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
|
defer cancel()
|
|
ch, err := client.ChainNotify(tctx)
|
|
require.NoError(t, err)
|
|
<-ch // current
|
|
hc := <-ch // wait for next block
|
|
require.Equal(t, store.HCApply, hc[0].Type)
|
|
|
|
afterNullHeight := hc[0].Val.Height()
|
|
|
|
nullHeight := afterNullHeight - 1
|
|
for nullHeight > 0 {
|
|
ts, err := client.ChainGetTipSetByHeight(ctx, nullHeight, types.EmptyTSK)
|
|
require.NoError(t, err)
|
|
if ts.Height() == nullHeight {
|
|
nullHeight--
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Fail when trying to fetch a null round.
|
|
_, err = client.EthGetBlockByNumber(ctx, (ethtypes.EthUint64(nullHeight)).Hex(), true)
|
|
require.Error(t, err)
|
|
|
|
// Fetch balance on a null round; should not fail and should return previous balance.
|
|
bal, err := client.EthGetBalance(ctx, ethAddr, ethtypes.NewEthBlockNumberOrHashFromNumber(ethtypes.EthUint64(nullHeight)))
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, big.Zero(), bal)
|
|
require.Equal(t, types.FromFil(10).Int, bal.Int)
|
|
}
|
|
|
|
func deployContractTx(ctx context.Context, client *kit.TestFullNode, ethAddr ethtypes.EthAddress, contract []byte) (*ethtypes.EthTxArgs, error) {
|
|
gaslimit, err := client.EthEstimateGas(ctx, ethtypes.EthCall{
|
|
From: ðAddr,
|
|
Data: contract,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
maxPriorityFeePerGas, err := client.EthMaxPriorityFeePerGas(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// now deploy a contract from the embryo, and validate it went well
|
|
return ðtypes.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(),
|
|
}, 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
|
|
}
|
|
return receipt, err
|
|
}
|