diff --git a/api/api_full.go b/api/api_full.go index 8ae857dfdc6..6ff5b0709bb 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -384,6 +384,9 @@ type FullNode interface { // MsigGetAvailableBalance returns the portion of a multisig's balance that can be withdrawn or spent MsigGetAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) + // MsigGetVested returns the amount of FIL that vested in a multisig in a certain period. + // It takes the following params: , , + MsigGetVested(context.Context, address.Address, types.TipSetKey, types.TipSetKey) (types.BigInt, error) // MsigCreate creates a multisig wallet // It takes the following params: , , //, , @@ -400,17 +403,29 @@ type FullNode interface { // It takes the following params: , , , , // , , MsigCancel(context.Context, address.Address, uint64, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) + // MsigAddPropose proposes adding a signer in the multisig + // It takes the following params: , , + // , + MsigAddPropose(context.Context, address.Address, address.Address, address.Address, bool) (cid.Cid, error) + // MsigAddApprove approves a previously proposed AddSigner message + // It takes the following params: , , , + // , , + MsigAddApprove(context.Context, address.Address, address.Address, uint64, address.Address, address.Address, bool) (cid.Cid, error) + // MsigAddCancel cancels a previously proposed AddSigner message + // It takes the following params: , , , + // , + MsigAddCancel(context.Context, address.Address, address.Address, uint64, address.Address, bool) (cid.Cid, error) // MsigSwapPropose proposes swapping 2 signers in the multisig // It takes the following params: , , - // + // , MsigSwapPropose(context.Context, address.Address, address.Address, address.Address, address.Address) (cid.Cid, error) // MsigSwapApprove approves a previously proposed SwapSigner // It takes the following params: , , , - // , + // , , MsigSwapApprove(context.Context, address.Address, address.Address, uint64, address.Address, address.Address, address.Address) (cid.Cid, error) // MsigSwapCancel cancels a previously proposed SwapSigner message // It takes the following params: , , , - // + // , MsigSwapCancel(context.Context, address.Address, address.Address, uint64, address.Address, address.Address) (cid.Cid, error) MarketEnsureAvailable(context.Context, address.Address, address.Address, types.BigInt) (cid.Cid, error) diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index e2444f16bb2..2325ae93a15 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -197,10 +197,14 @@ type FullNodeStruct struct { StateCirculatingSupply func(context.Context, types.TipSetKey) (api.CirculatingSupply, error) `perm:"read"` MsigGetAvailableBalance func(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) `perm:"read"` + MsigGetVested func(context.Context, address.Address, types.TipSetKey, types.TipSetKey) (types.BigInt, error) `perm:"read"` MsigCreate func(context.Context, uint64, []address.Address, abi.ChainEpoch, types.BigInt, address.Address, types.BigInt) (cid.Cid, error) `perm:"sign"` MsigPropose func(context.Context, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) `perm:"sign"` MsigApprove func(context.Context, address.Address, uint64, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) `perm:"sign"` MsigCancel func(context.Context, address.Address, uint64, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) `perm:"sign"` + MsigAddPropose func(context.Context, address.Address, address.Address, address.Address, bool) (cid.Cid, error) `perm:"sign"` + MsigAddApprove func(context.Context, address.Address, address.Address, uint64, address.Address, address.Address, bool) (cid.Cid, error) `perm:"sign"` + MsigAddCancel func(context.Context, address.Address, address.Address, uint64, address.Address, bool) (cid.Cid, error) `perm:"sign"` MsigSwapPropose func(context.Context, address.Address, address.Address, address.Address, address.Address) (cid.Cid, error) `perm:"sign"` MsigSwapApprove func(context.Context, address.Address, address.Address, uint64, address.Address, address.Address, address.Address) (cid.Cid, error) `perm:"sign"` MsigSwapCancel func(context.Context, address.Address, address.Address, uint64, address.Address, address.Address) (cid.Cid, error) `perm:"sign"` @@ -866,6 +870,10 @@ func (c *FullNodeStruct) MsigGetAvailableBalance(ctx context.Context, a address. return c.Internal.MsigGetAvailableBalance(ctx, a, tsk) } +func (c *FullNodeStruct) MsigGetVested(ctx context.Context, a address.Address, sTsk types.TipSetKey, eTsk types.TipSetKey) (types.BigInt, error) { + return c.Internal.MsigGetVested(ctx, a, sTsk, eTsk) +} + func (c *FullNodeStruct) MsigCreate(ctx context.Context, req uint64, addrs []address.Address, duration abi.ChainEpoch, val types.BigInt, src address.Address, gp types.BigInt) (cid.Cid, error) { return c.Internal.MsigCreate(ctx, req, addrs, duration, val, src, gp) } @@ -882,6 +890,18 @@ func (c *FullNodeStruct) MsigCancel(ctx context.Context, msig address.Address, t return c.Internal.MsigCancel(ctx, msig, txID, to, amt, src, method, params) } +func (c *FullNodeStruct) MsigAddPropose(ctx context.Context, msig address.Address, src address.Address, newAdd address.Address, inc bool) (cid.Cid, error) { + return c.Internal.MsigAddPropose(ctx, msig, src, newAdd, inc) +} + +func (c *FullNodeStruct) MsigAddApprove(ctx context.Context, msig address.Address, src address.Address, txID uint64, proposer address.Address, newAdd address.Address, inc bool) (cid.Cid, error) { + return c.Internal.MsigAddApprove(ctx, msig, src, txID, proposer, newAdd, inc) +} + +func (c *FullNodeStruct) MsigAddCancel(ctx context.Context, msig address.Address, src address.Address, txID uint64, newAdd address.Address, inc bool) (cid.Cid, error) { + return c.Internal.MsigAddCancel(ctx, msig, src, txID, newAdd, inc) +} + func (c *FullNodeStruct) MsigSwapPropose(ctx context.Context, msig address.Address, src address.Address, oldAdd address.Address, newAdd address.Address) (cid.Cid, error) { return c.Internal.MsigSwapPropose(ctx, msig, src, oldAdd, newAdd) } diff --git a/cli/multisig.go b/cli/multisig.go index 57f6c2c0393..ff6abf4f7df 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -37,9 +37,13 @@ var multisigCmd = &cli.Command{ msigInspectCmd, msigProposeCmd, msigApproveCmd, + msigAddProposeCmd, + msigAddApproveCmd, + msigAddCancelCmd, msigSwapProposeCmd, msigSwapApproveCmd, msigSwapCancelCmd, + msigVestedCmd, }, } @@ -506,6 +510,236 @@ var msigApproveCmd = &cli.Command{ }, } +var msigAddProposeCmd = &cli.Command{ + Name: "add-propose", + Usage: "Propose to add a signer", + ArgsUsage: "[multisigAddress signer]", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "increase-threshold", + Usage: "whether the number of required signers should be increased", + }, + &cli.StringFlag{ + Name: "from", + Usage: "account to send the propose message from", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.Args().Len() != 2 { + return ShowHelp(cctx, fmt.Errorf("must pass multisig address and signer address")) + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + msig, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return err + } + + addr, err := address.NewFromString(cctx.Args().Get(1)) + if err != nil { + return err + } + + var from address.Address + if cctx.IsSet("from") { + f, err := address.NewFromString(cctx.String("from")) + if err != nil { + return err + } + from = f + } else { + defaddr, err := api.WalletDefaultAddress(ctx) + if err != nil { + return err + } + from = defaddr + } + + msgCid, err := api.MsigAddPropose(ctx, msig, from, addr, cctx.Bool("increase-threshold")) + if err != nil { + return err + } + + fmt.Println("sent add proposal in message: ", msgCid) + + wait, err := api.StateWaitMsg(ctx, msgCid, build.MessageConfidence) + if err != nil { + return err + } + + if wait.Receipt.ExitCode != 0 { + return fmt.Errorf("add proposal returned exit %d", wait.Receipt.ExitCode) + } + + return nil + }, +} + +var msigAddApproveCmd = &cli.Command{ + Name: "add-approve", + Usage: "Approve a message to add a signer", + ArgsUsage: "[multisigAddress proposerAddress txId newAddress increaseThreshold]", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "from", + Usage: "account to send the approve message from", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.Args().Len() != 5 { + return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, new signer address, whether to increase threshold")) + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + msig, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return err + } + + prop, err := address.NewFromString(cctx.Args().Get(1)) + if err != nil { + return err + } + + txid, err := strconv.ParseUint(cctx.Args().Get(2), 10, 64) + if err != nil { + return err + } + + newAdd, err := address.NewFromString(cctx.Args().Get(3)) + if err != nil { + return err + } + + inc, err := strconv.ParseBool(cctx.Args().Get(4)) + if err != nil { + return err + } + + var from address.Address + if cctx.IsSet("from") { + f, err := address.NewFromString(cctx.String("from")) + if err != nil { + return err + } + from = f + } else { + defaddr, err := api.WalletDefaultAddress(ctx) + if err != nil { + return err + } + from = defaddr + } + + msgCid, err := api.MsigAddApprove(ctx, msig, from, txid, prop, newAdd, inc) + if err != nil { + return err + } + + fmt.Println("sent add approval in message: ", msgCid) + + wait, err := api.StateWaitMsg(ctx, msgCid, build.MessageConfidence) + if err != nil { + return err + } + + if wait.Receipt.ExitCode != 0 { + return fmt.Errorf("add approval returned exit %d", wait.Receipt.ExitCode) + } + + return nil + }, +} + +var msigAddCancelCmd = &cli.Command{ + Name: "add-cancel", + Usage: "Cancel a message to add a signer", + ArgsUsage: "[multisigAddress txId newAddress increaseThreshold]", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "from", + Usage: "account to send the approve message from", + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.Args().Len() != 4 { + return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, new signer address, whether to increase threshold")) + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + msig, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return err + } + + txid, err := strconv.ParseUint(cctx.Args().Get(1), 10, 64) + if err != nil { + return err + } + + newAdd, err := address.NewFromString(cctx.Args().Get(2)) + if err != nil { + return err + } + + inc, err := strconv.ParseBool(cctx.Args().Get(3)) + if err != nil { + return err + } + + var from address.Address + if cctx.IsSet("from") { + f, err := address.NewFromString(cctx.String("from")) + if err != nil { + return err + } + from = f + } else { + defaddr, err := api.WalletDefaultAddress(ctx) + if err != nil { + return err + } + from = defaddr + } + + msgCid, err := api.MsigAddCancel(ctx, msig, from, txid, newAdd, inc) + if err != nil { + return err + } + + fmt.Println("sent add cancellation in message: ", msgCid) + + wait, err := api.StateWaitMsg(ctx, msgCid, build.MessageConfidence) + if err != nil { + return err + } + + if wait.Receipt.ExitCode != 0 { + return fmt.Errorf("add cancellation returned exit %d", wait.Receipt.ExitCode) + } + + return nil + }, +} + var msigSwapProposeCmd = &cli.Command{ Name: "swap-propose", Usage: "Propose to swap signers", @@ -722,7 +956,7 @@ var msigSwapCancelCmd = &cli.Command{ return err } - fmt.Println("sent swap approval in message: ", msgCid) + fmt.Println("sent swap cancellation in message: ", msgCid) wait, err := api.StateWaitMsg(ctx, msgCid, build.MessageConfidence) if err != nil { @@ -730,9 +964,71 @@ var msigSwapCancelCmd = &cli.Command{ } if wait.Receipt.ExitCode != 0 { - return fmt.Errorf("swap approval returned exit %d", wait.Receipt.ExitCode) + return fmt.Errorf("swap cancellation returned exit %d", wait.Receipt.ExitCode) + } + + return nil + }, +} + +var msigVestedCmd = &cli.Command{ + Name: "vested", + Usage: "Gets the amount vested in an msig between two epochs", + ArgsUsage: "[multisigAddress]", + Flags: []cli.Flag{ + &cli.Int64Flag{ + Name: "start-epoch", + Usage: "start epoch to measure vesting from", + Value: 0, + }, + &cli.Int64Flag{ + Name: "end-epoch", + Usage: "end epoch to stop measure vesting at", + Value: -1, + }, + }, + Action: func(cctx *cli.Context) error { + if cctx.Args().Len() != 1 { + return ShowHelp(cctx, fmt.Errorf("must pass multisig address")) } + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + msig, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return err + } + + start, err := api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Int64("start-epoch")), types.EmptyTSK) + if err != nil { + return err + } + + var end *types.TipSet + if cctx.Int64("end-epoch") < 0 { + end, err = LoadTipSet(ctx, cctx, api) + if err != nil { + return err + } + } else { + end, err = api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Int64("end-epoch")), types.EmptyTSK) + if err != nil { + return err + } + } + + ret, err := api.MsigGetVested(ctx, msig, start.Key(), end.Key()) + if err != nil { + return err + } + + fmt.Printf("Vested: %s between %d and %d\n", types.FIL(ret), start.Height(), end.Height()) + return nil }, } diff --git a/documentation/en/api-methods.md b/documentation/en/api-methods.md index dd5c1381514..a4906660cf5 100644 --- a/documentation/en/api-methods.md +++ b/documentation/en/api-methods.md @@ -74,10 +74,14 @@ * [MpoolSetConfig](#MpoolSetConfig) * [MpoolSub](#MpoolSub) * [Msig](#Msig) + * [MsigAddApprove](#MsigAddApprove) + * [MsigAddCancel](#MsigAddCancel) + * [MsigAddPropose](#MsigAddPropose) * [MsigApprove](#MsigApprove) * [MsigCancel](#MsigCancel) * [MsigCreate](#MsigCreate) * [MsigGetAvailableBalance](#MsigGetAvailableBalance) + * [MsigGetVested](#MsigGetVested) * [MsigPropose](#MsigPropose) * [MsigSwapApprove](#MsigSwapApprove) * [MsigSwapCancel](#MsigSwapCancel) @@ -1820,6 +1824,84 @@ The Msig methods are used to interact with multisig wallets on the filecoin network +### MsigAddApprove +MsigAddApprove approves a previously proposed AddSigner message +It takes the following params: , , , +, , + + +Perms: sign + +Inputs: +```json +[ + "t01234", + "t01234", + 42, + "t01234", + "t01234", + true +] +``` + +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` + +### MsigAddCancel +MsigAddCancel cancels a previously proposed AddSigner message +It takes the following params: , , , +, + + +Perms: sign + +Inputs: +```json +[ + "t01234", + "t01234", + 42, + "t01234", + true +] +``` + +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` + +### MsigAddPropose +MsigAddPropose proposes adding a signer in the multisig +It takes the following params: , , +, + + +Perms: sign + +Inputs: +```json +[ + "t01234", + "t01234", + "t01234", + true +] +``` + +Response: +```json +{ + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" +} +``` + ### MsigApprove MsigApprove approves a previously-proposed multisig message It takes the following params: , , , , , @@ -1927,6 +2009,38 @@ Inputs: Response: `"0"` +### MsigGetVested +MsigGetVested returns the amount of FIL that vested in a multisig in a certain period. +It takes the following params: , , + + +Perms: read + +Inputs: +```json +[ + "t01234", + [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ], + [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ] +] +``` + +Response: `"0"` + ### MsigPropose MsigPropose proposes a multisig message It takes the following params: , , , @@ -1957,7 +2071,7 @@ Response: ### MsigSwapApprove MsigSwapApprove approves a previously proposed SwapSigner It takes the following params: , , , -, +, , Perms: sign @@ -1984,7 +2098,7 @@ Response: ### MsigSwapCancel MsigSwapCancel cancels a previously proposed SwapSigner message It takes the following params: , , , - +, Perms: sign @@ -2010,7 +2124,7 @@ Response: ### MsigSwapPropose MsigSwapPropose proposes swapping 2 signers in the multisig It takes the following params: , , - +, Perms: sign diff --git a/node/impl/full/multisig.go b/node/impl/full/multisig.go index f1e3c61fde1..9f412aa0700 100644 --- a/node/impl/full/multisig.go +++ b/node/impl/full/multisig.go @@ -130,6 +130,33 @@ func (a *MsigAPI) MsigPropose(ctx context.Context, msig address.Address, to addr return smsg.Cid(), nil } +func (a *MsigAPI) MsigAddPropose(ctx context.Context, msig address.Address, src address.Address, newAdd address.Address, inc bool) (cid.Cid, error) { + enc, actErr := serializeAddParams(newAdd, inc) + if actErr != nil { + return cid.Undef, actErr + } + + return a.MsigPropose(ctx, msig, msig, big.Zero(), src, uint64(builtin.MethodsMultisig.AddSigner), enc) +} + +func (a *MsigAPI) MsigAddApprove(ctx context.Context, msig address.Address, src address.Address, txID uint64, proposer address.Address, newAdd address.Address, inc bool) (cid.Cid, error) { + enc, actErr := serializeAddParams(newAdd, inc) + if actErr != nil { + return cid.Undef, actErr + } + + return a.MsigApprove(ctx, msig, txID, proposer, msig, big.Zero(), src, uint64(builtin.MethodsMultisig.AddSigner), enc) +} + +func (a *MsigAPI) MsigAddCancel(ctx context.Context, msig address.Address, src address.Address, txID uint64, newAdd address.Address, inc bool) (cid.Cid, error) { + enc, actErr := serializeAddParams(newAdd, inc) + if actErr != nil { + return cid.Undef, actErr + } + + return a.MsigCancel(ctx, msig, txID, msig, big.Zero(), src, uint64(builtin.MethodsMultisig.AddSigner), enc) +} + func (a *MsigAPI) MsigSwapPropose(ctx context.Context, msig address.Address, src address.Address, oldAdd address.Address, newAdd address.Address) (cid.Cid, error) { enc, actErr := serializeSwapParams(oldAdd, newAdd) if actErr != nil { @@ -244,6 +271,18 @@ func (a *MsigAPI) msigApproveOrCancel(ctx context.Context, operation api.MsigPro return smsg.Cid(), nil } +func serializeAddParams(new address.Address, inc bool) ([]byte, error) { + enc, actErr := actors.SerializeParams(&samsig.AddSignerParams{ + Signer: new, + Increase: inc, + }) + if actErr != nil { + return nil, actErr + } + + return enc, nil +} + func serializeSwapParams(old address.Address, new address.Address) ([]byte, error) { enc, actErr := actors.SerializeParams(&samsig.SwapSignerParams{ From: old, diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 36721a93dec..f2b0df0eccb 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -886,6 +886,48 @@ func (a *StateAPI) MsigGetAvailableBalance(ctx context.Context, addr address.Add return types.BigSub(act.Balance, minBalance), nil } +func (a *StateAPI) MsigGetVested(ctx context.Context, addr address.Address, start types.TipSetKey, end types.TipSetKey) (types.BigInt, error) { + startTs, err := a.Chain.GetTipSetFromKey(start) + if err != nil { + return types.EmptyInt, xerrors.Errorf("loading start tipset %s: %w", start, err) + } + + endTs, err := a.Chain.GetTipSetFromKey(end) + if err != nil { + return types.EmptyInt, xerrors.Errorf("loading end tipset %s: %w", end, err) + } + + if startTs.Height() > endTs.Height() { + return types.EmptyInt, xerrors.Errorf("start tipset %d is after end tipset %d", startTs.Height(), endTs.Height()) + } else if startTs.Height() == endTs.Height() { + return big.Zero(), nil + } + + var mst samsig.State + act, err := a.StateManager.LoadActorState(ctx, addr, &mst, endTs) + if err != nil { + return types.EmptyInt, xerrors.Errorf("failed to load multisig actor state at end epoch: %w", err) + } + + if act.Code != builtin.MultisigActorCodeID { + return types.EmptyInt, fmt.Errorf("given actor was not a multisig") + } + + if mst.UnlockDuration == 0 || + mst.InitialBalance.IsZero() || + mst.StartEpoch+mst.UnlockDuration <= startTs.Height() || + mst.StartEpoch >= endTs.Height() { + return big.Zero(), nil + } + + startLk := mst.InitialBalance + if startTs.Height() > mst.StartEpoch { + startLk = mst.AmountLocked(startTs.Height() - mst.StartEpoch) + } + + return big.Sub(startLk, mst.AmountLocked(endTs.Height()-mst.StartEpoch)), nil +} + var initialPledgeNum = types.NewInt(110) var initialPledgeDen = types.NewInt(100)