306 lines
10 KiB
Go
306 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"github.com/filecoin-project/specs-actors/actors/abi"
|
|
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
|
"github.com/filecoin-project/specs-actors/actors/builtin"
|
|
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
|
|
"github.com/filecoin-project/specs-actors/actors/builtin/multisig"
|
|
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
|
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/minio/blake2b-simd"
|
|
|
|
. "github.com/filecoin-project/oni/tvx/builders"
|
|
)
|
|
|
|
func constructor(v *Builder) {
|
|
var balance = abi.NewTokenAmount(1_000_000_000_000)
|
|
var amount = abi.NewTokenAmount(10)
|
|
|
|
v.Messages.SetDefaults(GasLimit(gasLimit), GasPremium(1), GasFeeCap(gasFeeCap))
|
|
|
|
// Set up one account.
|
|
alice := v.Actors.Account(address.SECP256K1, balance)
|
|
v.CommitPreconditions()
|
|
|
|
createMultisig(v, alice, []address.Address{alice.ID}, 1, Value(amount), Nonce(0))
|
|
v.CommitApplies()
|
|
}
|
|
|
|
func proposeAndCancelOk(v *Builder) {
|
|
var (
|
|
initial = abi.NewTokenAmount(1_000_000_000_000)
|
|
amount = abi.NewTokenAmount(10)
|
|
unlockDuration = abi.ChainEpoch(10)
|
|
)
|
|
|
|
v.Messages.SetDefaults(Value(big.Zero()), Epoch(1), GasLimit(gasLimit), GasPremium(1), GasFeeCap(gasFeeCap))
|
|
|
|
// Set up three accounts: alice and bob (signers), and charlie (outsider).
|
|
var alice, bob, charlie AddressHandle
|
|
v.Actors.AccountN(address.SECP256K1, initial, &alice, &bob, &charlie)
|
|
v.CommitPreconditions()
|
|
|
|
// create the multisig actor; created by alice.
|
|
multisigAddr := createMultisig(v, alice, []address.Address{alice.ID, bob.ID}, 2, Value(amount), Nonce(0))
|
|
|
|
// alice proposes that charlie should receive 'amount' FIL.
|
|
hash := proposeOk(v, proposeOpts{
|
|
multisigAddr: multisigAddr,
|
|
sender: alice.ID,
|
|
recipient: charlie.ID,
|
|
amount: amount,
|
|
}, Nonce(1))
|
|
|
|
// bob cancels alice's transaction. This fails as bob did not create alice's transaction.
|
|
bobCancelMsg := v.Messages.Typed(bob.ID, multisigAddr, MultisigCancel(&multisig.TxnIDParams{
|
|
ID: multisig.TxnID(0),
|
|
ProposalHash: hash,
|
|
}), Nonce(0))
|
|
v.Messages.ApplyOne(bobCancelMsg)
|
|
v.Assert.Equal(bobCancelMsg.Result.ExitCode, exitcode.ErrForbidden)
|
|
|
|
// alice cancels their transaction; charlie doesn't receive any FIL,
|
|
// the multisig actor's balance is empty, and the transaction is canceled.
|
|
aliceCancelMsg := v.Messages.Typed(alice.ID, multisigAddr, MultisigCancel(&multisig.TxnIDParams{
|
|
ID: multisig.TxnID(0),
|
|
ProposalHash: hash,
|
|
}), Nonce(2))
|
|
v.Messages.ApplyOne(aliceCancelMsg)
|
|
v.Assert.Equal(exitcode.Ok, aliceCancelMsg.Result.ExitCode)
|
|
|
|
v.CommitApplies()
|
|
|
|
// verify balance is untouched.
|
|
v.Assert.BalanceEq(multisigAddr, amount)
|
|
|
|
// reload the multisig state and verify
|
|
var multisigState multisig.State
|
|
v.Actors.ActorState(multisigAddr, &multisigState)
|
|
v.Assert.Equal(&multisig.State{
|
|
Signers: []address.Address{alice.ID, bob.ID},
|
|
NumApprovalsThreshold: 2,
|
|
NextTxnID: 1,
|
|
InitialBalance: amount,
|
|
StartEpoch: 1,
|
|
UnlockDuration: unlockDuration,
|
|
PendingTxns: EmptyMapCid,
|
|
}, &multisigState)
|
|
}
|
|
|
|
func proposeAndApprove(v *Builder) {
|
|
var (
|
|
initial = abi.NewTokenAmount(1_000_000_000_000)
|
|
amount = abi.NewTokenAmount(10)
|
|
unlockDuration = abi.ChainEpoch(10)
|
|
)
|
|
|
|
v.Messages.SetDefaults(Value(big.Zero()), Epoch(1), GasLimit(gasLimit), GasPremium(1), GasFeeCap(gasFeeCap))
|
|
|
|
// Set up three accounts: alice and bob (signers), and charlie (outsider).
|
|
var alice, bob, charlie AddressHandle
|
|
v.Actors.AccountN(address.SECP256K1, initial, &alice, &bob, &charlie)
|
|
v.CommitPreconditions()
|
|
|
|
// create the multisig actor; created by alice.
|
|
multisigAddr := createMultisig(v, alice, []address.Address{alice.ID, bob.ID}, 2, Value(amount), Nonce(0))
|
|
|
|
// alice proposes that charlie should receive 'amount' FIL.
|
|
hash := proposeOk(v, proposeOpts{
|
|
multisigAddr: multisigAddr,
|
|
sender: alice.ID,
|
|
recipient: charlie.ID,
|
|
amount: amount,
|
|
}, Nonce(1))
|
|
|
|
// charlie proposes himself -> fails.
|
|
charliePropose := v.Messages.Typed(charlie.ID, multisigAddr,
|
|
MultisigPropose(&multisig.ProposeParams{
|
|
To: charlie.ID,
|
|
Value: amount,
|
|
Method: builtin.MethodSend,
|
|
Params: nil,
|
|
}), Nonce(0))
|
|
v.Messages.ApplyOne(charliePropose)
|
|
v.Assert.Equal(exitcode.ErrForbidden, charliePropose.Result.ExitCode)
|
|
|
|
// charlie attempts to accept the pending transaction -> fails.
|
|
charlieApprove := v.Messages.Typed(charlie.ID, multisigAddr,
|
|
MultisigApprove(&multisig.TxnIDParams{
|
|
ID: multisig.TxnID(0),
|
|
ProposalHash: hash,
|
|
}), Nonce(1))
|
|
v.Messages.ApplyOne(charlieApprove)
|
|
v.Assert.Equal(exitcode.ErrForbidden, charlieApprove.Result.ExitCode)
|
|
|
|
// bob approves transfer of 'amount' FIL to charlie.
|
|
// epoch is unlockDuration + 1
|
|
bobApprove := v.Messages.Typed(bob.ID, multisigAddr,
|
|
MultisigApprove(&multisig.TxnIDParams{
|
|
ID: multisig.TxnID(0),
|
|
ProposalHash: hash,
|
|
}), Nonce(0), Epoch(unlockDuration+1))
|
|
v.Messages.ApplyOne(bobApprove)
|
|
v.Assert.Equal(exitcode.Ok, bobApprove.Result.ExitCode)
|
|
|
|
v.CommitApplies()
|
|
|
|
var approveRet multisig.ApproveReturn
|
|
MustDeserialize(bobApprove.Result.Return, &approveRet)
|
|
v.Assert.Equal(multisig.ApproveReturn{
|
|
Applied: true,
|
|
Code: 0,
|
|
Ret: nil,
|
|
}, approveRet)
|
|
|
|
// assert that the multisig balance has been drained, and charlie's incremented.
|
|
v.Assert.BalanceEq(multisigAddr, big.Zero())
|
|
v.Assert.MessageSendersSatisfy(BalanceUpdated(amount), charliePropose, charlieApprove)
|
|
|
|
// reload the multisig state and verify
|
|
var multisigState multisig.State
|
|
v.Actors.ActorState(multisigAddr, &multisigState)
|
|
v.Assert.Equal(&multisig.State{
|
|
Signers: []address.Address{alice.ID, bob.ID},
|
|
NumApprovalsThreshold: 2,
|
|
NextTxnID: 1,
|
|
InitialBalance: amount,
|
|
StartEpoch: 1,
|
|
UnlockDuration: unlockDuration,
|
|
PendingTxns: EmptyMapCid,
|
|
}, &multisigState)
|
|
}
|
|
|
|
func addSigner(v *Builder) {
|
|
var (
|
|
initial = abi.NewTokenAmount(1_000_000_000_000)
|
|
amount = abi.NewTokenAmount(10)
|
|
)
|
|
|
|
v.Messages.SetDefaults(Value(big.Zero()), Epoch(1), GasLimit(gasLimit), GasPremium(1), GasFeeCap(gasFeeCap))
|
|
|
|
// Set up three accounts: alice and bob (signers), and charlie (outsider).
|
|
var alice, bob, charlie AddressHandle
|
|
v.Actors.AccountN(address.SECP256K1, initial, &alice, &bob, &charlie)
|
|
v.CommitPreconditions()
|
|
|
|
// create the multisig actor; created by alice.
|
|
multisigAddr := createMultisig(v, alice, []address.Address{alice.ID}, 1, Value(amount), Nonce(0))
|
|
|
|
addParams := &multisig.AddSignerParams{
|
|
Signer: bob.ID,
|
|
Increase: false,
|
|
}
|
|
|
|
// attempt to add bob as a signer; this fails because the addition needs to go through
|
|
// the multisig flow, as it is subject to the same approval policy.
|
|
v.Messages.Typed(alice.ID, multisigAddr, MultisigAddSigner(addParams), Nonce(1))
|
|
|
|
// go through the multisig wallet.
|
|
// since approvals = 1, this auto-approves the transaction.
|
|
v.Messages.Typed(alice.ID, multisigAddr, MultisigPropose(&multisig.ProposeParams{
|
|
To: multisigAddr,
|
|
Value: big.Zero(),
|
|
Method: builtin.MethodsMultisig.AddSigner,
|
|
Params: MustSerialize(addParams),
|
|
}), Nonce(2))
|
|
|
|
// TODO also exercise the approvals = 2 case with explicit approval.
|
|
|
|
v.CommitApplies()
|
|
|
|
// reload the multisig state and verify that bob is now a signer.
|
|
var multisigState multisig.State
|
|
v.Actors.ActorState(multisigAddr, &multisigState)
|
|
v.Assert.Equal(&multisig.State{
|
|
Signers: []address.Address{alice.ID, bob.ID},
|
|
NumApprovalsThreshold: 1,
|
|
NextTxnID: 1,
|
|
InitialBalance: amount,
|
|
StartEpoch: 1,
|
|
UnlockDuration: 10,
|
|
PendingTxns: EmptyMapCid,
|
|
}, &multisigState)
|
|
}
|
|
|
|
type proposeOpts struct {
|
|
multisigAddr address.Address
|
|
sender address.Address
|
|
recipient address.Address
|
|
amount abi.TokenAmount
|
|
}
|
|
|
|
func proposeOk(v *Builder, proposeOpts proposeOpts, opts ...MsgOpt) []byte {
|
|
propose := &multisig.ProposeParams{
|
|
To: proposeOpts.recipient,
|
|
Value: proposeOpts.amount,
|
|
Method: builtin.MethodSend,
|
|
Params: nil,
|
|
}
|
|
proposeMsg := v.Messages.Typed(proposeOpts.sender, proposeOpts.multisigAddr, MultisigPropose(propose), opts...)
|
|
|
|
v.Messages.ApplyOne(proposeMsg)
|
|
|
|
// verify that the multisig state contains the outstanding TX.
|
|
var multisigState multisig.State
|
|
v.Actors.ActorState(proposeOpts.multisigAddr, &multisigState)
|
|
|
|
id := multisig.TxnID(0)
|
|
actualTxn := loadMultisigTxn(v, multisigState, id)
|
|
v.Assert.Equal(&multisig.Transaction{
|
|
To: propose.To,
|
|
Value: propose.Value,
|
|
Method: propose.Method,
|
|
Params: propose.Params,
|
|
Approved: []address.Address{proposeOpts.sender},
|
|
}, actualTxn)
|
|
|
|
return makeProposalHash(v, actualTxn)
|
|
}
|
|
|
|
func createMultisig(v *Builder, creator AddressHandle, approvers []address.Address, threshold uint64, opts ...MsgOpt) address.Address {
|
|
const unlockDuration = abi.ChainEpoch(10)
|
|
// create the multisig actor.
|
|
params := &multisig.ConstructorParams{
|
|
Signers: approvers,
|
|
NumApprovalsThreshold: threshold,
|
|
UnlockDuration: unlockDuration,
|
|
}
|
|
msg := v.Messages.Sugar().CreateMultisigActor(creator.ID, params, opts...)
|
|
v.Messages.ApplyOne(msg)
|
|
|
|
// verify ok
|
|
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.Ok))
|
|
|
|
// verify the assigned addess is as expected.
|
|
var ret init_.ExecReturn
|
|
MustDeserialize(msg.Result.Return, &ret)
|
|
v.Assert.Equal(creator.NextActorAddress(msg.Message.Nonce, 0), ret.RobustAddress)
|
|
handles := v.Actors.Handles()
|
|
v.Assert.Equal(MustNewIDAddr(MustIDFromAddress(handles[len(handles)-1].ID)+1), ret.IDAddress)
|
|
|
|
// the multisig address's balance is incremented by the value sent to it.
|
|
v.Assert.BalanceEq(ret.IDAddress, msg.Message.Value)
|
|
|
|
return ret.IDAddress
|
|
}
|
|
|
|
func loadMultisigTxn(v *Builder, state multisig.State, id multisig.TxnID) *multisig.Transaction {
|
|
pending, err := adt.AsMap(v.Stores.ADTStore, state.PendingTxns)
|
|
v.Assert.NoError(err)
|
|
|
|
var actualTxn multisig.Transaction
|
|
found, err := pending.Get(id, &actualTxn)
|
|
v.Assert.True(found)
|
|
v.Assert.NoError(err)
|
|
return &actualTxn
|
|
}
|
|
|
|
func makeProposalHash(v *Builder, txn *multisig.Transaction) []byte {
|
|
ret, err := multisig.ComputeProposalHash(txn, blake2b.Sum256)
|
|
v.Assert.NoError(err)
|
|
return ret
|
|
}
|