diff --git a/.circleci/config.yml b/.circleci/config.yml index 5b3a56c7c..d30b4776b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -774,6 +774,11 @@ workflows: suite: itest-eth_filter target: "./itests/eth_filter_test.go" + - test: + name: test-itest-fevm_address + suite: itest-fevm_address + target: "./itests/fevm_address_test.go" + - test: name: test-itest-fevm_events suite: itest-fevm_events diff --git a/chain/actors/builtin/builtin.go b/chain/actors/builtin/builtin.go index 8ab188317..093940b7c 100644 --- a/chain/actors/builtin/builtin.go +++ b/chain/actors/builtin/builtin.go @@ -277,7 +277,16 @@ func IsPaymentChannelActor(c cid.Cid) bool { func IsEmbryoActor(c cid.Cid) bool { name, _, ok := actors.GetActorMetaByCode(c) if ok { - return name == "embryo" + return name == manifest.EmbryoKey + } + + return false +} + +func IsEvmActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EvmKey } return false @@ -286,7 +295,7 @@ func IsEmbryoActor(c cid.Cid) bool { func IsEthAccountActor(c cid.Cid) bool { name, _, ok := actors.GetActorMetaByCode(c) if ok { - return name == "ethaccount" + return name == manifest.EthAccountKey } return false diff --git a/chain/actors/builtin/builtin.go.template b/chain/actors/builtin/builtin.go.template index 5cd9b1f7f..8b2b67645 100644 --- a/chain/actors/builtin/builtin.go.template +++ b/chain/actors/builtin/builtin.go.template @@ -156,7 +156,16 @@ func IsPaymentChannelActor(c cid.Cid) bool { func IsEmbryoActor(c cid.Cid) bool { name, _, ok := actors.GetActorMetaByCode(c) if ok { - return name == "embryo" + return name == manifest.EmbryoKey + } + + return false +} + +func IsEvmActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EvmKey } return false @@ -165,7 +174,7 @@ func IsEmbryoActor(c cid.Cid) bool { func IsEthAccountActor(c cid.Cid) bool { name, _, ok := actors.GetActorMetaByCode(c) if ok { - return name == "ethaccount" + return name == manifest.EthAccountKey } return false diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index cb9535615..0b100afc6 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -14,6 +14,7 @@ import ( "github.com/minio/blake2b-simd" "github.com/multiformats/go-multihash" "github.com/multiformats/go-varint" + "golang.org/x/crypto/sha3" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" @@ -601,3 +602,22 @@ type EthSubscriptionResponse struct { // The object matching the subscription. This may be a Block (tipset), a Transaction (message) or an EthLog Result interface{} `json:"result"` } + +func GetContractEthAddressFromCode(sender EthAddress, salt [32]byte, initcode []byte) (EthAddress, error) { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(initcode) + inithash := hasher.Sum(nil) + + hasher.Reset() + hasher.Write([]byte{0xff}) + hasher.Write(sender[:]) + hasher.Write(salt[:]) + hasher.Write(inithash) + + ethAddr, err := EthAddressFromBytes(hasher.Sum(nil)[12:]) + if err != nil { + return [20]byte{}, err + } + + return ethAddr, nil +} diff --git a/cli/eth.go b/cli/eth.go index e977e29f6..9262c6252 100644 --- a/cli/eth.go +++ b/cli/eth.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "fmt" + "os" "github.com/urfave/cli/v2" "golang.org/x/xerrors" @@ -21,6 +22,7 @@ var EthCmd = &cli.Command{ Subcommands: []*cli.Command{ EthGetInfoCmd, EthCallSimulateCmd, + EthGetContractAddress, }, } @@ -137,6 +139,56 @@ var EthCallSimulateCmd = &cli.Command{ }, } +var EthGetContractAddress = &cli.Command{ + Name: "contract-address", + Usage: "Generate contract address from smart contract code", + ArgsUsage: "[senderEthAddr] [salt] [contractHexPath]", + Action: func(cctx *cli.Context) error { + + if cctx.NArg() != 3 { + return IncorrectNumArgs(cctx) + } + + sender, err := ethtypes.EthAddressFromHex(cctx.Args().Get(0)) + if err != nil { + return err + } + + salt, err := hex.DecodeString(cctx.Args().Get(1)) + if err != nil { + return xerrors.Errorf("Could not decode salt: %w", err) + } + if len(salt) > 32 { + return xerrors.Errorf("Len of salt bytes greater than 32") + } + var fsalt [32]byte + copy(fsalt[:], salt[:]) + + contractBin := cctx.Args().Get(2) + if err != nil { + return err + } + contractHex, err := os.ReadFile(contractBin) + if err != nil { + + return err + } + contract, err := hex.DecodeString(string(contractHex)) + if err != nil { + return xerrors.Errorf("Could not decode contract file: %w", err) + } + + contractAddr, err := ethtypes.GetContractEthAddressFromCode(sender, fsalt, contract) + if err != nil { + return err + } + + fmt.Println("Contract Eth address: ", contractAddr) + + return nil + }, +} + func ethAddrFromFilecoinAddress(ctx context.Context, addr address.Address, fnapi v0api.FullNode) (ethtypes.EthAddress, address.Address, error) { var faddr address.Address var err error diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index e5c27f9b2..232f80baa 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -2579,9 +2579,10 @@ USAGE: lotus eth command [command options] [arguments...] COMMANDS: - stat Print eth/filecoin addrs and code cid - call Simulate an eth contract call - help, h Shows a list of commands or help for one command + stat Print eth/filecoin addrs and code cid + call Simulate an eth contract call + contract-address Generate contract address from smart contract code + help, h Shows a list of commands or help for one command OPTIONS: --help, -h show help (default: false) @@ -2615,6 +2616,19 @@ OPTIONS: ``` +### lotus eth contract-address +``` +NAME: + lotus eth contract-address - Generate contract address from smart contract code + +USAGE: + lotus eth contract-address [command options] [senderEthAddr] [salt] [contractHexPath] + +OPTIONS: + --help, -h show help (default: false) + +``` + ## lotus net ``` NAME: diff --git a/itests/fevm_address_test.go b/itests/fevm_address_test.go new file mode 100644 index 000000000..356328bef --- /dev/null +++ b/itests/fevm_address_test.go @@ -0,0 +1,123 @@ +package itests + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "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 TestAddressCreationBeforeDeploy(t *testing.T) { + kit.QuietMiningLogs() + + 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.bin") + require.NoError(t, err) + + contract, err := hex.DecodeString(string(contractHex)) + require.NoError(t, err) + + fromAddr, err := client.WalletDefaultAddress(ctx) + require.NoError(t, err) + fromId, err := client.StateLookupID(ctx, fromAddr, types.EmptyTSK) + require.NoError(t, err) + + senderEthAddr, err := ethtypes.EthAddressFromFilecoinAddress(fromId) + require.NoError(t, err) + + var salt [32]byte + binary.BigEndian.PutUint64(salt[:], 1) + + // Generate contract address before actually deploying contract + ethAddr, err := ethtypes.GetContractEthAddressFromCode(senderEthAddr, salt, contract) + require.NoError(t, err) + + contractFilAddr, err := ethAddr.ToFilecoinAddress() + require.NoError(t, err) + + // Send contract address some funds + + bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) + require.NoError(t, err) + sendAmount := big.Div(bal, big.NewInt(2)) + + sendMsg := &types.Message{ + From: fromAddr, + To: contractFilAddr, + Value: sendAmount, + } + signedMsg, err := client.MpoolPushMessage(ctx, sendMsg, nil) + require.NoError(t, err) + mLookup, err := client.StateWaitMsg(ctx, signedMsg.Cid(), 3, api.LookbackNoLimit, true) + require.NoError(t, err) + require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) + + // Check if actor at new address is an embryo actor + actor, err := client.StateGetActor(ctx, contractFilAddr, types.EmptyTSK) + require.NoError(t, err) + require.True(t, builtin.IsEmbryoActor(actor.Code)) + + // Create and deploy evm actor + + method := builtintypes.MethodsEAM.Create2 + params, err := actors.SerializeParams(&eam.Create2Params{ + Initcode: contract, + Salt: salt, + }) + require.NoError(t, err) + + 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) + + // Check if eth address returned from Create2 is the same as eth address predicted at the start + var create2Return eam.Create2Return + err = create2Return.UnmarshalCBOR(bytes.NewReader(wait.Receipt.Return)) + require.NoError(t, err) + + createdEthAddr, err := ethtypes.EthAddressFromBytes(create2Return.EthAddress[:]) + require.NoError(t, err) + 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)) + +}