2019-07-29 18:13:00 +00:00
|
|
|
package actors
|
|
|
|
|
|
|
|
import (
|
2019-07-29 21:19:34 +00:00
|
|
|
"github.com/ipfs/go-cid"
|
|
|
|
|
2019-12-19 20:13:17 +00:00
|
|
|
"github.com/filecoin-project/go-address"
|
2019-10-18 04:47:41 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
2019-07-29 18:13:00 +00:00
|
|
|
|
2019-09-10 19:58:45 +00:00
|
|
|
cbg "github.com/whyrusleeping/cbor-gen"
|
|
|
|
)
|
2019-07-29 21:19:34 +00:00
|
|
|
|
2019-07-29 18:13:00 +00:00
|
|
|
type MultiSigActor struct{}
|
|
|
|
type MultiSigActorState struct {
|
|
|
|
Signers []address.Address
|
2019-09-09 17:22:40 +00:00
|
|
|
Required uint64
|
2019-07-29 21:24:52 +00:00
|
|
|
NextTxID uint64
|
2019-07-29 18:13:00 +00:00
|
|
|
|
2019-10-18 04:42:49 +00:00
|
|
|
InitialBalance types.BigInt
|
|
|
|
StartingBlock uint64
|
|
|
|
UnlockDuration uint64
|
|
|
|
|
2019-07-29 18:13:00 +00:00
|
|
|
//TODO: make this map/sharray/whatever
|
|
|
|
Transactions []MTransaction
|
|
|
|
}
|
|
|
|
|
2019-10-18 04:42:49 +00:00
|
|
|
func (msas MultiSigActorState) canSpend(act *types.Actor, amnt types.BigInt, height uint64) bool {
|
|
|
|
if msas.UnlockDuration == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
offset := height - msas.StartingBlock
|
|
|
|
if offset > msas.UnlockDuration {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
minBalance := types.BigDiv(msas.InitialBalance, types.NewInt(msas.UnlockDuration))
|
|
|
|
minBalance = types.BigMul(minBalance, types.NewInt(offset))
|
|
|
|
return !minBalance.LessThan(types.BigSub(act.Balance, amnt))
|
|
|
|
}
|
|
|
|
|
2019-07-29 18:13:00 +00:00
|
|
|
func (msas MultiSigActorState) isSigner(addr address.Address) bool {
|
|
|
|
for _, s := range msas.Signers {
|
|
|
|
if s == addr {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-10-24 09:13:38 +00:00
|
|
|
func (msas MultiSigActorState) getTransaction(txid uint64) (*MTransaction, ActorError) {
|
|
|
|
if txid >= uint64(len(msas.Transactions)) {
|
|
|
|
return nil, aerrors.Newf(1, "could not get transaction (numbers of tx %d,want to get txid %d)", len(msas.Transactions), txid)
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
2019-10-24 09:13:38 +00:00
|
|
|
return &msas.Transactions[txid], nil
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type MTransaction struct {
|
|
|
|
Created uint64 // NOT USED ??
|
|
|
|
TxID uint64
|
|
|
|
|
|
|
|
To address.Address
|
|
|
|
Value types.BigInt
|
|
|
|
Method uint64
|
|
|
|
Params []byte
|
|
|
|
|
|
|
|
Approved []address.Address
|
|
|
|
Complete bool
|
|
|
|
Canceled bool
|
2019-09-09 17:22:40 +00:00
|
|
|
RetCode uint64
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
func (tx MTransaction) Active() ActorError {
|
|
|
|
if tx.Complete {
|
|
|
|
return aerrors.New(2, "transaction already completed")
|
|
|
|
}
|
|
|
|
if tx.Canceled {
|
|
|
|
return aerrors.New(3, "transaction canceled")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-29 18:13:00 +00:00
|
|
|
type musigMethods struct {
|
|
|
|
MultiSigConstructor uint64
|
|
|
|
Propose uint64
|
|
|
|
Approve uint64
|
|
|
|
Cancel uint64
|
|
|
|
ClearCompleted uint64
|
|
|
|
AddSigner uint64
|
|
|
|
RemoveSigner uint64
|
|
|
|
SwapSigner uint64
|
|
|
|
ChangeRequirement uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
var MultiSigMethods = musigMethods{1, 2, 3, 4, 5, 6, 7, 8, 9}
|
|
|
|
|
|
|
|
func (msa MultiSigActor) Exports() []interface{} {
|
|
|
|
return []interface{}{
|
|
|
|
1: msa.MultiSigConstructor,
|
2019-07-31 15:37:00 +00:00
|
|
|
2: msa.Propose,
|
|
|
|
3: msa.Approve,
|
2019-07-29 21:19:34 +00:00
|
|
|
4: msa.Cancel,
|
|
|
|
//5: msa.ClearCompleted,
|
|
|
|
6: msa.AddSigner,
|
|
|
|
7: msa.RemoveSigner,
|
|
|
|
8: msa.SwapSigner,
|
|
|
|
9: msa.ChangeRequirement,
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type MultiSigConstructorParams struct {
|
2019-10-18 04:42:49 +00:00
|
|
|
Signers []address.Address
|
|
|
|
Required uint64
|
|
|
|
UnlockDuration uint64
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (MultiSigActor) MultiSigConstructor(act *types.Actor, vmctx types.VMContext,
|
|
|
|
params *MultiSigConstructorParams) ([]byte, ActorError) {
|
|
|
|
self := &MultiSigActorState{
|
|
|
|
Signers: params.Signers,
|
|
|
|
Required: params.Required,
|
|
|
|
}
|
2019-10-18 04:42:49 +00:00
|
|
|
|
2019-10-18 07:08:19 +00:00
|
|
|
if params.UnlockDuration != 0 {
|
|
|
|
self.InitialBalance = vmctx.Message().Value
|
|
|
|
self.UnlockDuration = params.UnlockDuration
|
|
|
|
self.StartingBlock = vmctx.BlockHeight()
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
2019-10-18 04:42:49 +00:00
|
|
|
|
2019-07-29 18:13:00 +00:00
|
|
|
head, err := vmctx.Storage().Put(self)
|
|
|
|
if err != nil {
|
|
|
|
return nil, aerrors.Wrap(err, "could not put new head")
|
|
|
|
}
|
|
|
|
err = vmctx.Storage().Commit(EmptyCBOR, head)
|
|
|
|
if err != nil {
|
|
|
|
return nil, aerrors.Wrap(err, "could not commit new head")
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type MultiSigProposeParams struct {
|
|
|
|
To address.Address
|
|
|
|
Value types.BigInt
|
|
|
|
Method uint64
|
|
|
|
Params []byte
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
func (MultiSigActor) load(vmctx types.VMContext) (cid.Cid, *MultiSigActorState, ActorError) {
|
2019-07-29 18:13:00 +00:00
|
|
|
var self MultiSigActorState
|
|
|
|
head := vmctx.Storage().GetHead()
|
|
|
|
|
|
|
|
err := vmctx.Storage().Get(head, &self)
|
|
|
|
if err != nil {
|
2019-07-29 21:19:34 +00:00
|
|
|
return cid.Undef, nil, aerrors.Wrap(err, "could not get self")
|
|
|
|
}
|
|
|
|
return head, &self, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (msa MultiSigActor) loadAndVerify(vmctx types.VMContext) (cid.Cid, *MultiSigActorState, ActorError) {
|
|
|
|
head, self, err := msa.load(vmctx)
|
|
|
|
if err != nil {
|
|
|
|
return cid.Undef, nil, err
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !self.isSigner(vmctx.Message().From) {
|
2019-07-29 21:19:34 +00:00
|
|
|
return cid.Undef, nil, aerrors.New(1, "not authorized")
|
|
|
|
}
|
|
|
|
return head, self, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (MultiSigActor) save(vmctx types.VMContext, oldHead cid.Cid, self *MultiSigActorState) ActorError {
|
|
|
|
newHead, err := vmctx.Storage().Put(self)
|
|
|
|
if err != nil {
|
|
|
|
return aerrors.Wrap(err, "could not put new head")
|
|
|
|
}
|
|
|
|
err = vmctx.Storage().Commit(oldHead, newHead)
|
|
|
|
if err != nil {
|
|
|
|
return aerrors.Wrap(err, "could not commit new head")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (msa MultiSigActor) Propose(act *types.Actor, vmctx types.VMContext,
|
|
|
|
params *MultiSigProposeParams) ([]byte, ActorError) {
|
|
|
|
|
|
|
|
head, self, err := msa.loadAndVerify(vmctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
2019-07-29 21:24:52 +00:00
|
|
|
txid := self.NextTxID
|
|
|
|
self.NextTxID++
|
2019-07-29 18:13:00 +00:00
|
|
|
|
2019-07-29 21:39:29 +00:00
|
|
|
{
|
|
|
|
tx := MTransaction{
|
|
|
|
TxID: txid,
|
|
|
|
To: params.To,
|
|
|
|
Value: params.Value,
|
|
|
|
Method: params.Method,
|
|
|
|
Params: params.Params,
|
|
|
|
Approved: []address.Address{vmctx.Message().From},
|
|
|
|
}
|
|
|
|
self.Transactions = append(self.Transactions, tx)
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
2019-10-24 09:13:38 +00:00
|
|
|
|
|
|
|
tx, err := self.getTransaction(txid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-29 18:13:00 +00:00
|
|
|
|
|
|
|
if self.Required == 1 {
|
2019-10-18 04:42:49 +00:00
|
|
|
if !self.canSpend(act, tx.Value, vmctx.BlockHeight()) {
|
|
|
|
return nil, aerrors.New(100, "transaction amount exceeds available")
|
|
|
|
}
|
2019-07-29 18:13:00 +00:00
|
|
|
_, err := vmctx.Send(tx.To, tx.Method, tx.Value, tx.Params)
|
|
|
|
if aerrors.IsFatal(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-09-09 17:22:40 +00:00
|
|
|
tx.RetCode = uint64(aerrors.RetCode(err))
|
2019-07-29 18:13:00 +00:00
|
|
|
tx.Complete = true
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
err = msa.save(vmctx, head, self)
|
2019-07-29 18:13:00 +00:00
|
|
|
if err != nil {
|
2019-07-29 21:19:34 +00:00
|
|
|
return nil, aerrors.Wrap(err, "saving state")
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
2019-09-10 19:58:45 +00:00
|
|
|
// REVIEW: On one hand, I like being very explicit about how we're doing the serialization
|
|
|
|
// on the other, maybe we shouldnt do direct calls to underlying serialization libs?
|
|
|
|
return cbg.CborEncodeMajorType(cbg.MajUnsignedInt, tx.TxID), nil
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
type MultiSigTxID struct {
|
2019-07-29 18:13:00 +00:00
|
|
|
TxID uint64
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
func (msa MultiSigActor) Approve(act *types.Actor, vmctx types.VMContext,
|
|
|
|
params *MultiSigTxID) ([]byte, ActorError) {
|
2019-07-29 18:13:00 +00:00
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
head, self, err := msa.loadAndVerify(vmctx)
|
2019-07-29 18:13:00 +00:00
|
|
|
if err != nil {
|
2019-07-29 21:19:34 +00:00
|
|
|
return nil, err
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
2019-10-24 09:13:38 +00:00
|
|
|
tx, err := self.getTransaction(params.TxID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
if err := tx.Active(); err != nil {
|
|
|
|
return nil, aerrors.Wrap(err, "could not approve")
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, signer := range tx.Approved {
|
|
|
|
if signer == vmctx.Message().From {
|
|
|
|
return nil, aerrors.New(4, "already signed this message")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tx.Approved = append(tx.Approved, vmctx.Message().From)
|
2019-09-09 17:22:40 +00:00
|
|
|
if uint64(len(tx.Approved)) >= self.Required {
|
2019-10-18 04:42:49 +00:00
|
|
|
if !self.canSpend(act, tx.Value, vmctx.BlockHeight()) {
|
|
|
|
return nil, aerrors.New(100, "transaction amount exceeds available")
|
|
|
|
}
|
2019-07-29 18:13:00 +00:00
|
|
|
_, err := vmctx.Send(tx.To, tx.Method, tx.Value, tx.Params)
|
|
|
|
if aerrors.IsFatal(err) {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-09-09 17:22:40 +00:00
|
|
|
tx.RetCode = uint64(aerrors.RetCode(err))
|
2019-07-29 18:13:00 +00:00
|
|
|
tx.Complete = true
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:26:12 +00:00
|
|
|
return nil, msa.save(vmctx, head, self)
|
2019-07-29 18:13:00 +00:00
|
|
|
}
|
2019-07-29 21:19:34 +00:00
|
|
|
|
|
|
|
func (msa MultiSigActor) Cancel(act *types.Actor, vmctx types.VMContext,
|
|
|
|
params *MultiSigTxID) ([]byte, ActorError) {
|
|
|
|
|
|
|
|
head, self, err := msa.loadAndVerify(vmctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-10-24 09:13:38 +00:00
|
|
|
tx, err := self.getTransaction(params.TxID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
if err := tx.Active(); err != nil {
|
|
|
|
return nil, aerrors.Wrap(err, "could not cancel")
|
|
|
|
}
|
|
|
|
|
|
|
|
proposer := tx.Approved[0]
|
|
|
|
if proposer != vmctx.Message().From && self.isSigner(proposer) {
|
|
|
|
return nil, aerrors.New(4, "cannot cancel another signers transaction")
|
|
|
|
}
|
|
|
|
tx.Canceled = true
|
|
|
|
|
2019-07-29 21:26:12 +00:00
|
|
|
return nil, msa.save(vmctx, head, self)
|
2019-07-29 21:19:34 +00:00
|
|
|
}
|
|
|
|
|
2019-08-04 12:11:06 +00:00
|
|
|
type MultiSigAddSignerParam struct {
|
|
|
|
Signer address.Address
|
|
|
|
Increase bool
|
2019-07-29 21:19:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (msa MultiSigActor) AddSigner(act *types.Actor, vmctx types.VMContext,
|
2019-08-04 12:11:06 +00:00
|
|
|
params *MultiSigAddSignerParam) ([]byte, ActorError) {
|
2019-07-29 21:19:34 +00:00
|
|
|
|
|
|
|
head, self, err := msa.load(vmctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := vmctx.Message()
|
|
|
|
if msg.From != msg.To {
|
|
|
|
return nil, aerrors.New(4, "add signer must be called by wallet itself")
|
|
|
|
}
|
|
|
|
if self.isSigner(params.Signer) {
|
|
|
|
return nil, aerrors.New(5, "new address is already a signer")
|
|
|
|
}
|
2019-08-04 12:11:06 +00:00
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
self.Signers = append(self.Signers, params.Signer)
|
2019-08-04 12:11:06 +00:00
|
|
|
if params.Increase {
|
|
|
|
self.Required = self.Required + 1
|
|
|
|
}
|
2019-07-29 21:19:34 +00:00
|
|
|
|
|
|
|
return nil, msa.save(vmctx, head, self)
|
|
|
|
}
|
|
|
|
|
2019-08-04 12:11:06 +00:00
|
|
|
type MultiSigRemoveSignerParam struct {
|
|
|
|
Signer address.Address
|
|
|
|
Decrease bool
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
func (msa MultiSigActor) RemoveSigner(act *types.Actor, vmctx types.VMContext,
|
2019-08-04 12:11:06 +00:00
|
|
|
params *MultiSigRemoveSignerParam) ([]byte, ActorError) {
|
2019-07-29 21:19:34 +00:00
|
|
|
|
|
|
|
head, self, err := msa.load(vmctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := vmctx.Message()
|
|
|
|
if msg.From != msg.To {
|
|
|
|
return nil, aerrors.New(4, "remove signer must be called by wallet itself")
|
|
|
|
}
|
|
|
|
if !self.isSigner(params.Signer) {
|
|
|
|
return nil, aerrors.New(5, "given address was not a signer")
|
|
|
|
}
|
|
|
|
|
|
|
|
newSigners := make([]address.Address, 0, len(self.Signers)-1)
|
|
|
|
for _, s := range self.Signers {
|
|
|
|
if s != params.Signer {
|
|
|
|
newSigners = append(newSigners, s)
|
|
|
|
}
|
|
|
|
}
|
2019-09-09 17:22:40 +00:00
|
|
|
if params.Decrease || uint64(len(self.Signers)-1) < self.Required {
|
2019-08-04 12:11:06 +00:00
|
|
|
self.Required = self.Required - 1
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
self.Signers = newSigners
|
|
|
|
|
|
|
|
return nil, msa.save(vmctx, head, self)
|
|
|
|
}
|
|
|
|
|
|
|
|
type MultiSigSwapSignerParams struct {
|
|
|
|
From address.Address
|
|
|
|
To address.Address
|
|
|
|
}
|
|
|
|
|
|
|
|
func (msa MultiSigActor) SwapSigner(act *types.Actor, vmctx types.VMContext,
|
|
|
|
params *MultiSigSwapSignerParams) ([]byte, ActorError) {
|
|
|
|
|
|
|
|
head, self, err := msa.load(vmctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := vmctx.Message()
|
|
|
|
if msg.From != msg.To {
|
|
|
|
return nil, aerrors.New(4, "swap signer must be called by wallet itself")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !self.isSigner(params.From) {
|
|
|
|
return nil, aerrors.New(5, "given old address was not a signer")
|
|
|
|
}
|
|
|
|
if self.isSigner(params.To) {
|
|
|
|
return nil, aerrors.New(6, "given new address was already a signer")
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:32:17 +00:00
|
|
|
newSigners := make([]address.Address, 0, len(self.Signers))
|
2019-07-29 21:19:34 +00:00
|
|
|
for _, s := range self.Signers {
|
|
|
|
if s != params.From {
|
|
|
|
newSigners = append(newSigners, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
newSigners = append(newSigners, params.To)
|
|
|
|
self.Signers = newSigners
|
|
|
|
|
|
|
|
return nil, msa.save(vmctx, head, self)
|
|
|
|
}
|
|
|
|
|
|
|
|
type MultiSigChangeReqParams struct {
|
2019-09-09 17:22:40 +00:00
|
|
|
Req uint64
|
2019-07-29 21:19:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (msa MultiSigActor) ChangeRequirement(act *types.Actor, vmctx types.VMContext,
|
|
|
|
params *MultiSigChangeReqParams) ([]byte, ActorError) {
|
|
|
|
|
|
|
|
head, self, err := msa.load(vmctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := vmctx.Message()
|
|
|
|
if msg.From != msg.To {
|
|
|
|
return nil, aerrors.New(4, "change requirement must be called by wallet itself")
|
|
|
|
}
|
|
|
|
|
|
|
|
if params.Req < 1 {
|
|
|
|
return nil, aerrors.New(5, "requirement must be at least 1")
|
|
|
|
}
|
2019-10-24 09:13:38 +00:00
|
|
|
|
|
|
|
if params.Req > uint64(len(self.Signers)) {
|
|
|
|
return nil, aerrors.New(6, "requirement must be at most the numbers of signers")
|
|
|
|
}
|
|
|
|
|
2019-07-29 21:19:34 +00:00
|
|
|
self.Required = params.Req
|
|
|
|
return nil, msa.save(vmctx, head, self)
|
|
|
|
}
|