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