github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/builtin/multisig/multisig_state.go (about) 1 package multisig 2 3 import ( 4 address "github.com/filecoin-project/go-address" 5 "github.com/filecoin-project/go-state-types/abi" 6 "github.com/filecoin-project/go-state-types/big" 7 cid "github.com/ipfs/go-cid" 8 "golang.org/x/xerrors" 9 10 "github.com/filecoin-project/specs-actors/v4/actors/builtin" 11 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" 12 ) 13 14 type State struct { 15 Signers []address.Address // Signers must be canonical ID-addresses. 16 NumApprovalsThreshold uint64 17 NextTxnID TxnID 18 19 // Linear unlock 20 InitialBalance abi.TokenAmount 21 StartEpoch abi.ChainEpoch 22 UnlockDuration abi.ChainEpoch 23 24 PendingTxns cid.Cid // HAMT[TxnID]Transaction 25 } 26 27 // Tests whether an address is in the list of signers. 28 func (st *State) IsSigner(address address.Address) bool { 29 for _, signer := range st.Signers { 30 if signer == address { 31 return true 32 } 33 } 34 return false 35 } 36 37 func (st *State) SetLocked(startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, lockedAmount abi.TokenAmount) { 38 st.StartEpoch = startEpoch 39 st.UnlockDuration = unlockDuration 40 st.InitialBalance = lockedAmount 41 } 42 43 func (st *State) AmountLocked(elapsedEpoch abi.ChainEpoch) abi.TokenAmount { 44 if elapsedEpoch >= st.UnlockDuration { 45 return abi.NewTokenAmount(0) 46 } 47 if elapsedEpoch <= 0 { 48 return st.InitialBalance 49 } 50 51 unlockDuration := big.NewInt(int64(st.UnlockDuration)) 52 remainingLockDuration := big.Sub(unlockDuration, big.NewInt(int64(elapsedEpoch))) 53 54 // locked = ceil(InitialBalance * remainingLockDuration / UnlockDuration) 55 numerator := big.Mul(st.InitialBalance, remainingLockDuration) 56 denominator := unlockDuration 57 quot := big.Div(numerator, denominator) 58 rem := big.Mod(numerator, denominator) 59 60 locked := quot 61 if !rem.IsZero() { 62 locked = big.Add(locked, big.NewInt(1)) 63 } 64 return locked 65 } 66 67 // Iterates all pending transactions and removes an address from each list of approvals, if present. 68 // If an approval list becomes empty, the pending transaction is deleted. 69 func (st *State) PurgeApprovals(store adt.Store, addr address.Address) error { 70 txns, err := adt.AsMap(store, st.PendingTxns, builtin.DefaultHamtBitwidth) 71 if err != nil { 72 return xerrors.Errorf("failed to load transactions: %w", err) 73 } 74 75 // Identify the transactions that need updating. 76 var txnIdsToPurge []string // For stable iteration 77 txnsToPurge := map[string]Transaction{} // Values are not pointers, we need copies 78 var txn Transaction 79 if err = txns.ForEach(&txn, func(txid string) error { 80 for _, approver := range txn.Approved { 81 if approver == addr { 82 txnIdsToPurge = append(txnIdsToPurge, txid) 83 txnsToPurge[txid] = txn 84 break 85 } 86 } 87 return nil 88 }); err != nil { 89 return xerrors.Errorf("failed to traverse transactions: %w", err) 90 } 91 92 // Update or remove those transactions. 93 for _, txid := range txnIdsToPurge { 94 txn := txnsToPurge[txid] 95 // The right length is almost certainly len-1, but let's not be too clever. 96 newApprovers := make([]address.Address, 0, len(txn.Approved)) 97 for _, approver := range txn.Approved { 98 if approver != addr { 99 newApprovers = append(newApprovers, approver) 100 } 101 } 102 103 if len(newApprovers) > 0 { 104 txn.Approved = newApprovers 105 if err := txns.Put(StringKey(txid), &txn); err != nil { 106 return xerrors.Errorf("failed to update transaction approvers: %w", err) 107 } 108 } else { 109 if err := txns.Delete(StringKey(txid)); err != nil { 110 return xerrors.Errorf("failed to delete transaction with no approvers: %w", err) 111 } 112 } 113 } 114 115 if newTxns, err := txns.Root(); err != nil { 116 return xerrors.Errorf("failed to persist transactions: %w", err) 117 } else { 118 st.PendingTxns = newTxns 119 } 120 return nil 121 } 122 123 // return nil if MultiSig maintains required locked balance after spending the amount, else return an error. 124 func (st *State) assertAvailable(currBalance abi.TokenAmount, amountToSpend abi.TokenAmount, currEpoch abi.ChainEpoch) error { 125 if amountToSpend.LessThan(big.Zero()) { 126 return xerrors.Errorf("amount to spend %s less than zero", amountToSpend.String()) 127 } 128 if currBalance.LessThan(amountToSpend) { 129 return xerrors.Errorf("current balance %s less than amount to spend %s", currBalance.String(), amountToSpend.String()) 130 } 131 if amountToSpend.IsZero() { 132 // Always permit a transaction that sends no value, even if the lockup exceeds the current balance. 133 return nil 134 } 135 136 remainingBalance := big.Sub(currBalance, amountToSpend) 137 amountLocked := st.AmountLocked(currEpoch - st.StartEpoch) 138 if remainingBalance.LessThan(amountLocked) { 139 return xerrors.Errorf("balance %s if spent %s would be less than locked amount %s", 140 remainingBalance.String(), amountToSpend, amountLocked.String()) 141 } 142 143 return nil 144 } 145 146 // An adt.Map key that just preserves the underlying string. 147 type StringKey string 148 149 func (k StringKey) Key() string { 150 return string(k) 151 }