test: Add f4 address integration test and cli to generate eth address from code (#9924)

* Add f4 integration test and cli to generate eth addr from code

* make gen and docsgen

* fix lint

* address comments

* make gen and make docsgen sigh

* address moar comments

* use existing APIs to determine actor types

* Add IsEvmActor API

* "strings are bad" -Geoff
This commit is contained in:
Shrenuj Bansal 2022-12-21 12:51:22 -05:00 committed by GitHub
parent d9c13f19b0
commit 2bdae2f444
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 239 additions and 7 deletions

View File

@ -774,6 +774,11 @@ workflows:
suite: itest-eth_filter suite: itest-eth_filter
target: "./itests/eth_filter_test.go" target: "./itests/eth_filter_test.go"
- test:
name: test-itest-fevm_address
suite: itest-fevm_address
target: "./itests/fevm_address_test.go"
- test: - test:
name: test-itest-fevm_events name: test-itest-fevm_events
suite: itest-fevm_events suite: itest-fevm_events

View File

@ -277,7 +277,16 @@ func IsPaymentChannelActor(c cid.Cid) bool {
func IsEmbryoActor(c cid.Cid) bool { func IsEmbryoActor(c cid.Cid) bool {
name, _, ok := actors.GetActorMetaByCode(c) name, _, ok := actors.GetActorMetaByCode(c)
if ok { 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 return false
@ -286,7 +295,7 @@ func IsEmbryoActor(c cid.Cid) bool {
func IsEthAccountActor(c cid.Cid) bool { func IsEthAccountActor(c cid.Cid) bool {
name, _, ok := actors.GetActorMetaByCode(c) name, _, ok := actors.GetActorMetaByCode(c)
if ok { if ok {
return name == "ethaccount" return name == manifest.EthAccountKey
} }
return false return false

View File

@ -156,7 +156,16 @@ func IsPaymentChannelActor(c cid.Cid) bool {
func IsEmbryoActor(c cid.Cid) bool { func IsEmbryoActor(c cid.Cid) bool {
name, _, ok := actors.GetActorMetaByCode(c) name, _, ok := actors.GetActorMetaByCode(c)
if ok { 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 return false
@ -165,7 +174,7 @@ func IsEmbryoActor(c cid.Cid) bool {
func IsEthAccountActor(c cid.Cid) bool { func IsEthAccountActor(c cid.Cid) bool {
name, _, ok := actors.GetActorMetaByCode(c) name, _, ok := actors.GetActorMetaByCode(c)
if ok { if ok {
return name == "ethaccount" return name == manifest.EthAccountKey
} }
return false return false

View File

@ -14,6 +14,7 @@ import (
"github.com/minio/blake2b-simd" "github.com/minio/blake2b-simd"
"github.com/multiformats/go-multihash" "github.com/multiformats/go-multihash"
"github.com/multiformats/go-varint" "github.com/multiformats/go-varint"
"golang.org/x/crypto/sha3"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/filecoin-project/go-address" "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 // The object matching the subscription. This may be a Block (tipset), a Transaction (message) or an EthLog
Result interface{} `json:"result"` 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
}

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"os"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@ -21,6 +22,7 @@ var EthCmd = &cli.Command{
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
EthGetInfoCmd, EthGetInfoCmd,
EthCallSimulateCmd, 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) { func ethAddrFromFilecoinAddress(ctx context.Context, addr address.Address, fnapi v0api.FullNode) (ethtypes.EthAddress, address.Address, error) {
var faddr address.Address var faddr address.Address
var err error var err error

View File

@ -2579,9 +2579,10 @@ USAGE:
lotus eth command [command options] [arguments...] lotus eth command [command options] [arguments...]
COMMANDS: COMMANDS:
stat Print eth/filecoin addrs and code cid stat Print eth/filecoin addrs and code cid
call Simulate an eth contract call call Simulate an eth contract call
help, h Shows a list of commands or help for one command contract-address Generate contract address from smart contract code
help, h Shows a list of commands or help for one command
OPTIONS: OPTIONS:
--help, -h show help (default: false) --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 ## lotus net
``` ```
NAME: NAME:

123
itests/fevm_address_test.go Normal file
View File

@ -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))
}