diff --git a/cli/state.go b/cli/state.go index 63e923485..6c3c4b111 100644 --- a/cli/state.go +++ b/cli/state.go @@ -3,6 +3,8 @@ package cli import ( "bytes" "context" + "encoding/base64" + "encoding/hex" "encoding/json" "fmt" "html/template" @@ -22,7 +24,6 @@ import ( "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" - "github.com/libp2p/go-libp2p-core/peer" "github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multihash" "github.com/urfave/cli/v2" @@ -31,7 +32,6 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/lotus/api" @@ -1521,7 +1521,7 @@ func printMsg(ctx context.Context, api v0api.FullNode, msg cid.Cid, mw *lapi.Msg var StateCallCmd = &cli.Command{ Name: "call", Usage: "Invoke a method on an actor locally", - ArgsUsage: "[toAddress methodId (optional)]", + ArgsUsage: "[toAddress methodId params (optional)]", Flags: []cli.Flag{ &cli.StringFlag{ Name: "from", @@ -1535,8 +1535,13 @@ var StateCallCmd = &cli.Command{ }, &cli.StringFlag{ Name: "ret", - Usage: "specify how to parse output (auto, raw, addr, big)", - Value: "auto", + Usage: "specify how to parse output (raw, decoded, base64, hex)", + Value: "decoded", + }, + &cli.StringFlag{ + Name: "encoding", + Value: "base64", + Usage: "specify params encoding to parse (base64, hex)", }, }, Action: func(cctx *cli.Context) error { @@ -1577,14 +1582,23 @@ var StateCallCmd = &cli.Command{ return fmt.Errorf("failed to parse 'value': %s", err) } - act, err := api.StateGetActor(ctx, toa, ts.Key()) - if err != nil { - return fmt.Errorf("failed to lookup target actor: %s", err) - } - - params, err := parseParamsForMethod(act.Code, method, cctx.Args().Slice()[2:]) - if err != nil { - return fmt.Errorf("failed to parse params: %s", err) + var params []byte + // If params were passed in, decode them + if cctx.Args().Len() > 2 { + switch cctx.String("encoding") { + case "base64": + params, err = base64.StdEncoding.DecodeString(cctx.Args().Get(2)) + if err != nil { + return xerrors.Errorf("decoding base64 value: %w", err) + } + case "hex": + params, err = hex.DecodeString(cctx.Args().Get(2)) + if err != nil { + return xerrors.Errorf("decoding hex value: %w", err) + } + default: + return xerrors.Errorf("unrecognized encoding: %s", cctx.String("encoding")) + } } ret, err := api.StateCall(ctx, &types.Message{ @@ -1595,137 +1609,42 @@ var StateCallCmd = &cli.Command{ Params: params, }, ts.Key()) if err != nil { - return fmt.Errorf("state call failed: %s", err) + return fmt.Errorf("state call failed: %w", err) } if ret.MsgRct.ExitCode != 0 { return fmt.Errorf("invocation failed (exit: %d, gasUsed: %d): %s", ret.MsgRct.ExitCode, ret.MsgRct.GasUsed, ret.Error) } - s, err := formatOutput(cctx.String("ret"), ret.MsgRct.Return) - if err != nil { - return fmt.Errorf("failed to format output: %s", err) - } + fmt.Println("Call receipt:") + fmt.Printf("Exit code: %d\n", ret.MsgRct.ExitCode) + fmt.Printf("Gas Used: %d\n", ret.MsgRct.GasUsed) - fmt.Printf("gas used: %d\n", ret.MsgRct.GasUsed) - fmt.Printf("return: %s\n", s) + switch cctx.String("ret") { + case "decoded": + act, err := api.StateGetActor(ctx, toa, ts.Key()) + if err != nil { + return xerrors.Errorf("getting actor: %w", err) + } + + retStr, err := jsonReturn(act.Code, abi.MethodNum(method), ret.MsgRct.Return) + if err != nil { + return xerrors.Errorf("decoding return: %w", err) + } + + fmt.Printf("Return:\n%s\n", retStr) + case "raw": + fmt.Printf("Return: \n%s\n", ret.MsgRct.Return) + case "hex": + fmt.Printf("Return: \n%x\n", ret.MsgRct.Return) + case "base64": + fmt.Printf("Return: \n%s\n", base64.StdEncoding.EncodeToString(ret.MsgRct.Return)) + } return nil }, } -func formatOutput(t string, val []byte) (string, error) { - switch t { - case "raw", "hex": - return fmt.Sprintf("%x", val), nil - case "address", "addr", "a": - a, err := address.NewFromBytes(val) - if err != nil { - return "", err - } - return a.String(), nil - case "big", "int", "bigint": - bi := types.BigFromBytes(val) - return bi.String(), nil - case "fil": - bi := types.FIL(types.BigFromBytes(val)) - return bi.String(), nil - case "pid", "peerid", "peer": - pid, err := peer.IDFromBytes(val) - if err != nil { - return "", err - } - - return pid.Pretty(), nil - case "auto": - if len(val) == 0 { - return "", nil - } - - a, err := address.NewFromBytes(val) - if err == nil { - return "address: " + a.String(), nil - } - - pid, err := peer.IDFromBytes(val) - if err == nil { - return "peerID: " + pid.Pretty(), nil - } - - bi := types.BigFromBytes(val) - return "bigint: " + bi.String(), nil - default: - return "", fmt.Errorf("unrecognized output type: %q", t) - } -} - -func parseParamsForMethod(act cid.Cid, method uint64, args []string) ([]byte, error) { - if len(args) == 0 { - return nil, nil - } - - // TODO: consider moving this to a dedicated helper - actMeta, ok := stmgr.MethodsMap[act] - if !ok { - return nil, fmt.Errorf("unknown actor %s", act) - } - - methodMeta, ok := actMeta[abi.MethodNum(method)] - if !ok { - return nil, fmt.Errorf("unknown method %d for actor %s", method, act) - } - - paramObj := methodMeta.Params.Elem() - if paramObj.NumField() != len(args) { - return nil, fmt.Errorf("not enough arguments given to call that method (expecting %d)", paramObj.NumField()) - } - - p := reflect.New(paramObj) - for i := 0; i < len(args); i++ { - switch paramObj.Field(i).Type { - case reflect.TypeOf(address.Address{}): - a, err := address.NewFromString(args[i]) - if err != nil { - return nil, fmt.Errorf("failed to parse address: %s", err) - } - p.Elem().Field(i).Set(reflect.ValueOf(a)) - case reflect.TypeOf(uint64(0)): - val, err := strconv.ParseUint(args[i], 10, 64) - if err != nil { - return nil, err - } - p.Elem().Field(i).Set(reflect.ValueOf(val)) - case reflect.TypeOf(abi.ChainEpoch(0)): - val, err := strconv.ParseInt(args[i], 10, 64) - if err != nil { - return nil, err - } - p.Elem().Field(i).Set(reflect.ValueOf(abi.ChainEpoch(val))) - case reflect.TypeOf(big.Int{}): - val, err := big.FromString(args[i]) - if err != nil { - return nil, err - } - p.Elem().Field(i).Set(reflect.ValueOf(val)) - case reflect.TypeOf(peer.ID("")): - pid, err := peer.Decode(args[i]) - if err != nil { - return nil, fmt.Errorf("failed to parse peer ID: %s", err) - } - p.Elem().Field(i).Set(reflect.ValueOf(pid)) - default: - return nil, fmt.Errorf("unsupported type for call (TODO): %s", paramObj.Field(i).Type) - } - } - - m := p.Interface().(cbg.CBORMarshaler) - buf := new(bytes.Buffer) - if err := m.MarshalCBOR(buf); err != nil { - return nil, fmt.Errorf("failed to marshal param object: %s", err) - } - return buf.Bytes(), nil -} - var StateCircSupplyCmd = &cli.Command{ Name: "circulating-supply", Usage: "Get the exact current circulating supply of Filecoin", diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index f9eb3aac1..8e7e45f51 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -1756,13 +1756,14 @@ NAME: lotus state call - Invoke a method on an actor locally USAGE: - lotus state call [command options] [toAddress methodId (optional)] + lotus state call [command options] [toAddress methodId params (optional)] OPTIONS: - --from value (default: "f00") - --value value specify value field for invocation (default: "0") - --ret value specify how to parse output (auto, raw, addr, big) (default: "auto") - --help, -h show help (default: false) + --from value (default: "f00") + --value value specify value field for invocation (default: "0") + --ret value specify how to parse output (raw, decoded, base64, hex) (default: "decoded") + --encoding value specify params encoding to parse (base64, hex) (default: "base64") + --help, -h show help (default: false) ```