363 lines
11 KiB
Go
363 lines
11 KiB
Go
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
|
|
}
|