NV18: Filecoin EVM runtime + Actor Events + EthAccount + EAM + f4 addressing (#9998)
Co-authored-by: Steven Allen <steven@stebalien.com> Co-authored-by: Raul Kripalani <raulk@users.noreply.github.com> Co-authored-by: Kevin Li <ychiaoli18@users.noreply.github.com> Co-authored-by: vyzo <vyzo@hackzen.org> Co-authored-by: Ian Davis <nospam@iandavis.com> Co-authored-by: Aayush Rajasekaran <arajasek94@gmail.com> Co-authored-by: Jiaying Wang <42981373+jennijuju@users.noreply.github.com> Co-authored-by: Jennifer Wang <jiayingw703@gmail.com> Co-authored-by: Geoff Stuart <geoff.vball@gmail.com> Co-authored-by: Shrenuj Bansal <shrenuj.bansal@protocol.ai> Co-authored-by: Shrenuj Bansal <108157875+shrenujbansal@users.noreply.github.com> Co-authored-by: Geoff Stuart <geoffrey.stuart@protocol.ai> Co-authored-by: Aayush Rajasekaran <aayushrajasekaran@Aayushs-MacBook-Pro.local> Co-authored-by: ZenGround0 <5515260+ZenGround0@users.noreply.github.com> Co-authored-by: zenground0 <ZenGround0@users.noreply.github.com>
This commit is contained in:
co-authored by
Steven Allen
Raul Kripalani
Kevin Li
vyzo
Ian Davis
Aayush Rajasekaran
Jiaying Wang
Jennifer Wang
Geoff Stuart
Shrenuj Bansal
Shrenuj Bansal
Geoff Stuart
Aayush Rajasekaran
ZenGround0
zenground0
parent
048ccc71bb
commit
cdf3812e40
+1
-1
@@ -149,7 +149,7 @@ var ChainGetBlock = &cli.Command{
|
||||
recpts, err := api.ChainGetParentReceipts(ctx, bcid)
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
//return xerrors.Errorf("failed to get receipts: %w", err)
|
||||
// return xerrors.Errorf("failed to get receipts: %w", err)
|
||||
}
|
||||
|
||||
cblock := struct {
|
||||
|
||||
@@ -81,6 +81,7 @@ var Commands = []*cli.Command{
|
||||
WithCategory("developer", LogCmd),
|
||||
WithCategory("developer", WaitApiCmd),
|
||||
WithCategory("developer", FetchParamCmd),
|
||||
WithCategory("developer", EvmCmd),
|
||||
WithCategory("network", NetCmd),
|
||||
WithCategory("network", SyncCmd),
|
||||
WithCategory("status", StatusCmd),
|
||||
|
||||
+503
@@ -0,0 +1,503 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
amt4 "github.com/filecoin-project/go-amt-ipld/v4"
|
||||
"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/lotus/api/v0api"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||
)
|
||||
|
||||
var EvmCmd = &cli.Command{
|
||||
Name: "evm",
|
||||
Usage: "Commands related to the Filecoin EVM runtime",
|
||||
Subcommands: []*cli.Command{
|
||||
EvmDeployCmd,
|
||||
EvmInvokeCmd,
|
||||
EvmGetInfoCmd,
|
||||
EvmCallSimulateCmd,
|
||||
EvmGetContractAddress,
|
||||
},
|
||||
}
|
||||
|
||||
var EvmGetInfoCmd = &cli.Command{
|
||||
Name: "stat",
|
||||
Usage: "Print eth/filecoin addrs and code cid",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "filAddr",
|
||||
Usage: "Filecoin address",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ethAddr",
|
||||
Usage: "Ethereum address",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
|
||||
filAddr := cctx.String("filAddr")
|
||||
ethAddr := cctx.String("ethAddr")
|
||||
|
||||
var faddr address.Address
|
||||
var eaddr ethtypes.EthAddress
|
||||
|
||||
api, closer, err := GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
ctx := ReqContext(cctx)
|
||||
|
||||
if filAddr != "" {
|
||||
addr, err := address.NewFromString(filAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
eaddr, faddr, err = ethAddrFromFilecoinAddress(ctx, addr, api)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if ethAddr != "" {
|
||||
eaddr, err = ethtypes.ParseEthAddress(ethAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
faddr, err = eaddr.ToFilecoinAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return xerrors.Errorf("Neither filAddr nor ethAddr specified")
|
||||
}
|
||||
|
||||
actor, err := api.StateGetActor(ctx, faddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Filecoin address: ", faddr)
|
||||
fmt.Println("Eth address: ", eaddr)
|
||||
fmt.Println("Code cid: ", actor.Code.String())
|
||||
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
var EvmCallSimulateCmd = &cli.Command{
|
||||
Name: "call",
|
||||
Usage: "Simulate an eth contract call",
|
||||
ArgsUsage: "[from] [to] [params]",
|
||||
Action: func(cctx *cli.Context) error {
|
||||
|
||||
if cctx.NArg() != 3 {
|
||||
return IncorrectNumArgs(cctx)
|
||||
}
|
||||
|
||||
fromEthAddr, err := ethtypes.ParseEthAddress(cctx.Args().Get(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toEthAddr, err := ethtypes.ParseEthAddress(cctx.Args().Get(1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
params, err := hex.DecodeString(cctx.Args().Get(2))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
api, closer, err := GetFullNodeAPIV1(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
ctx := ReqContext(cctx)
|
||||
|
||||
res, err := api.EthCall(ctx, ethtypes.EthCall{
|
||||
From: &fromEthAddr,
|
||||
To: &toEthAddr,
|
||||
Data: params,
|
||||
}, "")
|
||||
if err != nil {
|
||||
fmt.Println("Eth call fails, return val: ", res)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Result: ", res)
|
||||
|
||||
return nil
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
var EvmGetContractAddress = &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.ParseEthAddress(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
|
||||
},
|
||||
}
|
||||
|
||||
var EvmDeployCmd = &cli.Command{
|
||||
Name: "deploy",
|
||||
Usage: "Deploy an EVM smart contract and return its address",
|
||||
ArgsUsage: "contract",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "from",
|
||||
Usage: "optionally specify the account to use for sending the creation message",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "hex",
|
||||
Usage: "use when input contract is in hex",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
afmt := NewAppFmt(cctx.App)
|
||||
|
||||
api, closer, err := GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
ctx := ReqContext(cctx)
|
||||
|
||||
if argc := cctx.Args().Len(); argc != 1 {
|
||||
return xerrors.Errorf("must pass the contract init code")
|
||||
}
|
||||
|
||||
contract, err := os.ReadFile(cctx.Args().First())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to read contract: %w", err)
|
||||
}
|
||||
if cctx.Bool("hex") {
|
||||
contract, err = hex.DecodeString(string(contract))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to decode contract: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var fromAddr address.Address
|
||||
if from := cctx.String("from"); from == "" {
|
||||
fromAddr, err = api.WalletDefaultAddress(ctx)
|
||||
} else {
|
||||
fromAddr, err = address.NewFromString(from)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nonce, err := api.MpoolGetNonce(ctx, fromAddr)
|
||||
if err != nil {
|
||||
nonce = 0 // assume a zero nonce on error (e.g. sender doesn't exist).
|
||||
}
|
||||
|
||||
params, err := actors.SerializeParams(&eam.CreateParams{
|
||||
Initcode: contract,
|
||||
Nonce: nonce,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to serialize Create params: %w", err)
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
To: builtintypes.EthereumAddressManagerActorAddr,
|
||||
From: fromAddr,
|
||||
Value: big.Zero(),
|
||||
Method: builtintypes.MethodsEAM.Create,
|
||||
Params: params,
|
||||
}
|
||||
|
||||
// TODO: On Jan 11th, we decided to add an `EAM#create_external` method
|
||||
// that uses the nonce of the caller instead of taking a user-supplied nonce.
|
||||
// Track: https://github.com/filecoin-project/ref-fvm/issues/1255
|
||||
// When that's implemented, we should migrate the CLI to use that,
|
||||
// as `EAM#create` will be reserved for the EVM runtime actor.
|
||||
// TODO: this is very racy. It may assign a _different_ nonce than the expected one.
|
||||
afmt.Println("sending message...")
|
||||
smsg, err := api.MpoolPushMessage(ctx, msg, nil)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to push message: %w", err)
|
||||
}
|
||||
|
||||
afmt.Println("waiting for message to execute...")
|
||||
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), 0)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error waiting for message: %w", err)
|
||||
}
|
||||
|
||||
// check it executed successfully
|
||||
if wait.Receipt.ExitCode != 0 {
|
||||
return xerrors.Errorf("actor execution failed")
|
||||
}
|
||||
|
||||
var result eam.CreateReturn
|
||||
r := bytes.NewReader(wait.Receipt.Return)
|
||||
if err := result.UnmarshalCBOR(r); err != nil {
|
||||
return xerrors.Errorf("error unmarshaling return value: %w", err)
|
||||
}
|
||||
|
||||
addr, err := address.NewIDAddress(result.ActorID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
afmt.Printf("Actor ID: %d\n", result.ActorID)
|
||||
afmt.Printf("ID Address: %s\n", addr)
|
||||
afmt.Printf("Robust Address: %s\n", result.RobustAddress)
|
||||
afmt.Printf("Eth Address: %s\n", "0x"+hex.EncodeToString(result.EthAddress[:]))
|
||||
|
||||
ea, err := ethtypes.CastEthAddress(result.EthAddress[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create ethereum address: %w", err)
|
||||
}
|
||||
|
||||
delegated, err := ea.ToFilecoinAddress()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to calculate f4 address: %w", err)
|
||||
}
|
||||
|
||||
afmt.Printf("f4 Address: %s\n", delegated)
|
||||
|
||||
if len(wait.Receipt.Return) > 0 {
|
||||
result := base64.StdEncoding.EncodeToString(wait.Receipt.Return)
|
||||
afmt.Printf("Return: %s\n", result)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var EvmInvokeCmd = &cli.Command{
|
||||
Name: "invoke",
|
||||
Usage: "Invoke an EVM smart contract using the specified CALLDATA",
|
||||
ArgsUsage: "address calldata",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "from",
|
||||
Usage: "optionally specify the account to use for sending the exec message",
|
||||
}, &cli.IntFlag{
|
||||
Name: "value",
|
||||
Usage: "optionally specify the value to be sent with the invokation message",
|
||||
},
|
||||
},
|
||||
Action: func(cctx *cli.Context) error {
|
||||
afmt := NewAppFmt(cctx.App)
|
||||
|
||||
api, closer, err := GetFullNodeAPI(cctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer closer()
|
||||
ctx := ReqContext(cctx)
|
||||
|
||||
if argc := cctx.Args().Len(); argc < 2 || argc > 3 {
|
||||
return xerrors.Errorf("must pass the address, entry point and (optionally) input data")
|
||||
}
|
||||
|
||||
addr, err := address.NewFromString(cctx.Args().Get(0))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to decode address: %w", err)
|
||||
}
|
||||
|
||||
var calldata []byte
|
||||
calldata, err = hex.DecodeString(cctx.Args().Get(2))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("decoding hex input data: %w", err)
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
if err := cbg.WriteByteArray(&buffer, calldata); err != nil {
|
||||
return xerrors.Errorf("failed to encode evm params as cbor: %w", err)
|
||||
}
|
||||
calldata = buffer.Bytes()
|
||||
|
||||
var fromAddr address.Address
|
||||
if from := cctx.String("from"); from == "" {
|
||||
defaddr, err := api.WalletDefaultAddress(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromAddr = defaddr
|
||||
} else {
|
||||
addr, err := address.NewFromString(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromAddr = addr
|
||||
}
|
||||
|
||||
val := abi.NewTokenAmount(cctx.Int64("value"))
|
||||
msg := &types.Message{
|
||||
To: addr,
|
||||
From: fromAddr,
|
||||
Value: val,
|
||||
Method: abi.MethodNum(2),
|
||||
Params: calldata,
|
||||
}
|
||||
|
||||
afmt.Println("sending message...")
|
||||
smsg, err := api.MpoolPushMessage(ctx, msg, nil)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to push message: %w", err)
|
||||
}
|
||||
|
||||
afmt.Println("waiting for message to execute...")
|
||||
wait, err := api.StateWaitMsg(ctx, smsg.Cid(), 0)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error waiting for message: %w", err)
|
||||
}
|
||||
|
||||
// check it executed successfully
|
||||
if wait.Receipt.ExitCode != 0 {
|
||||
return xerrors.Errorf("actor execution failed")
|
||||
}
|
||||
|
||||
afmt.Println("Gas used: ", wait.Receipt.GasUsed)
|
||||
result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return)))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("evm result not correctly encoded: %w", err)
|
||||
}
|
||||
|
||||
if len(result) > 0 {
|
||||
afmt.Println(hex.EncodeToString(result))
|
||||
} else {
|
||||
afmt.Println("OK")
|
||||
}
|
||||
|
||||
if eventsRoot := wait.Receipt.EventsRoot; eventsRoot != nil {
|
||||
afmt.Println("Events emitted:")
|
||||
|
||||
s := &apiIpldStore{ctx, api}
|
||||
amt, err := amt4.LoadAMT(ctx, s, *eventsRoot, amt4.UseTreeBitWidth(types.EventAMTBitwidth))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var evt types.Event
|
||||
err = amt.ForEach(ctx, func(u uint64, deferred *cbg.Deferred) error {
|
||||
fmt.Printf("%x\n", deferred.Raw)
|
||||
if err := evt.UnmarshalCBOR(bytes.NewReader(deferred.Raw)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("\tEmitter ID: %s\n", evt.Emitter)
|
||||
for _, e := range evt.Entries {
|
||||
value, err := cbg.ReadByteArray(bytes.NewBuffer(e.Value), uint64(len(e.Value)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("\t\tKey: %s, Value: 0x%x, Flags: b%b\n", e.Key, value, e.Flags)
|
||||
}
|
||||
return nil
|
||||
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
switch addr.Protocol() {
|
||||
case address.BLS, address.SECP256K1:
|
||||
faddr, err = fnapi.StateLookupID(ctx, addr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return ethtypes.EthAddress{}, addr, err
|
||||
}
|
||||
case address.Actor, address.ID:
|
||||
faddr, err = fnapi.StateLookupID(ctx, addr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return ethtypes.EthAddress{}, addr, err
|
||||
}
|
||||
fAct, err := fnapi.StateGetActor(ctx, faddr, types.EmptyTSK)
|
||||
if err != nil {
|
||||
return ethtypes.EthAddress{}, addr, err
|
||||
}
|
||||
if fAct.Address != nil && (*fAct.Address).Protocol() == address.Delegated {
|
||||
faddr = *fAct.Address
|
||||
}
|
||||
case address.Delegated:
|
||||
faddr = addr
|
||||
default:
|
||||
return ethtypes.EthAddress{}, addr, xerrors.Errorf("Filecoin address doesn't match known protocols")
|
||||
}
|
||||
|
||||
ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(faddr)
|
||||
if err != nil {
|
||||
return ethtypes.EthAddress{}, addr, err
|
||||
}
|
||||
|
||||
return ethAddr, faddr, nil
|
||||
}
|
||||
@@ -776,6 +776,7 @@ var StateGetActorCmd = &cli.Command{
|
||||
fmt.Printf("Nonce:\t\t%d\n", a.Nonce)
|
||||
fmt.Printf("Code:\t\t%s (%s)\n", a.Code, strtype)
|
||||
fmt.Printf("Head:\t\t%s\n", a.Head)
|
||||
fmt.Printf("Delegated address:\t\t%s\n", a.Address)
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user