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
|
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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
52
cli/eth.go
52
cli/eth.go
@ -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
|
||||||
|
@ -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
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