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  }