migrate multisig suite. (#241)

This commit is contained in:
Raúl Kripalani 2020-08-16 19:27:53 +01:00 committed by GitHub
parent 328cd14897
commit 6164d16f19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 403 additions and 386 deletions

View File

@ -1,362 +0,0 @@
package main
import (
"fmt"
"os"
address "github.com/filecoin-project/go-address"
"github.com/filecoin-project/oni/tvx/chain"
"github.com/filecoin-project/oni/tvx/drivers"
abi_spec "github.com/filecoin-project/specs-actors/actors/abi"
big_spec "github.com/filecoin-project/specs-actors/actors/abi/big"
builtin_spec "github.com/filecoin-project/specs-actors/actors/builtin"
multisig_spec "github.com/filecoin-project/specs-actors/actors/builtin/multisig"
exitcode_spec "github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
"github.com/minio/blake2b-simd"
)
func MessageTest_MultiSigActor() error {
err := func(testname string) error {
const numApprovals = 1
const unlockDuration = 10
var valueSend = abi_spec.NewTokenAmount(10)
var initialBal = abi_spec.NewTokenAmount(200000000000)
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
// creator of the multisig actor
alice, aliceID := td.NewAccountActor(drivers.SECP, initialBal)
// expected address of the actor
multisigAddr := chain.MustNewIDAddr(1 + chain.MustIDFromAddress(aliceID))
preroot := td.GetStateRoot()
createRet := td.ComputeInitActorExecReturn(alice, 0, 0, multisigAddr)
td.MustCreateAndVerifyMultisigActor(0, valueSend, multisigAddr, alice,
&multisig_spec.ConstructorParams{
Signers: []address.Address{aliceID},
NumApprovalsThreshold: numApprovals,
UnlockDuration: unlockDuration,
},
exitcode_spec.Ok, chain.MustSerialize(&createRet))
postroot := td.GetStateRoot()
td.Vector.CAR = td.MustMarshalGzippedCAR(preroot, postroot)
td.Vector.Pre.StateTree.RootCID = preroot
td.Vector.Post.StateTree.RootCID = postroot
// encode and output
fmt.Fprintln(os.Stdout, string(td.Vector.MustMarshalJSON()))
return nil
}("constructor test")
if err != nil {
return err
}
err = func(testname string) error {
const numApprovals = 2
const unlockDuration = 10
var valueSend = abi_spec.NewTokenAmount(10)
var initialBal = abi_spec.NewTokenAmount(200000000000)
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
alice, aliceID := td.NewAccountActor(drivers.SECP, initialBal)
bob, bobID := td.NewAccountActor(drivers.SECP, initialBal)
outsider, outsiderID := td.NewAccountActor(drivers.SECP, initialBal)
multisigAddr := chain.MustNewIDAddr(1 + chain.MustIDFromAddress(outsiderID))
preroot := td.GetStateRoot()
createRet := td.ComputeInitActorExecReturn(alice, 0, 0, multisigAddr)
// create the multisig actor
td.MustCreateAndVerifyMultisigActor(0, valueSend, multisigAddr, alice,
&multisig_spec.ConstructorParams{
Signers: []address.Address{aliceID, bobID},
NumApprovalsThreshold: numApprovals,
UnlockDuration: unlockDuration,
},
exitcode_spec.Ok, chain.MustSerialize(&createRet))
td.AssertBalance(multisigAddr, valueSend)
// alice proposes that outsider should receive 'valueSend' FIL.
pparams := multisig_spec.ProposeParams{
To: outsider,
Value: valueSend,
Method: builtin_spec.MethodSend,
Params: nil,
}
// propose the transaction and assert it exists in the actor state
txID0 := multisig_spec.TxnID(0)
expected := multisig_spec.ProposeReturn{
TxnID: 0,
Applied: false,
Code: 0,
Ret: nil,
}
td.ApplyExpect(
td.MessageProducer.MultisigPropose(alice, multisigAddr, &pparams, chain.Nonce(1)),
chain.MustSerialize(&expected))
txn0 := multisig_spec.Transaction{
To: pparams.To,
Value: pparams.Value,
Method: pparams.Method,
Params: pparams.Params,
Approved: []address.Address{aliceID},
}
ph := mustMakeProposalHash(&txn0)
td.AssertMultisigTransaction(multisigAddr, txID0, txn0)
// bob cancels alice's transaction. This fails as bob did not create alice's transaction.
td.ApplyFailure(
td.MessageProducer.MultisigCancel(bob, multisigAddr, &multisig_spec.TxnIDParams{ID: txID0, ProposalHash: ph}, chain.Nonce(0)),
exitcode_spec.ErrForbidden)
// alice cancels their transaction. The outsider doesn't receive any FIL, the multisig actor's balance is empty, and the
// transaction is canceled.
td.ApplyOk(
td.MessageProducer.MultisigCancel(alice, multisigAddr, &multisig_spec.TxnIDParams{ID: txID0, ProposalHash: ph}, chain.Nonce(2)),
)
td.AssertMultisigState(multisigAddr, multisig_spec.State{
Signers: []address.Address{aliceID, bobID},
NumApprovalsThreshold: numApprovals,
NextTxnID: 1,
InitialBalance: valueSend,
StartEpoch: 1,
UnlockDuration: unlockDuration,
})
td.AssertBalance(multisigAddr, valueSend)
postroot := td.GetStateRoot()
td.Vector.CAR = td.MustMarshalGzippedCAR(preroot, postroot)
td.Vector.Pre.StateTree.RootCID = preroot
td.Vector.Post.StateTree.RootCID = postroot
// encode and output
fmt.Fprintln(os.Stdout, string(td.Vector.MustMarshalJSON()))
return nil
}("propose and cancel")
if err != nil {
return err
}
err = func(testname string) error {
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
var initialBal = abi_spec.NewTokenAmount(200000000000)
const numApprovals = 2
const unlockDuration = 1
var valueSend = abi_spec.NewTokenAmount(10)
// Signers
alice, aliceID := td.NewAccountActor(drivers.SECP, initialBal)
bob, bobID := td.NewAccountActor(drivers.SECP, initialBal)
// Not Signer
outsider, outsiderID := td.NewAccountActor(drivers.SECP, initialBal)
preroot := td.GetStateRoot()
// Multisig actor address
multisigAddr := chain.MustNewIDAddr(1 + chain.MustIDFromAddress(outsiderID))
createRet := td.ComputeInitActorExecReturn(alice, 0, 0, multisigAddr)
// create the multisig actor
td.MustCreateAndVerifyMultisigActor(0, valueSend, multisigAddr, alice,
&multisig_spec.ConstructorParams{
Signers: []address.Address{aliceID, bobID},
NumApprovalsThreshold: numApprovals,
UnlockDuration: unlockDuration,
},
exitcode_spec.Ok, chain.MustSerialize(&createRet))
// setup propose expected values and params
pparams := multisig_spec.ProposeParams{
To: outsider,
Value: valueSend,
Method: builtin_spec.MethodSend,
Params: nil,
}
// propose the transaction and assert it exists in the actor state
txID0 := multisig_spec.TxnID(0)
expectedPropose := multisig_spec.ProposeReturn{
TxnID: 0,
Applied: false,
Code: 0,
Ret: nil,
}
td.ApplyExpect(
td.MessageProducer.MultisigPropose(alice, multisigAddr, &pparams, chain.Nonce(1)),
chain.MustSerialize(&expectedPropose))
txn0 := multisig_spec.Transaction{
To: pparams.To,
Value: pparams.Value,
Method: pparams.Method,
Params: pparams.Params,
Approved: []address.Address{aliceID},
}
ph := mustMakeProposalHash(&txn0)
td.AssertMultisigTransaction(multisigAddr, txID0, txn0)
// outsider proposes themselves to receive 'valueSend' FIL. This fails as they are not a signer.
td.ApplyFailure(
td.MessageProducer.MultisigPropose(outsider, multisigAddr, &pparams, chain.Nonce(0)),
exitcode_spec.ErrForbidden)
// outsider approves the value transfer alice sent. This fails as they are not a signer.
td.ApplyFailure(
td.MessageProducer.MultisigApprove(outsider, multisigAddr, &multisig_spec.TxnIDParams{ID: txID0, ProposalHash: ph}, chain.Nonce(1)),
exitcode_spec.ErrForbidden)
// increment the epoch to unlock the funds
td.ExeCtx.Epoch += unlockDuration
balanceBefore := td.GetBalance(outsider)
// bob approves transfer of 'valueSend' FIL to outsider.
expectedApprove := multisig_spec.ApproveReturn{
Applied: true,
Code: 0,
Ret: nil,
}
td.ApplyExpect(
td.MessageProducer.MultisigApprove(bob, multisigAddr, &multisig_spec.TxnIDParams{ID: txID0, ProposalHash: ph}, chain.Nonce(0)),
chain.MustSerialize(&expectedApprove))
txID1 := multisig_spec.TxnID(1)
td.AssertMultisigState(multisigAddr, multisig_spec.State{
Signers: []address.Address{aliceID, bobID},
NumApprovalsThreshold: numApprovals,
NextTxnID: txID1,
InitialBalance: valueSend,
StartEpoch: 1,
UnlockDuration: unlockDuration,
})
td.AssertMultisigContainsTransaction(multisigAddr, txID0, false)
// Multisig balance has been transferred to outsider.
td.AssertBalance(multisigAddr, big_spec.Zero())
td.AssertBalance(outsider, big_spec.Add(balanceBefore, valueSend))
postroot := td.GetStateRoot()
td.Vector.CAR = td.MustMarshalGzippedCAR(preroot, postroot)
td.Vector.Pre.StateTree.RootCID = preroot
td.Vector.Post.StateTree.RootCID = postroot
// encode and output
fmt.Fprintln(os.Stdout, string(td.Vector.MustMarshalJSON()))
return nil
}("propose and approve")
if err != nil {
return err
}
err = func(testname string) error {
const initialNumApprovals = 1
var msValue = abi_spec.NewTokenAmount(100000000000)
var initialBal = abi_spec.NewTokenAmount(200000000000)
td := drivers.NewTestDriver()
td.Vector.Meta.Desc = testname
alice, aliceID := td.NewAccountActor(drivers.SECP, initialBal) // 101
_, bobID := td.NewAccountActor(drivers.SECP, initialBal) // 102
var initialSigners = []address.Address{aliceID}
multisigAddr := chain.MustNewIDAddr(1 + chain.MustIDFromAddress(bobID))
preroot := td.GetStateRoot()
createRet := td.ComputeInitActorExecReturn(alice, 0, 0, multisigAddr)
td.MustCreateAndVerifyMultisigActor(0, msValue, multisigAddr, alice,
&multisig_spec.ConstructorParams{
Signers: initialSigners,
NumApprovalsThreshold: initialNumApprovals,
UnlockDuration: 0,
},
exitcode_spec.Ok,
chain.MustSerialize(&createRet),
)
addSignerParams := multisig_spec.AddSignerParams{
Signer: bobID,
Increase: false,
}
// alice fails to call directly since AddSigner
td.ApplyFailure(
td.MessageProducer.MultisigAddSigner(alice, multisigAddr, &addSignerParams, chain.Nonce(1)),
exitcode_spec.SysErrForbidden,
)
// AddSigner must be staged through the multisig itself
// Alice proposes the AddSigner.
// Since approvals = 1 this auto-approves the transaction.
expected := multisig_spec.ProposeReturn{
TxnID: 0,
Applied: true,
Code: 0,
Ret: nil,
}
td.ApplyExpect(
td.MessageProducer.MultisigPropose(alice, multisigAddr, &multisig_spec.ProposeParams{
To: multisigAddr,
Value: big_spec.Zero(),
Method: builtin_spec.MethodsMultisig.AddSigner,
Params: chain.MustSerialize(&addSignerParams),
}, chain.Nonce(2)),
chain.MustSerialize(&expected),
)
// TODO also exercise the approvals = 2 case with explicit approval.
// Check that bob is now a signer
td.AssertMultisigState(multisigAddr, multisig_spec.State{
Signers: append(initialSigners, bobID),
NumApprovalsThreshold: initialNumApprovals,
NextTxnID: multisig_spec.TxnID(1),
InitialBalance: big_spec.Zero(),
StartEpoch: 0,
UnlockDuration: 0,
})
postroot := td.GetStateRoot()
td.Vector.CAR = td.MustMarshalGzippedCAR(preroot, postroot)
td.Vector.Pre.StateTree.RootCID = preroot
td.Vector.Post.StateTree.RootCID = postroot
// encode and output
fmt.Fprintln(os.Stdout, string(td.Vector.MustMarshalJSON()))
return nil
}("add signer")
if err != nil {
return err
}
return nil
}
func mustMakeProposalHash(txn *multisig_spec.Transaction) []byte {
txnHash, err := multisig_spec.ComputeProposalHash(txn, blake2b.Sum256)
if err != nil {
panic(err)
}
return txnHash
}

View File

@ -18,30 +18,37 @@ import (
cbg "github.com/whyrusleeping/cbor-gen"
)
type registeredActor struct {
handle AddressHandle
initial abi.TokenAmount
}
// Actors is an object that manages actors in the test vector.
type Actors struct {
// registered stores registered actors and their initial balances.
registered map[AddressHandle]abi.TokenAmount
registered []registeredActor
b *Builder
}
func newActors(b *Builder) *Actors {
return &Actors{
registered: make(map[AddressHandle]abi.TokenAmount),
b: b,
}
return &Actors{b: b}
}
// Count returns the number of actors registered during preconditions.
func (a *Actors) Count() int {
return len(a.registered)
}
// HandleFor gets the canonical handle for a registered address, which can
// appear at either ID or Robust position.
func (a *Actors) HandleFor(addr address.Address) AddressHandle {
for h := range a.registered {
if h.ID == addr || h.Robust == addr {
return h
for _, r := range a.registered {
if r.handle.ID == addr || r.handle.Robust == addr {
return r.handle
}
}
a.b.Assert.FailNowf("asked for initial balance of unknown actor", "actor: %s", addr)
a.b.Assert.FailNowf("asked for handle of unknown actor", "actor: %s", addr)
return AddressHandle{} // will never reach here.
}
@ -49,8 +56,22 @@ func (a *Actors) HandleFor(addr address.Address) AddressHandle {
// during preconditions. It matches against both the ID and Robust
// addresses. It records an assertion failure if the actor is unknown.
func (a *Actors) InitialBalance(addr address.Address) abi.TokenAmount {
handle := a.HandleFor(addr)
return a.registered[handle]
for _, r := range a.registered {
if r.handle.ID == addr || r.handle.Robust == addr {
return r.initial
}
}
a.b.Assert.FailNowf("asked for initial balance of unknown actor", "actor: %s", addr)
return big.Zero() // will never reach here.
}
// Handles returns the AddressHandles for all registered actors.
func (a *Actors) Handles() []AddressHandle {
ret := make([]AddressHandle, 0, len(a.registered))
for _, r := range a.registered {
ret = append(ret, r.handle)
}
return ret
}
// AccountN creates many account actors of the specified kind, with the
@ -78,7 +99,7 @@ func (a *Actors) Account(typ address.Protocol, balance abi.TokenAmount) AddressH
actorState := &account.State{Address: addr}
handle := a.CreateActor(builtin.AccountActorCodeID, addr, balance, actorState)
a.registered[handle] = balance
a.registered = append(a.registered, registeredActor{handle, balance})
return handle
}
@ -169,7 +190,7 @@ func (a *Actors) Miner(cfg MinerActorCfg) (minerActor, owner, worker AddressHand
panic(err)
}
a.registered[handle] = big.Zero()
a.registered = append(a.registered, registeredActor{handle, big.Zero()})
return handle, owner, worker
}

View File

@ -85,7 +85,11 @@ func (m *Messages) Raw(from, to address.Address, method abi.MethodNum, params []
// - we know about this message (i.e. it has been added through Typed, Raw or Sugar).
func (m *Messages) ApplyOne(am *ApplicableMessage) {
var found bool
for _, other := range m.messages {
for i, other := range m.messages {
if other.Result != nil {
// message has been applied, continue.
continue
}
if am == other {
// we have scanned all preceding messages, and verified they had been applied.
// we are ready to perform the application.
@ -94,7 +98,7 @@ func (m *Messages) ApplyOne(am *ApplicableMessage) {
}
// verify that preceding messages have been applied.
// this will abort if unsatisfied.
m.b.Assert.Nil(other.Result, "preceding messages must have been applied when calling Apply*; first unapplied: %v", other)
m.b.Assert.Nil(other.Result, "preceding messages must have been applied when calling Apply*; index of first unapplied: %d", i)
}
m.b.Assert.True(found, "ApplicableMessage not found")
m.b.applyMessage(am)

View File

@ -31,16 +31,10 @@ func (s *sugarMsg) CreatePaychActor(from, to address.Address, opts ...MsgOpt) *A
}), opts...)
}
func (s *sugarMsg) CreateMultisigActor(from address.Address, signers []address.Address, unlockDuration abi.ChainEpoch, numApprovals uint64, opts ...MsgOpt) *ApplicableMessage {
ctorparams := &multisig.ConstructorParams{
Signers: signers,
NumApprovalsThreshold: numApprovals,
UnlockDuration: unlockDuration,
}
func (s *sugarMsg) CreateMultisigActor(from address.Address, params *multisig.ConstructorParams, opts ...MsgOpt) *ApplicableMessage {
return s.m.Typed(from, builtin.InitActorAddr, InitExec(&init_.ExecParams{
CodeCID: builtin.MultisigActorCodeID,
ConstructorParams: MustSerialize(ctorparams),
ConstructorParams: MustSerialize(params),
}), opts...)
}

View File

@ -0,0 +1,51 @@
package main
import (
. "github.com/filecoin-project/oni/tvx/builders"
"github.com/filecoin-project/oni/tvx/schema"
)
const (
gasLimit = 1_000_000_000
gasFeeCap = 200
)
func main() {
g := NewGenerator()
defer g.Wait()
g.MessageVectorGroup("basic",
&MessageVectorGenItem{
Metadata: &schema.Metadata{
ID: "ok-create",
Version: "v1",
Desc: "multisig actor constructor ok",
},
Func: constructor,
},
&MessageVectorGenItem{
Metadata: &schema.Metadata{
ID: "ok-propose-and-cancel",
Version: "v1",
Desc: "multisig actor propose and cancel ok",
},
Func: proposeAndCancelOk,
},
&MessageVectorGenItem{
Metadata: &schema.Metadata{
ID: "ok-propose-and-approve",
Version: "v1",
Desc: "multisig actor propose, unauthorized proposals+approval, and approval ok",
},
Func: proposeAndApprove,
},
&MessageVectorGenItem{
Metadata: &schema.Metadata{
ID: "ok-add-signer",
Version: "v1",
Desc: "multisig actor accepts only AddSigner messages that go through a reflexive flow",
},
Func: addSigner,
},
)
}

305
tvx/scripts/multisig/ok.go Normal file
View File

@ -0,0 +1,305 @@
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
}

View File

@ -308,7 +308,11 @@ func prepareStage(v *Builder, creatorBalance, msBalance abi.TokenAmount) *msStag
creator := v.Actors.Account(address.SECP256K1, creatorBalance)
v.CommitPreconditions()
msg := v.Messages.Sugar().CreateMultisigActor(creator.ID, []address.Address{creator.ID}, 0, 1, Value(msBalance), Nonce(0))
msg := v.Messages.Sugar().CreateMultisigActor(creator.ID, &multisig.ConstructorParams{
Signers: []address.Address{creator.ID},
NumApprovalsThreshold: 1,
UnlockDuration: 0,
}, Value(msBalance), Nonce(0))
v.Messages.ApplyOne(msg)
v.Assert.Equal(msg.Result.ExitCode, exitcode.Ok)