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:
parent
d9c13f19b0
commit
2bdae2f444
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
52
cli/eth.go
52
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
|
||||
|
@ -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:
|
||||
|
123
itests/fevm_address_test.go
Normal file
123
itests/fevm_address_test.go
Normal 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))
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user