484 lines
17 KiB
Go
484 lines
17 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
//"fmt"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/lotus/chain/vm"
|
|
"github.com/filecoin-project/specs-actors/actors/abi"
|
|
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
|
builtin "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/builtin/paych"
|
|
"github.com/filecoin-project/specs-actors/actors/runtime"
|
|
typegen "github.com/whyrusleeping/cbor-gen"
|
|
//"github.com/filecoin-project/specs-actors/actors/crypto"
|
|
"github.com/filecoin-project/specs-actors/actors/builtin/reward"
|
|
"github.com/filecoin-project/specs-actors/actors/puppet"
|
|
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
|
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
|
|
|
. "github.com/filecoin-project/oni/tvx/builders"
|
|
"github.com/filecoin-project/oni/tvx/schema"
|
|
//"github.com/davecgh/go-spew/spew"
|
|
)
|
|
|
|
var (
|
|
acctDefaultBalance = abi.NewTokenAmount(1_000_000_000_000)
|
|
multisigBalance = abi.NewTokenAmount(1_000_000_000)
|
|
nonce = uint64(1)
|
|
PuppetAddress address.Address
|
|
)
|
|
|
|
func init() {
|
|
var err error
|
|
// the address before the burnt funds address
|
|
PuppetAddress, err = address.NewIDAddress(builtin.FirstNonSingletonActorId - 2)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
nestedSends_OkBasic()
|
|
nestedSends_OkToNewActor()
|
|
nestedSends_OkToNewActorWithInvoke()
|
|
nestedSends_OkRecursive()
|
|
nestedSends_OKNonCBORParamsWithTransfer()
|
|
|
|
// TODO: Tests to exercise invalid "syntax" of the inner message.
|
|
// These would fail message syntax validation if the message were top-level.
|
|
//
|
|
// Some of these require handcrafting the proposal params serialization.
|
|
// - malformed address: zero-length, one-length, too-short pubkeys, invalid UVarints, ...
|
|
// - negative method num
|
|
//
|
|
// Unfortunately the multisig actor can't be used to trigger a negative-value internal transfer because
|
|
// it checks just before sending.
|
|
// We need a custom actor for staging whackier messages.
|
|
|
|
//
|
|
// The following tests exercise invalid semantics of the inner message
|
|
//
|
|
|
|
nestedSends_FailNonexistentIDAddress()
|
|
nestedSends_FailNonexistentActorAddress()
|
|
nestedSends_FailInvalidMethodNumNewActor()
|
|
nestedSends_FailInvalidMethodNumForActor()
|
|
|
|
// The multisig actor checks before attempting to transfer more than its balance, so we can't exercise that
|
|
// the VM also checks this. Need a custome actor to exercise this.
|
|
//t.Run("fail insufficient funds", func(t *testing.T) {
|
|
// td := builder.Build(t)
|
|
// defer td.Complete()
|
|
//
|
|
// stage := prepareStage(td, acctDefaultBalance, multisigBalance)
|
|
// balanceBefore := td.GetBalance(stage.creator)
|
|
//
|
|
// // Attempt to transfer from the multisig more than the balance it has.
|
|
// // The proposal to do should succeed, but the inner message fail.
|
|
// amtSent := big.Add(multisigBalance, abi.NewTokenAmount(1))
|
|
// result := stage.send(stage.creator, amtSent, builtin.MethodSend, nil, nonce)
|
|
// assert.Equal(t, exitcode_spec.Ok, result.Receipt.ExitCode)
|
|
//
|
|
// td.AssertBalance(stage.msAddr, multisigBalance) // No change.
|
|
// td.AssertBalance(stage.creator, big.Sub(balanceBefore, result.Receipt.GasUsed.Big())) // Pay gas, don't receive funds.
|
|
//})
|
|
|
|
nestedSends_FailMissingParams()
|
|
nestedSends_FailMismatchParams()
|
|
nestedSends_FailInnerAbort()
|
|
nestedSends_FailAbortedExec()
|
|
nestedSends_FailInsufficientFundsForTransferInInnerSend()
|
|
}
|
|
|
|
func nestedSends_OkBasic() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-ok-basic", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
balanceBefore := v.Actors.Balance(stage.creator)
|
|
|
|
// Multisig sends back to the creator.
|
|
amtSent := abi.NewTokenAmount(1)
|
|
result := stage.sendOk(stage.creator, amtSent, builtin.MethodSend, nil, nonce)
|
|
|
|
//td.AssertActor(stage.creator, big.Sub(big.Add(balanceBefore, amtSent), result.Receipt.GasUsed.Big()), nonce+1)
|
|
v.Assert.NonceEq(stage.creator, nonce+1)
|
|
v.Assert.BalanceEq(stage.creator, big.Sub(big.Add(balanceBefore, amtSent), big.NewInt(result.MessageReceipt.GasUsed)))
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_OkToNewActor() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-ok-to-new-actor", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
balanceBefore := v.Actors.Balance(stage.creator)
|
|
|
|
// Multisig sends to new address.
|
|
newAddr := v.Wallet.NewSECP256k1Account()
|
|
amtSent := abi.NewTokenAmount(1)
|
|
result := stage.sendOk(newAddr, amtSent, builtin.MethodSend, nil, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, big.Sub(multisigBalance, amtSent))
|
|
v.Assert.BalanceEq(stage.creator, big.Sub(balanceBefore, big.NewInt(result.MessageReceipt.GasUsed)))
|
|
v.Assert.BalanceEq(newAddr, amtSent)
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_OkToNewActorWithInvoke() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-ok-to-new-actor-with-invoke", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
balanceBefore := v.Actors.Balance(stage.creator)
|
|
|
|
// Multisig sends to new address and invokes pubkey method at the same time.
|
|
newAddr := v.Wallet.NewSECP256k1Account()
|
|
amtSent := abi.NewTokenAmount(1)
|
|
result := stage.sendOk(newAddr, amtSent, builtin.MethodsAccount.PubkeyAddress, nil, nonce)
|
|
// TODO: use an explicit Approve() and check the return value is the correct pubkey address
|
|
// when the multisig Approve() method plumbs through the inner exit code and value.
|
|
// https://github.com/filecoin-project/specs-actors/issues/113
|
|
//expected := bytes.Buffer{}
|
|
//require.NoError(t, newAddr.MarshalCBOR(&expected))
|
|
//assert.Equal(t, expected.Bytes(), result.Receipt.ReturnValue)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, big.Sub(multisigBalance, amtSent))
|
|
v.Assert.BalanceEq(stage.creator, big.Sub(balanceBefore, big.NewInt(result.MessageReceipt.GasUsed)))
|
|
v.Assert.BalanceEq(newAddr, amtSent)
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_OkRecursive() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-ok-recursive", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
another := v.Actors.Account(address.SECP256K1, big.Zero())
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
balanceBefore := v.Actors.Balance(stage.creator)
|
|
|
|
// Multisig sends to itself.
|
|
params := multisig.AddSignerParams{
|
|
Signer: another.ID,
|
|
Increase: false,
|
|
}
|
|
result := stage.sendOk(stage.msAddr, big.Zero(), builtin.MethodsMultisig.AddSigner, ¶ms, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, multisigBalance)
|
|
v.Assert.Equal(big.Sub(balanceBefore, big.NewInt(result.MessageReceipt.GasUsed)), v.Actors.Balance(stage.creator))
|
|
|
|
var st multisig.State
|
|
v.Actors.ActorState(stage.msAddr, &st)
|
|
v.Assert.Equal([]address.Address{stage.creator, another.ID}, st.Signers)
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_OKNonCBORParamsWithTransfer() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-ok-non-cbor-params-with-transfer", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
|
|
newAddr := v.Wallet.NewSECP256k1Account()
|
|
amtSent := abi.NewTokenAmount(1)
|
|
// So long as the parameters are not actually used by the method, a message can carry arbitrary bytes.
|
|
params := typegen.Deferred{Raw: []byte{1, 2, 3, 4}}
|
|
stage.sendOk(newAddr, amtSent, builtin.MethodSend, ¶ms, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, big.Sub(multisigBalance, amtSent))
|
|
v.Assert.BalanceEq(newAddr, amtSent)
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_FailNonexistentIDAddress() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-fail-nonexistent-id-address", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
|
|
newAddr := MustNewIDAddr(1234)
|
|
amtSent := abi.NewTokenAmount(1)
|
|
stage.sendOk(newAddr, amtSent, builtin.MethodSend, nil, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
|
v.Assert.ActorMissing(newAddr)
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_FailNonexistentActorAddress() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-fail-nonexistent-actor-address", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
|
|
newAddr := MustNewActorAddr("1234")
|
|
amtSent := abi.NewTokenAmount(1)
|
|
stage.sendOk(newAddr, amtSent, builtin.MethodSend, nil, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
|
v.Assert.ActorMissing(newAddr)
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_FailInvalidMethodNumNewActor() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-fail-invalid-methodnum-new-actor", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
|
|
newAddr := v.Wallet.NewSECP256k1Account()
|
|
amtSent := abi.NewTokenAmount(1)
|
|
stage.sendOk(newAddr, amtSent, abi.MethodNum(99), nil, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
|
v.Assert.ActorMissing(newAddr)
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_FailInvalidMethodNumForActor() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-fail-invalid-methodnum-for-actor", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
balanceBefore := v.Actors.Balance(stage.creator)
|
|
|
|
amtSent := abi.NewTokenAmount(1)
|
|
result := stage.sendOk(stage.creator, amtSent, abi.MethodNum(99), nil, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
|
v.Assert.BalanceEq(stage.creator, big.Sub(balanceBefore, big.NewInt(result.MessageReceipt.GasUsed))) // Pay gas, don't receive funds.
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_FailMissingParams() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-fail-missing-params", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
balanceBefore := v.Actors.Balance(stage.creator)
|
|
|
|
params := adt.Empty // Missing params required by AddSigner
|
|
amtSent := abi.NewTokenAmount(1)
|
|
result := stage.sendOk(stage.msAddr, amtSent, builtin.MethodsMultisig.AddSigner, params, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.creator, big.Sub(balanceBefore, big.NewInt(result.MessageReceipt.GasUsed)))
|
|
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
|
v.Assert.Equal(1, len(stage.state().Signers)) // No new signers
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_FailMismatchParams() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-fail-mismatched-params", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
balanceBefore := v.Actors.Balance(stage.creator)
|
|
|
|
// Wrong params for AddSigner
|
|
params := multisig.ProposeParams{
|
|
To: stage.creator,
|
|
Value: big.Zero(),
|
|
Method: builtin.MethodSend,
|
|
Params: nil,
|
|
}
|
|
amtSent := abi.NewTokenAmount(1)
|
|
result := stage.sendOk(stage.msAddr, amtSent, builtin.MethodsMultisig.AddSigner, ¶ms, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.creator, big.Sub(balanceBefore, big.NewInt(result.MessageReceipt.GasUsed)))
|
|
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
|
v.Assert.Equal(1, len(stage.state().Signers)) // No new signers
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_FailInnerAbort() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-fail-inner-abort", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
prevHead := v.Actors.Head(builtin.RewardActorAddr)
|
|
|
|
// AwardBlockReward will abort unless invoked by the system actor
|
|
params := reward.AwardBlockRewardParams{
|
|
Miner: stage.creator,
|
|
Penalty: big.Zero(),
|
|
GasReward: big.Zero(),
|
|
}
|
|
amtSent := abi.NewTokenAmount(1)
|
|
stage.sendOk(builtin.RewardActorAddr, amtSent, builtin.MethodsReward.AwardBlockReward, ¶ms, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
|
v.Assert.HeadEq(builtin.RewardActorAddr, prevHead)
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_FailAbortedExec() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-fail-aborted-exec", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
stage := prepareStage(v, acctDefaultBalance, multisigBalance)
|
|
prevHead := v.Actors.Head(builtin.InitActorAddr)
|
|
|
|
// Illegal paych constructor params (addresses are not accounts)
|
|
ctorParams := paych.ConstructorParams{
|
|
From: builtin.SystemActorAddr,
|
|
To: builtin.SystemActorAddr,
|
|
}
|
|
execParams := init_.ExecParams{
|
|
CodeCID: builtin.PaymentChannelActorCodeID,
|
|
ConstructorParams: MustSerialize(&ctorParams),
|
|
}
|
|
|
|
amtSent := abi.NewTokenAmount(1)
|
|
stage.sendOk(builtin.InitActorAddr, amtSent, builtin.MethodsInit.Exec, &execParams, nonce)
|
|
|
|
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
|
v.Assert.HeadEq(builtin.InitActorAddr, prevHead) // Init state unchanged.
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
func nestedSends_FailInsufficientFundsForTransferInInnerSend() {
|
|
metadata := &schema.Metadata{ID: "nested-sends-fail-insufficient-funds-for-transfer-in-inner-send", Version: "v1", Desc: ""}
|
|
|
|
v := MessageVector(metadata)
|
|
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPrice(1))
|
|
|
|
// puppet actor has zero funds
|
|
puppetBalance := big.Zero()
|
|
_ = v.Actors.CreateActor(puppet.PuppetActorCodeID, PuppetAddress, puppetBalance, &puppet.State{})
|
|
|
|
alice := v.Actors.Account(address.SECP256K1, acctDefaultBalance)
|
|
bob := v.Actors.Account(address.SECP256K1, big.Zero())
|
|
|
|
v.CommitPreconditions()
|
|
|
|
// alice tells the puppet actor to send funds to bob, the puppet actor has 0 balance so the inner send will fail,
|
|
// and alice will pay the gas cost.
|
|
amtSent := abi.NewTokenAmount(1)
|
|
msg := v.Messages.Typed(alice.ID, PuppetAddress, PuppetSend(&puppet.SendParams{
|
|
To: bob.ID,
|
|
Value: amtSent,
|
|
Method: builtin.MethodSend,
|
|
Params: nil,
|
|
}), Nonce(0), Value(big.Zero()))
|
|
|
|
v.Messages.ApplyOne(msg)
|
|
|
|
v.CommitApplies()
|
|
|
|
// the outer message should be applied successfully
|
|
v.Assert.Equal(exitcode.Ok, msg.Result.ExitCode)
|
|
|
|
var puppetRet puppet.SendReturn
|
|
MustDeserialize(msg.Result.MessageReceipt.Return, &puppetRet)
|
|
|
|
// the inner message should fail
|
|
v.Assert.Equal(exitcode.SysErrInsufficientFunds, puppetRet.Code)
|
|
|
|
// alice should be charged for the gas cost and bob should have not received any funds.
|
|
v.Assert.BalanceEq(alice.ID, big.Sub(acctDefaultBalance, big.NewInt(msg.Result.GasUsed)))
|
|
v.Assert.BalanceEq(bob.ID, big.Zero())
|
|
|
|
v.Finish(os.Stdout)
|
|
}
|
|
|
|
type msStage struct {
|
|
v *Builder
|
|
creator address.Address // Address of the creator and sole signer of the multisig.
|
|
msAddr address.Address // Address of the multisig actor from which nested messages are sent.
|
|
}
|
|
|
|
// Creates a multisig actor with its creator as sole approver.
|
|
func prepareStage(v *Builder, creatorBalance, msBalance abi.TokenAmount) *msStage {
|
|
// Set up sender and receiver accounts.
|
|
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))
|
|
v.Messages.ApplyOne(msg)
|
|
|
|
v.Assert.Equal(msg.Result.ExitCode, exitcode.Ok)
|
|
|
|
// Verify init actor return.
|
|
var ret init_.ExecReturn
|
|
MustDeserialize(msg.Result.Return, &ret)
|
|
|
|
return &msStage{
|
|
v: v,
|
|
creator: creator.ID,
|
|
msAddr: ret.IDAddress,
|
|
}
|
|
}
|
|
|
|
func (s *msStage) sendOk(to address.Address, value abi.TokenAmount, method abi.MethodNum, params runtime.CBORMarshaler, approverNonce uint64) *vm.ApplyRet {
|
|
buf := bytes.Buffer{}
|
|
if params != nil {
|
|
err := params.MarshalCBOR(&buf)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
pparams := multisig.ProposeParams{
|
|
To: to,
|
|
Value: value,
|
|
Method: method,
|
|
Params: buf.Bytes(),
|
|
}
|
|
msg := s.v.Messages.Typed(s.creator, s.msAddr, MultisigPropose(&pparams), Nonce(approverNonce), Value(big.NewInt(0)))
|
|
s.v.CommitApplies()
|
|
|
|
// all messages succeeded.
|
|
s.v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.Ok))
|
|
|
|
return msg.Result
|
|
}
|
|
|
|
func (s *msStage) state() *multisig.State {
|
|
var msState multisig.State
|
|
s.v.Actors.ActorState(s.msAddr, &msState)
|
|
return &msState
|
|
}
|