migrate multisig suite. (#241)
This commit is contained in:
parent
328cd14897
commit
6164d16f19
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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...)
|
||||
}
|
||||
|
||||
|
51
tvx/scripts/multisig/main.go
Normal file
51
tvx/scripts/multisig/main.go
Normal 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
305
tvx/scripts/multisig/ok.go
Normal 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
|
||||
}
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user