package itests import ( "bytes" "context" "encoding/hex" "os" "testing" "github.com/stretchr/testify/require" "golang.org/x/crypto/sha3" "github.com/filecoin-project/go-address" "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" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/itests/kit" ) func effectiveEthAddressForCreate(t *testing.T, sender address.Address) ethtypes.EthAddress { switch sender.Protocol() { case address.SECP256K1, address.BLS: hasher := sha3.NewLegacyKeccak256() hasher.Write(sender.Bytes()) addr, err := ethtypes.CastEthAddress(hasher.Sum(nil)[12:]) require.NoError(t, err) return addr case address.Delegated: addr, err := ethtypes.EthAddressFromFilecoinAddress(sender) require.NoError(t, err) return addr default: require.FailNow(t, "unsupported protocol %d", sender.Protocol()) } panic("unreachable") } func createAndDeploy(ctx context.Context, t *testing.T, client *kit.TestFullNode, fromAddr address.Address, contract []byte) *api.MsgLookup { // Create and deploy evm actor method := builtintypes.MethodsEAM.CreateExternal contractParams := abi.CborBytes(contract) params, actorsErr := actors.SerializeParams(&contractParams) require.NoError(t, actorsErr) createMsg := &types.Message{ To: builtintypes.EthereumAddressManagerActorAddr, From: fromAddr, Value: big.Zero(), Method: method, Params: params, } 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)) require.NoError(t, err) createdEthAddr, err := ethtypes.CastEthAddress(createExternalReturn.EthAddress[:]) require.NoError(t, err) return createdEthAddr } func TestAddressCreationBeforeDeploy(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) 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) fromAddr, err := client.WalletDefaultAddress(ctx) require.NoError(t, err) // We hash the f1/f3 address into the EVM's address space when deploying contracts from // accounts. effectiveEvmAddress := effectiveEthAddressForCreate(t, fromAddr) ethAddr := client.EVM().ComputeContractAddress(effectiveEvmAddress, 1) contractFilAddr, err := ethAddr.ToFilecoinAddress() require.NoError(t, err) //transfer half the wallet balance bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) require.NoError(t, err) sendAmount := big.Div(bal, big.NewInt(2)) client.EVM().TransferValueOrFail(ctx, fromAddr, contractFilAddr, sendAmount) // Check if actor at new address is a placeholder actor actor, err := client.StateGetActor(ctx, contractFilAddr, types.EmptyTSK) require.NoError(t, err) require.True(t, builtin.IsPlaceholderActor(actor.Code)) // Create and deploy evm actor wait := 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 newly deployed actor still has funds actorPostCreate, err := client.StateGetActor(ctx, contractFilAddr, types.EmptyTSK) require.NoError(t, err) require.Equal(t, actorPostCreate.Balance, sendAmount) require.True(t, builtin.IsEvmActor(actorPostCreate.Code)) } func TestDeployAddressMultipleTimes(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) 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) fromAddr, err := client.WalletDefaultAddress(ctx) require.NoError(t, err) // We hash the f1/f3 address into the EVM's address space when deploying contracts from // accounts. effectiveEvmAddress := effectiveEthAddressForCreate(t, fromAddr) ethAddr := client.EVM().ComputeContractAddress(effectiveEvmAddress, 1) contractFilAddr, err := ethAddr.ToFilecoinAddress() require.NoError(t, err) // Send contract address small funds to init sendAmount := big.NewInt(2) client.EVM().TransferValueOrFail(ctx, fromAddr, contractFilAddr, sendAmount) // Check if actor at new address is a placeholder actor actor, err := client.StateGetActor(ctx, contractFilAddr, types.EmptyTSK) require.NoError(t, err) require.True(t, builtin.IsPlaceholderActor(actor.Code)) // Create and deploy evm actor wait := 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 newly deployed actor still has funds actorPostCreate, err := client.StateGetActor(ctx, contractFilAddr, types.EmptyTSK) require.NoError(t, err) require.Equal(t, actorPostCreate.Balance, sendAmount) require.True(t, builtin.IsEvmActor(actorPostCreate.Code)) // Create and deploy evm actor wait = 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) }