transfer builders and scripts to filecoin-project/test-vectors. (#242)
This commit is contained in:
parent
6164d16f19
commit
497bda21b2
@ -52,21 +52,6 @@ jobs:
|
||||
- run:
|
||||
name: "build tvx"
|
||||
command: pushd tvx && go build .
|
||||
- run:
|
||||
name: "run messages test vector suite: msg_application"
|
||||
command: pushd tvx/scripts/msg_application && go build . && ./msg_application | ../../tvx exec-lotus
|
||||
- run:
|
||||
name: "run messages test vector suite: nested send"
|
||||
command: pushd tvx/scripts/nested && go build . && ./nested | ../../tvx exec-lotus
|
||||
- run:
|
||||
name: "run messages test vector suite: paych"
|
||||
command: pushd tvx/scripts/paych && go build . && ./paych | ../../tvx exec-lotus
|
||||
- run:
|
||||
name: "run messages test vector suite: actor_creation"
|
||||
command: pushd tvx/scripts/actor_creation && go build . && ./actor_creation | ../../tvx exec-lotus
|
||||
- run:
|
||||
name: "run messages test vector suite: transfer"
|
||||
command: pushd tvx/scripts/transfer && go build . && ./transfer | ../../tvx exec-lotus
|
||||
soup-build-linux:
|
||||
executor: linux
|
||||
steps:
|
||||
|
@ -1,261 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"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"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/account"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
"github.com/ipfs/go-cid"
|
||||
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 []registeredActor
|
||||
|
||||
b *Builder
|
||||
}
|
||||
|
||||
func newActors(b *Builder) *Actors {
|
||||
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 _, r := range a.registered {
|
||||
if r.handle.ID == addr || r.handle.Robust == addr {
|
||||
return r.handle
|
||||
}
|
||||
}
|
||||
a.b.Assert.FailNowf("asked for handle of unknown actor", "actor: %s", addr)
|
||||
return AddressHandle{} // will never reach here.
|
||||
}
|
||||
|
||||
// InitialBalance returns the initial balance of an actor that was registered
|
||||
// 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 {
|
||||
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
|
||||
// specified balance, and places their addresses in the supplied AddressHandles.
|
||||
func (a *Actors) AccountN(typ address.Protocol, balance abi.TokenAmount, handles ...*AddressHandle) {
|
||||
for _, handle := range handles {
|
||||
h := a.Account(typ, balance)
|
||||
*handle = h
|
||||
}
|
||||
}
|
||||
|
||||
// Account creates a single account actor of the specified kind, with the
|
||||
// specified balance, and returns its AddressHandle.
|
||||
func (a *Actors) Account(typ address.Protocol, balance abi.TokenAmount) AddressHandle {
|
||||
a.b.Assert.In(typ, address.SECP256K1, address.BLS)
|
||||
|
||||
var addr address.Address
|
||||
switch typ {
|
||||
case address.SECP256K1:
|
||||
addr = a.b.Wallet.NewSECP256k1Account()
|
||||
case address.BLS:
|
||||
addr = a.b.Wallet.NewBLSAccount()
|
||||
}
|
||||
|
||||
actorState := &account.State{Address: addr}
|
||||
handle := a.CreateActor(builtin.AccountActorCodeID, addr, balance, actorState)
|
||||
|
||||
a.registered = append(a.registered, registeredActor{handle, balance})
|
||||
return handle
|
||||
}
|
||||
|
||||
type MinerActorCfg struct {
|
||||
SealProofType abi.RegisteredSealProof
|
||||
PeriodBoundary abi.ChainEpoch
|
||||
OwnerBalance abi.TokenAmount
|
||||
}
|
||||
|
||||
// Miner creates an owner account, a worker account, and a miner actor managed
|
||||
// by those initial.
|
||||
func (a *Actors) Miner(cfg MinerActorCfg) (minerActor, owner, worker AddressHandle) {
|
||||
owner = a.Account(address.SECP256K1, cfg.OwnerBalance)
|
||||
worker = a.Account(address.BLS, big.Zero())
|
||||
// expectedMinerActorIDAddress := chain.MustNewIDAddr(chain.MustIDFromAddress(minerWorkerID) + 1)
|
||||
// minerActorAddrs := computeInitActorExecReturn(minerWorkerPk, 0, 1, expectedMinerActorIDAddress)
|
||||
|
||||
ss, err := cfg.SealProofType.SectorSize()
|
||||
a.b.Assert.NoError(err, "seal proof sector size")
|
||||
|
||||
ps, err := cfg.SealProofType.WindowPoStPartitionSectors()
|
||||
a.b.Assert.NoError(err, "seal proof window PoSt partition sectors")
|
||||
|
||||
mi := &miner.MinerInfo{
|
||||
Owner: owner.ID,
|
||||
Worker: worker.ID,
|
||||
PendingWorkerKey: nil,
|
||||
PeerId: abi.PeerID("chain-validation"),
|
||||
Multiaddrs: nil,
|
||||
SealProofType: cfg.SealProofType,
|
||||
SectorSize: ss,
|
||||
WindowPoStPartitionSectors: ps,
|
||||
}
|
||||
infoCid, err := a.b.Stores.CBORStore.Put(context.Background(), mi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create the miner actor s.t. it exists in the init actors map
|
||||
minerState, err := miner.ConstructState(infoCid,
|
||||
cfg.PeriodBoundary,
|
||||
EmptyBitfieldCid,
|
||||
EmptyArrayCid,
|
||||
EmptyMapCid,
|
||||
EmptyDeadlinesCid,
|
||||
EmptyVestingFundsCid,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
minerActorAddr := worker.NextActorAddress(0, 0)
|
||||
handle := a.CreateActor(builtin.StorageMinerActorCodeID, minerActorAddr, big.Zero(), minerState)
|
||||
|
||||
// assert miner actor has been created, exists in the state tree, and has an entry in the init actor.
|
||||
// next update the storage power actor to track the miner
|
||||
|
||||
var spa power.State
|
||||
a.ActorState(builtin.StoragePowerActorAddr, &spa)
|
||||
|
||||
// set the miners claim
|
||||
hm, err := adt.AsMap(adt.WrapStore(context.Background(), a.b.Stores.CBORStore), spa.Claims)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// add claim for the miner
|
||||
err = hm.Put(adt.AddrKey(handle.ID), &power.Claim{
|
||||
RawBytePower: abi.NewStoragePower(0),
|
||||
QualityAdjPower: abi.NewTokenAmount(0),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// save the claim
|
||||
spa.Claims, err = hm.Root()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// update miner count
|
||||
spa.MinerCount += 1
|
||||
|
||||
// update storage power actor's state in the tree
|
||||
_, err = a.b.Stores.CBORStore.Put(context.Background(), &spa)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
a.registered = append(a.registered, registeredActor{handle, big.Zero()})
|
||||
return handle, owner, worker
|
||||
}
|
||||
|
||||
// CreateActor creates an actor in the state tree, of the specified kind, with
|
||||
// the specified address and balance, and sets its state to the supplied state.
|
||||
func (a *Actors) CreateActor(code cid.Cid, addr address.Address, balance abi.TokenAmount, state runtime.CBORMarshaler) AddressHandle {
|
||||
var id address.Address
|
||||
if addr.Protocol() != address.ID {
|
||||
var err error
|
||||
id, err = a.b.StateTree.RegisterNewAddress(addr)
|
||||
if err != nil {
|
||||
log.Panicf("register new address for actor: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Store the new state.
|
||||
head, err := a.b.StateTree.Store.Put(context.Background(), state)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Set the actor's head to point to that state.
|
||||
actr := &types.Actor{
|
||||
Code: code,
|
||||
Head: head,
|
||||
Balance: balance,
|
||||
}
|
||||
if err := a.b.StateTree.SetActor(addr, actr); err != nil {
|
||||
log.Panicf("setting new actor for actor: %v", err)
|
||||
}
|
||||
return AddressHandle{id, addr}
|
||||
}
|
||||
|
||||
// ActorState retrieves the state of the supplied actor, and sets it in the
|
||||
// provided object. It also returns the actor's header from the state tree.
|
||||
func (a *Actors) ActorState(addr address.Address, out cbg.CBORUnmarshaler) *types.Actor {
|
||||
actor := a.Header(addr)
|
||||
err := a.b.StateTree.Store.Get(context.Background(), actor.Head, out)
|
||||
a.b.Assert.NoError(err, "failed to load state for actorr %s; head=%s", addr, actor.Head)
|
||||
return actor
|
||||
}
|
||||
|
||||
// Header returns the actor's header from the state tree.
|
||||
func (a *Actors) Header(addr address.Address) *types.Actor {
|
||||
actor, err := a.b.StateTree.GetActor(addr)
|
||||
a.b.Assert.NoError(err, "failed to fetch actor %s from state", addr)
|
||||
return actor
|
||||
}
|
||||
|
||||
// Balance is a shortcut for Header(addr).Balance.
|
||||
func (a *Actors) Balance(addr address.Address) abi.TokenAmount {
|
||||
return a.Header(addr).Balance
|
||||
}
|
||||
|
||||
// Head is a shortcut for Header(addr).Head.
|
||||
func (a *Actors) Head(addr address.Address) cid.Cid {
|
||||
return a.Header(addr).Head
|
||||
}
|
||||
|
||||
// Nonce is a shortcut for Header(addr).Nonce.
|
||||
func (a *Actors) Nonce(addr address.Address) uint64 {
|
||||
return a.Header(addr).Nonce
|
||||
}
|
||||
|
||||
// Code is a shortcut for Header(addr).Code.
|
||||
func (a *Actors) Code(addr address.Address) cid.Cid {
|
||||
return a.Header(addr).Code
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/chain/actors/aerrors"
|
||||
"github.com/multiformats/go-varint"
|
||||
)
|
||||
|
||||
// AddressHandle encapsulates both the ID and Robust addresses of an actor.
|
||||
type AddressHandle struct {
|
||||
ID, Robust address.Address
|
||||
}
|
||||
|
||||
func (ah AddressHandle) IDAddr() address.Address {
|
||||
return ah.ID
|
||||
}
|
||||
|
||||
func (ah AddressHandle) RobustAddr() address.Address {
|
||||
return ah.Robust
|
||||
}
|
||||
|
||||
func (ah AddressHandle) String() string {
|
||||
return fmt.Sprintf("AddressHandle[ID: %s, Robust: %s]", ah.ID, ah.Robust)
|
||||
}
|
||||
|
||||
// NextActorAddress predicts the address of the next actor created by this address.
|
||||
//
|
||||
// Code is adapted from vm.Runtime#NewActorAddress()
|
||||
func (ah *AddressHandle) NextActorAddress(nonce, numActorsCreated uint64) address.Address {
|
||||
var b bytes.Buffer
|
||||
if err := ah.Robust.MarshalCBOR(&b); err != nil {
|
||||
panic(aerrors.Fatalf("writing caller address into assert buffer: %v", err))
|
||||
}
|
||||
|
||||
if err := binary.Write(&b, binary.BigEndian, nonce); err != nil {
|
||||
panic(aerrors.Fatalf("writing nonce address into assert buffer: %v", err))
|
||||
}
|
||||
if err := binary.Write(&b, binary.BigEndian, numActorsCreated); err != nil {
|
||||
panic(aerrors.Fatalf("writing callSeqNum address into assert buffer: %v", err))
|
||||
}
|
||||
addr, err := address.NewActorAddress(b.Bytes())
|
||||
if err != nil {
|
||||
panic(aerrors.Fatalf("create actor address: %v", err))
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// MustNewIDAddr returns an address.Address of kind ID.
|
||||
func MustNewIDAddr(id uint64) address.Address {
|
||||
addr, err := address.NewIDAddress(id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// MustNewSECP256K1Addr returns an address.Address of kind secp256k1.
|
||||
func MustNewSECP256K1Addr(pubkey string) address.Address {
|
||||
// the pubkey of assert secp256k1 address is hashed for consistent length.
|
||||
addr, err := address.NewSecp256k1Address([]byte(pubkey))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// MustNewBLSAddr returns an address.Address of kind bls.
|
||||
func MustNewBLSAddr(seed int64) address.Address {
|
||||
buf := make([]byte, address.BlsPublicKeyBytes)
|
||||
binary.PutVarint(buf, seed)
|
||||
|
||||
addr, err := address.NewBLSAddress(buf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// MustNewActorAddr returns an address.Address of kind actor.
|
||||
func MustNewActorAddr(data string) address.Address {
|
||||
addr, err := address.NewActorAddress([]byte(data))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// MustIDFromAddress returns the integer ID from an ID address.
|
||||
func MustIDFromAddress(a address.Address) uint64 {
|
||||
if a.Protocol() != address.ID {
|
||||
panic("must be ID protocol address")
|
||||
}
|
||||
id, _, err := varint.FromUvarint(a.Payload())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/chain/state"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Asserter offers useful assertions to verify outcomes at various stages of
|
||||
// the test vector creation.
|
||||
type Asserter struct {
|
||||
*require.Assertions
|
||||
|
||||
b *Builder
|
||||
stage Stage
|
||||
}
|
||||
|
||||
var _ require.TestingT = &Asserter{}
|
||||
|
||||
func newAsserter(b *Builder, stage Stage) *Asserter {
|
||||
a := &Asserter{stage: stage, b: b}
|
||||
a.Assertions = require.New(a)
|
||||
return a
|
||||
}
|
||||
|
||||
// In is assert fluid version of require.Contains. It inverts the argument order,
|
||||
// such that the admissible set can be supplied through assert variadic argument.
|
||||
func (a *Asserter) In(v interface{}, set ...interface{}) {
|
||||
a.Contains(set, v, "set %v does not contain element %v", set, v)
|
||||
}
|
||||
|
||||
// BalanceEq verifies that the balance of the address equals the expected one.
|
||||
func (a *Asserter) BalanceEq(addr address.Address, expected abi.TokenAmount) {
|
||||
actor, err := a.b.StateTree.GetActor(addr)
|
||||
a.NoError(err, "failed to fetch actor %s from state", addr)
|
||||
a.Equal(expected, actor.Balance, "balances mismatch for address %s", addr)
|
||||
}
|
||||
|
||||
// NonceEq verifies that the nonce of the actor equals the expected one.
|
||||
func (a *Asserter) NonceEq(addr address.Address, expected uint64) {
|
||||
actor, err := a.b.StateTree.GetActor(addr)
|
||||
a.NoError(err, "failed to fetch actor %s from state", addr)
|
||||
a.Equal(expected, actor.Nonce, "expected actor %s nonce: %d, got: %d", addr, expected, actor.Nonce)
|
||||
}
|
||||
|
||||
// HeadEq verifies that the head of the actor equals the expected one.
|
||||
func (a *Asserter) HeadEq(addr address.Address, expected cid.Cid) {
|
||||
actor, err := a.b.StateTree.GetActor(addr)
|
||||
a.NoError(err, "failed to fetch actor %s from state", addr)
|
||||
a.Equal(expected, actor.Head, "expected actor %s head: %v, got: %v", addr, expected, actor.Head)
|
||||
}
|
||||
|
||||
// ActorExists verifies that the actor exists in the state tree.
|
||||
func (a *Asserter) ActorExists(addr address.Address) {
|
||||
_, err := a.b.StateTree.GetActor(addr)
|
||||
a.NoError(err, "expected no error while looking up actor %s", addr)
|
||||
}
|
||||
|
||||
// ActorExists verifies that the actor is absent from the state tree.
|
||||
func (a *Asserter) ActorMissing(addr address.Address) {
|
||||
_, err := a.b.StateTree.GetActor(addr)
|
||||
a.Error(err, "expected error while looking up actor %s", addr)
|
||||
}
|
||||
|
||||
// EveryMessageResultSatisfies verifies that every message result satisfies the
|
||||
// provided predicate.
|
||||
func (a *Asserter) EveryMessageResultSatisfies(predicate ApplyRetPredicate, except ...*ApplicableMessage) {
|
||||
exceptm := make(map[*ApplicableMessage]struct{}, len(except))
|
||||
for _, am := range except {
|
||||
exceptm[am] = struct{}{}
|
||||
}
|
||||
for i, m := range a.b.Messages.messages {
|
||||
if _, ok := exceptm[m]; ok {
|
||||
continue
|
||||
}
|
||||
err := predicate(m.Result)
|
||||
a.NoError(err, "message result predicate failed on message %d: %s", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// EveryMessageSenderSatisfies verifies that the sender actors of the supplied
|
||||
// messages match a condition.
|
||||
//
|
||||
// This function groups ApplicableMessages by sender actor, and calls the
|
||||
// predicate for each unique sender, passing in the initial state (when
|
||||
// preconditions were committed), the final state (could be nil), and the
|
||||
// ApplicableMessages themselves.
|
||||
func (a *Asserter) MessageSendersSatisfy(predicate ActorPredicate, ams ...*ApplicableMessage) {
|
||||
bysender := make(map[AddressHandle][]*ApplicableMessage, len(ams))
|
||||
for _, am := range ams {
|
||||
h := a.b.Actors.HandleFor(am.Message.From)
|
||||
bysender[h] = append(bysender[h], am)
|
||||
}
|
||||
// we now have messages organized by unique senders.
|
||||
for sender, amss := range bysender {
|
||||
// get precondition state
|
||||
pretree, err := state.LoadStateTree(a.b.Stores.CBORStore, a.b.PreRoot)
|
||||
a.NoError(err)
|
||||
prestate, err := pretree.GetActor(sender.Robust)
|
||||
a.NoError(err)
|
||||
|
||||
// get postcondition state; if actor has been deleted, we store a nil.
|
||||
poststate, _ := a.b.StateTree.GetActor(sender.Robust)
|
||||
|
||||
// invoke predicate.
|
||||
err = predicate(sender, prestate, poststate, amss)
|
||||
a.NoError(err, "'every sender actor' predicate failed for sender %s: %s", sender, err)
|
||||
}
|
||||
}
|
||||
|
||||
// EveryMessageSenderSatisfies is sugar for MessageSendersSatisfy(predicate, Messages.All()),
|
||||
// but supports an exclusion set to restrict the messages that will actually be asserted.
|
||||
func (a *Asserter) EveryMessageSenderSatisfies(predicate ActorPredicate, except ...*ApplicableMessage) {
|
||||
ams := a.b.Messages.All()
|
||||
if len(except) > 0 {
|
||||
filtered := ams[:0]
|
||||
for _, ex := range except {
|
||||
for _, am := range ams {
|
||||
if am == ex {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, am)
|
||||
}
|
||||
}
|
||||
ams = filtered
|
||||
}
|
||||
a.MessageSendersSatisfy(predicate, ams...)
|
||||
}
|
||||
|
||||
func (a *Asserter) FailNow() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (a *Asserter) Errorf(format string, args ...interface{}) {
|
||||
id := a.b.vector.Meta.ID
|
||||
stage := a.stage
|
||||
fmt.Printf("❌ id: %s, stage: %s:"+format, append([]interface{}{id, stage}, args...)...)
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/state"
|
||||
"github.com/ipfs/go-cid"
|
||||
format "github.com/ipfs/go-ipld-format"
|
||||
"github.com/ipld/go-car"
|
||||
|
||||
"github.com/filecoin-project/oni/tvx/lotus"
|
||||
"github.com/filecoin-project/oni/tvx/schema"
|
||||
ostate "github.com/filecoin-project/oni/tvx/state"
|
||||
)
|
||||
|
||||
type Stage string
|
||||
|
||||
const (
|
||||
StagePreconditions = Stage("preconditions")
|
||||
StageApplies = Stage("applies")
|
||||
StageChecks = Stage("checks")
|
||||
StageFinished = Stage("finished")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// disable logs, as we need a clean stdout output.
|
||||
log.SetOutput(os.Stderr)
|
||||
log.SetPrefix(">>> ")
|
||||
|
||||
_ = os.Setenv("LOTUS_DISABLE_VM_BUF", "iknowitsabadidea")
|
||||
}
|
||||
|
||||
// TODO use stage.Surgeon with non-proxying blockstore.
|
||||
type Builder struct {
|
||||
Actors *Actors
|
||||
Assert *Asserter
|
||||
Messages *Messages
|
||||
Driver *lotus.Driver
|
||||
PreRoot cid.Cid
|
||||
PostRoot cid.Cid
|
||||
CurrRoot cid.Cid
|
||||
Wallet *Wallet
|
||||
StateTree *state.StateTree
|
||||
Stores *ostate.Stores
|
||||
|
||||
vector schema.TestVector
|
||||
stage Stage
|
||||
}
|
||||
|
||||
// MessageVector creates a builder for a message-class vector.
|
||||
func MessageVector(metadata *schema.Metadata) *Builder {
|
||||
stores := ostate.NewLocalStores(context.Background())
|
||||
|
||||
// Create a brand new state tree.
|
||||
st, err := state.NewStateTree(stores.CBORStore)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
b := &Builder{
|
||||
stage: StagePreconditions,
|
||||
Stores: stores,
|
||||
StateTree: st,
|
||||
PreRoot: cid.Undef,
|
||||
Driver: lotus.NewDriver(context.Background()),
|
||||
}
|
||||
|
||||
b.Wallet = newWallet()
|
||||
b.Assert = newAsserter(b, StagePreconditions)
|
||||
b.Actors = newActors(b)
|
||||
b.Messages = &Messages{b: b}
|
||||
|
||||
b.vector.Class = schema.ClassMessage
|
||||
b.vector.Meta = metadata
|
||||
b.vector.Pre = &schema.Preconditions{}
|
||||
b.vector.Post = &schema.Postconditions{}
|
||||
|
||||
b.initializeZeroState()
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Builder) CommitPreconditions() {
|
||||
if b.stage != StagePreconditions {
|
||||
panic("called CommitPreconditions at the wrong time")
|
||||
}
|
||||
|
||||
// capture the preroot after applying all preconditions.
|
||||
preroot := b.FlushState()
|
||||
|
||||
b.vector.Pre.Epoch = 0
|
||||
b.vector.Pre.StateTree = &schema.StateTree{RootCID: preroot}
|
||||
|
||||
b.CurrRoot, b.PreRoot = preroot, preroot
|
||||
b.stage = StageApplies
|
||||
b.Assert = newAsserter(b, StageApplies)
|
||||
}
|
||||
|
||||
func (b *Builder) CommitApplies() {
|
||||
if b.stage != StageApplies {
|
||||
panic("called CommitApplies at the wrong time")
|
||||
}
|
||||
|
||||
for _, am := range b.Messages.All() {
|
||||
// apply all messages that are pending application.
|
||||
if am.Result == nil {
|
||||
b.applyMessage(am)
|
||||
}
|
||||
}
|
||||
|
||||
b.PostRoot = b.CurrRoot
|
||||
b.vector.Post.StateTree = &schema.StateTree{RootCID: b.CurrRoot}
|
||||
b.stage = StageChecks
|
||||
b.Assert = newAsserter(b, StageChecks)
|
||||
}
|
||||
|
||||
// applyMessage executes the provided message via the driver, records the new
|
||||
// root, refreshes the state tree, and updates the underlying vector with the
|
||||
// message and its receipt.
|
||||
func (b *Builder) applyMessage(am *ApplicableMessage) {
|
||||
var err error
|
||||
am.Result, b.CurrRoot, err = b.Driver.ExecuteMessage(am.Message, b.CurrRoot, b.Stores.Blockstore, am.Epoch)
|
||||
b.Assert.NoError(err)
|
||||
|
||||
// replace the state tree.
|
||||
b.StateTree, err = state.LoadStateTree(b.Stores.CBORStore, b.CurrRoot)
|
||||
b.Assert.NoError(err)
|
||||
|
||||
b.vector.ApplyMessages = append(b.vector.ApplyMessages, schema.Message{
|
||||
Bytes: MustSerialize(am.Message),
|
||||
Epoch: &am.Epoch,
|
||||
})
|
||||
b.vector.Post.Receipts = append(b.vector.Post.Receipts, &schema.Receipt{
|
||||
ExitCode: am.Result.ExitCode,
|
||||
ReturnValue: am.Result.Return,
|
||||
GasUsed: am.Result.GasUsed,
|
||||
})
|
||||
}
|
||||
|
||||
func (b *Builder) Finish(w io.Writer) {
|
||||
if b.stage != StageChecks {
|
||||
panic("called Finish at the wrong time")
|
||||
}
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
gw := gzip.NewWriter(out)
|
||||
if err := b.WriteCAR(gw, b.vector.Pre.StateTree.RootCID, b.vector.Post.StateTree.RootCID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gw.Flush(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gw.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
b.vector.CAR = out.Bytes()
|
||||
|
||||
b.stage = StageFinished
|
||||
b.Assert = nil
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
if err := encoder.Encode(b.vector); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteCAR recursively writes the tree referenced by the root as assert CAR into the
|
||||
// supplied io.Writer.
|
||||
//
|
||||
// TODO use state.Surgeon instead. (This is assert copy of Surgeon#WriteCAR).
|
||||
func (b *Builder) WriteCAR(w io.Writer, roots ...cid.Cid) error {
|
||||
carWalkFn := func(nd format.Node) (out []*format.Link, err error) {
|
||||
for _, link := range nd.Links() {
|
||||
if link.Cid.Prefix().Codec == cid.FilCommitmentSealed || link.Cid.Prefix().Codec == cid.FilCommitmentUnsealed {
|
||||
continue
|
||||
}
|
||||
out = append(out, link)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
return car.WriteCarWithWalker(context.Background(), b.Stores.DAGService, roots, w, carWalkFn)
|
||||
}
|
||||
|
||||
func (b *Builder) FlushState() cid.Cid {
|
||||
preroot, err := b.StateTree.Flush(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return preroot
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
|
||||
"github.com/filecoin-project/oni/tvx/lotus"
|
||||
)
|
||||
|
||||
const (
|
||||
overuseNum = 11
|
||||
overuseDen = 10
|
||||
)
|
||||
|
||||
// CalculateDeduction returns the balance that shall be deducted from the
|
||||
// sender's account as a result of applying this message.
|
||||
func CalculateDeduction(am *ApplicableMessage) big.Int {
|
||||
if am.Result.GasUsed == 0 {
|
||||
return big.Zero()
|
||||
}
|
||||
|
||||
m := am.Message
|
||||
minerReward := GetMinerReward(m.GasLimit, m.GasPremium) // goes to the miner
|
||||
burn := CalculateBurn(m.GasLimit, am.Result.GasUsed) // vanishes
|
||||
deducted := big.Add(minerReward, burn) // sum of gas accrued
|
||||
|
||||
if am.Result.ExitCode.IsSuccess() {
|
||||
deducted = big.Add(deducted, m.Value) // message value
|
||||
}
|
||||
return deducted
|
||||
}
|
||||
|
||||
// GetMinerReward returns the amount that the miner gets to keep, which is
|
||||
func GetMinerReward(gasLimit int64, gasPremium abi.TokenAmount) abi.TokenAmount {
|
||||
return big.Mul(big.NewInt(gasLimit), gasPremium)
|
||||
}
|
||||
|
||||
func GetMinerPenalty(gasLimit int64) big.Int {
|
||||
return big.Mul(lotus.BaseFee, big.NewInt(gasLimit))
|
||||
}
|
||||
|
||||
// CalculateBurn calcualtes the amount that will be burnt, a function of the
|
||||
// gas limit and the gas actually used.
|
||||
func CalculateBurn(gasLimit int64, gasUsed int64) big.Int {
|
||||
over := gasLimit - (overuseNum*gasUsed)/overuseDen
|
||||
if over < 0 {
|
||||
over = 0
|
||||
}
|
||||
if over > gasUsed {
|
||||
over = gasUsed
|
||||
}
|
||||
|
||||
overestimateGas := big.NewInt(gasLimit - gasUsed)
|
||||
overestimateGas = big.Mul(overestimateGas, big.NewInt(over))
|
||||
overestimateGas = big.Div(overestimateGas, big.NewInt(gasUsed))
|
||||
|
||||
totalBurnGas := big.Add(overestimateGas, big.NewInt(gasUsed))
|
||||
return big.Mul(lotus.BaseFee, totalBurnGas)
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/filecoin-project/oni/tvx/schema"
|
||||
)
|
||||
|
||||
// Generator is a batch generator and organizer of test vectors.
|
||||
//
|
||||
// Test vector scripts are simple programs (main function). Test vector scripts
|
||||
// can delegate to the Generator to handle the execution, reporting and capture
|
||||
// of emitted test vectors into files.
|
||||
//
|
||||
// Generator supports the following CLI flags:
|
||||
//
|
||||
// -o <directory>
|
||||
// directory where test vector JSON files will be saved; if omitted,
|
||||
// vectors will be written to stdout.
|
||||
//
|
||||
// -f <regex>
|
||||
// regex filter to select a subset of vectors to execute; matched against
|
||||
// the vector's ID.
|
||||
//
|
||||
// Scripts can bundle test vectors into "groups". The generator will execute
|
||||
// each group in parallel, and will write each vector in a file:
|
||||
// <output_dir>/<group>--<vector_id>.json
|
||||
type Generator struct {
|
||||
OutputPath string
|
||||
Filter *regexp.Regexp
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// genData is the generation data to stamp into vectors.
|
||||
// TODO in the future this should contain the commit of this tool and
|
||||
// the builder api.
|
||||
var genData = schema.GenerationData{
|
||||
Source: "script",
|
||||
Version: "v0",
|
||||
}
|
||||
|
||||
type MessageVectorGenItem struct {
|
||||
Metadata *schema.Metadata
|
||||
Func func(*Builder)
|
||||
}
|
||||
|
||||
func NewGenerator() *Generator {
|
||||
// Consume CLI parameters.
|
||||
var (
|
||||
outputDir = flag.String("o", "", "directory where test vector JSON files will be saved; if omitted, vectors will be written to stdout")
|
||||
filter = flag.String("f", "", "regex filter to select a subset of vectors to execute; matched against the vector's ID")
|
||||
)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
ret := new(Generator)
|
||||
|
||||
// If output directory is provided, we ensure it exists, or create it.
|
||||
// Else, we'll output to stdout.
|
||||
if dir := *outputDir; dir != "" {
|
||||
err := ensureDirectory(dir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
ret.OutputPath = dir
|
||||
}
|
||||
|
||||
// If a filter has been provided, compile it into a regex.
|
||||
if *filter != "" {
|
||||
exp, err := regexp.Compile(*filter)
|
||||
if err != nil {
|
||||
log.Fatalf("supplied regex %s is invalid: %s", *filter, err)
|
||||
}
|
||||
ret.Filter = exp
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (g *Generator) Wait() {
|
||||
g.wg.Wait()
|
||||
}
|
||||
|
||||
func (g *Generator) MessageVectorGroup(group string, vectors ...*MessageVectorGenItem) {
|
||||
g.wg.Add(1)
|
||||
go func() {
|
||||
defer g.wg.Done()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, item := range vectors {
|
||||
if id := item.Metadata.ID; g.Filter != nil && !g.Filter.MatchString(id) {
|
||||
log.Printf("skipping %s", id)
|
||||
continue
|
||||
}
|
||||
|
||||
var w io.Writer
|
||||
if g.OutputPath == "" {
|
||||
w = os.Stdout
|
||||
} else {
|
||||
file := filepath.Join(g.OutputPath, fmt.Sprintf("%s--%s.json", group, item.Metadata.ID))
|
||||
out, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
log.Printf("failed to write to file %s: %s", file, err)
|
||||
return
|
||||
}
|
||||
w = out
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(item *MessageVectorGenItem) {
|
||||
g.generateOne(w, item, w != os.Stdout)
|
||||
wg.Done()
|
||||
}(item)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}()
|
||||
}
|
||||
|
||||
func (g *Generator) generateOne(w io.Writer, b *MessageVectorGenItem, indent bool) {
|
||||
log.Printf("generating test vector: %s", b.Metadata.ID)
|
||||
|
||||
// stamp with our generation data.
|
||||
b.Metadata.Gen = genData
|
||||
|
||||
vector := MessageVector(b.Metadata)
|
||||
|
||||
// TODO: currently if an assertion fails, we call os.Exit(1), which
|
||||
// aborts all ongoing vector generations. The Asserter should
|
||||
// call runtime.Goexit() instead so only that goroutine is
|
||||
// cancelled. The assertion error must bubble up somehow.
|
||||
b.Func(vector)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
vector.Finish(buf)
|
||||
|
||||
final := buf
|
||||
if indent {
|
||||
// reparse and reindent.
|
||||
final = new(bytes.Buffer)
|
||||
if err := json.Indent(final, buf.Bytes(), "", "\t"); err != nil {
|
||||
log.Printf("failed to indent json: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
n, err := w.Write(final.Bytes())
|
||||
if err != nil {
|
||||
log.Printf("failed to write to output: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("generated test vector: %s (size: %d bytes)", b.Metadata.ID, n)
|
||||
}
|
||||
|
||||
// ensureDirectory checks if the provided path is a directory. If yes, it
|
||||
// returns nil. If the path doesn't exist, it creates the directory and
|
||||
// returns nil. If the path is not a directory, or another error occurs, an
|
||||
// error is returned.
|
||||
func ensureDirectory(path string) error {
|
||||
switch stat, err := os.Stat(path); {
|
||||
case os.IsNotExist(err):
|
||||
// create directory.
|
||||
log.Printf("creating directory %s", path)
|
||||
err := os.MkdirAll(path, 0700)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %s", path, err)
|
||||
}
|
||||
|
||||
case err == nil && !stat.IsDir():
|
||||
return fmt.Errorf("path %s exists, but it's not a directory", path)
|
||||
|
||||
case err != nil:
|
||||
return fmt.Errorf("failed to stat directory %s: %w", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
)
|
||||
|
||||
// TypedCall represents a call to a known built-in actor kind.
|
||||
type TypedCall func() (method abi.MethodNum, params []byte)
|
||||
|
||||
// Messages accumulates the messages to be executed within the test vector.
|
||||
type Messages struct {
|
||||
b *Builder
|
||||
defaults msgOpts
|
||||
|
||||
messages []*ApplicableMessage
|
||||
}
|
||||
|
||||
// SetDefaults sets default options for all messages.
|
||||
func (m *Messages) SetDefaults(opts ...MsgOpt) *Messages {
|
||||
for _, opt := range opts {
|
||||
opt(&m.defaults)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ApplicableMessage represents a message to be applied on the test vector.
|
||||
type ApplicableMessage struct {
|
||||
Epoch abi.ChainEpoch
|
||||
Message *types.Message
|
||||
Result *vm.ApplyRet
|
||||
}
|
||||
|
||||
func (m *Messages) Sugar() *sugarMsg {
|
||||
return &sugarMsg{m}
|
||||
}
|
||||
|
||||
// All returns all ApplicableMessages that have been accumulated, in the same
|
||||
// order they were added.
|
||||
func (m *Messages) All() []*ApplicableMessage {
|
||||
cpy := make([]*ApplicableMessage, len(m.messages))
|
||||
copy(cpy, m.messages)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// Typed adds a typed call to this message accumulator.
|
||||
func (m *Messages) Typed(from, to address.Address, typedm TypedCall, opts ...MsgOpt) *ApplicableMessage {
|
||||
method, params := typedm()
|
||||
return m.Raw(from, to, method, params, opts...)
|
||||
}
|
||||
|
||||
// Raw adds a raw message to this message accumulator.
|
||||
func (m *Messages) Raw(from, to address.Address, method abi.MethodNum, params []byte, opts ...MsgOpt) *ApplicableMessage {
|
||||
options := m.defaults
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
To: to,
|
||||
From: from,
|
||||
Nonce: options.nonce,
|
||||
Value: options.value,
|
||||
Method: method,
|
||||
Params: params,
|
||||
GasLimit: options.gasLimit,
|
||||
GasFeeCap: options.gasFeeCap,
|
||||
GasPremium: options.gasPremium,
|
||||
}
|
||||
|
||||
am := &ApplicableMessage{
|
||||
Epoch: options.epoch,
|
||||
Message: msg,
|
||||
}
|
||||
|
||||
m.messages = append(m.messages, am)
|
||||
return am
|
||||
}
|
||||
|
||||
// ApplyOne applies the provided message. The following constraints are checked:
|
||||
// - all previous messages have been applied.
|
||||
// - 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 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.
|
||||
found = true
|
||||
break
|
||||
}
|
||||
// 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*; index of first unapplied: %d", i)
|
||||
}
|
||||
m.b.Assert.True(found, "ApplicableMessage not found")
|
||||
m.b.applyMessage(am)
|
||||
}
|
||||
|
||||
// ApplyN calls ApplyOne for the supplied messages, in the order they are passed.
|
||||
// The constraints described in ApplyOne apply.
|
||||
func (m *Messages) ApplyN(ams ...*ApplicableMessage) {
|
||||
for _, am := range ams {
|
||||
m.ApplyOne(am)
|
||||
}
|
||||
}
|
||||
|
||||
type msgOpts struct {
|
||||
nonce uint64
|
||||
value big.Int
|
||||
gasLimit int64
|
||||
gasFeeCap abi.TokenAmount
|
||||
gasPremium abi.TokenAmount
|
||||
epoch abi.ChainEpoch
|
||||
}
|
||||
|
||||
// MsgOpt is an option configuring message value, gas parameters, execution
|
||||
// epoch, and other elements.
|
||||
type MsgOpt func(*msgOpts)
|
||||
|
||||
// Value sets a value on a message.
|
||||
func Value(value big.Int) MsgOpt {
|
||||
return func(opts *msgOpts) {
|
||||
opts.value = value
|
||||
}
|
||||
}
|
||||
|
||||
// Nonce sets the nonce of a message.
|
||||
func Nonce(n uint64) MsgOpt {
|
||||
return func(opts *msgOpts) {
|
||||
opts.nonce = n
|
||||
}
|
||||
}
|
||||
|
||||
// GasLimit sets the gas limit of a message.
|
||||
func GasLimit(limit int64) MsgOpt {
|
||||
return func(opts *msgOpts) {
|
||||
opts.gasLimit = limit
|
||||
}
|
||||
}
|
||||
|
||||
// GasFeeCap sets the gas fee cap of a message.
|
||||
func GasFeeCap(feeCap int64) MsgOpt {
|
||||
return func(opts *msgOpts) {
|
||||
opts.gasFeeCap = big.NewInt(feeCap)
|
||||
}
|
||||
}
|
||||
|
||||
// GasPremium sets the gas premium of a message.
|
||||
func GasPremium(premium int64) MsgOpt {
|
||||
return func(opts *msgOpts) {
|
||||
opts.gasPremium = big.NewInt(premium)
|
||||
}
|
||||
}
|
||||
|
||||
// Epoch sets the epoch in which a message is to be executed.
|
||||
func Epoch(epoch abi.ChainEpoch) MsgOpt {
|
||||
return func(opts *msgOpts) {
|
||||
opts.epoch = epoch
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"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/builtin/power"
|
||||
|
||||
"github.com/libp2p/go-libp2p-core/peer"
|
||||
)
|
||||
|
||||
type sugarMsg struct{ m *Messages }
|
||||
|
||||
// Transfer enlists a value transfer message.
|
||||
func (s *sugarMsg) Transfer(from, to address.Address, opts ...MsgOpt) *ApplicableMessage {
|
||||
return s.m.Typed(from, to, Transfer(), opts...)
|
||||
}
|
||||
|
||||
func (s *sugarMsg) CreatePaychActor(from, to address.Address, opts ...MsgOpt) *ApplicableMessage {
|
||||
ctorparams := &paych.ConstructorParams{
|
||||
From: from,
|
||||
To: to,
|
||||
}
|
||||
return s.m.Typed(from, builtin.InitActorAddr, InitExec(&init_.ExecParams{
|
||||
CodeCID: builtin.PaymentChannelActorCodeID,
|
||||
ConstructorParams: MustSerialize(ctorparams),
|
||||
}), opts...)
|
||||
}
|
||||
|
||||
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(params),
|
||||
}), opts...)
|
||||
}
|
||||
|
||||
func (s *sugarMsg) CreateMinerActor(owner, worker address.Address, sealProofType abi.RegisteredSealProof, pid peer.ID, maddrs []abi.Multiaddrs, opts ...MsgOpt) *ApplicableMessage {
|
||||
params := &power.CreateMinerParams{
|
||||
Worker: worker,
|
||||
Owner: owner,
|
||||
SealProofType: sealProofType,
|
||||
Peer: abi.PeerID(pid),
|
||||
Multiaddrs: maddrs,
|
||||
}
|
||||
return s.m.Typed(owner, builtin.StoragePowerActorAddr, PowerCreateMiner(params), opts...)
|
||||
}
|
@ -1,379 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/puppet"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/cron"
|
||||
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
"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/builtin/power"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/reward"
|
||||
)
|
||||
|
||||
func Transfer() TypedCall {
|
||||
return func() (method abi.MethodNum, params []byte) {
|
||||
return builtin.MethodSend, []byte{}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | ACCOUNT
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func AccountConstructor(params *address.Address) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsAccount.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
func AccountPubkeyAddress(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsAccount.PubkeyAddress, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | MARKET
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func MarketConstructor(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMarket.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
func MarketAddBalance(params *address.Address) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMarket.AddBalance, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MarketWithdrawBalance(params *market.WithdrawBalanceParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMarket.WithdrawBalance, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MarketPublishStorageDeals(params *market.PublishStorageDealsParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMarket.PublishStorageDeals, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MarketVerifyDealsForActivation(params *market.VerifyDealsForActivationParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMarket.VerifyDealsForActivation, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MarketActivateDeals(params *market.ActivateDealsParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMarket.ActivateDeals, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MarketOnMinerSectorsTerminate(params *market.OnMinerSectorsTerminateParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMarket.OnMinerSectorsTerminate, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MarketComputeDataCommitment(params *market.ComputeDataCommitmentParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMarket.ComputeDataCommitment, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MarketCronTick(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMarket.CronTick, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | MINER
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func MinerConstructor(params *power.MinerConstructorParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerControlAddresses(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.ControlAddresses, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerChangeWorkerAddress(params *miner.ChangeWorkerAddressParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.ChangeWorkerAddress, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerChangePeerID(params *miner.ChangePeerIDParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.ChangePeerID, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerSubmitWindowedPoSt(params *miner.SubmitWindowedPoStParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.SubmitWindowedPoSt, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerPreCommitSector(params *miner.SectorPreCommitInfo) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.PreCommitSector, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerProveCommitSector(params *miner.ProveCommitSectorParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.ProveCommitSector, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerExtendSectorExpiration(params *miner.ExtendSectorExpirationParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.ExtendSectorExpiration, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerTerminateSectors(params *miner.TerminateSectorsParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.TerminateSectors, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerDeclareFaults(params *miner.DeclareFaultsParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.DeclareFaults, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerDeclareFaultsRecovered(params *miner.DeclareFaultsRecoveredParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.DeclareFaultsRecovered, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerOnDeferredCronEvent(params *miner.CronEventPayload) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.OnDeferredCronEvent, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerCheckSectorProven(params *miner.CheckSectorProvenParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.CheckSectorProven, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerAddLockedFund(params *big.Int) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.AddLockedFund, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerReportConsensusFault(params *miner.ReportConsensusFaultParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.ReportConsensusFault, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerWithdrawBalance(params *miner.WithdrawBalanceParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.WithdrawBalance, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerConfirmSectorProofsValid(params *builtin.ConfirmSectorProofsParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.ConfirmSectorProofsValid, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MinerChangeMultiaddrs(params *miner.ChangeMultiaddrsParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMiner.ChangeMultiaddrs, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | MULTISIG
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func MultisigConstructor(params *multisig.ConstructorParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMultisig.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MultisigPropose(params *multisig.ProposeParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMultisig.Propose, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MultisigApprove(params *multisig.TxnIDParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMultisig.Approve, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MultisigCancel(params *multisig.TxnIDParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMultisig.Cancel, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MultisigAddSigner(params *multisig.AddSignerParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMultisig.AddSigner, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MultisigRemoveSigner(params *multisig.RemoveSignerParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMultisig.RemoveSigner, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MultisigSwapSigner(params *multisig.SwapSignerParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMultisig.SwapSigner, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func MultisigChangeNumApprovalsThreshold(params *multisig.ChangeNumApprovalsThresholdParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsMultisig.ChangeNumApprovalsThreshold, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | POWER
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func PowerConstructor(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPower.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PowerCreateMiner(params *power.CreateMinerParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPower.CreateMiner, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PowerUpdateClaimedPower(params *power.UpdateClaimedPowerParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPower.UpdateClaimedPower, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PowerEnrollCronEvent(params *power.EnrollCronEventParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPower.EnrollCronEvent, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PowerOnEpochTickEnd(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPower.OnEpochTickEnd, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PowerUpdatePledgeTotal(params *big.Int) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPower.UpdatePledgeTotal, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PowerOnConsensusFault(params *big.Int) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPower.OnConsensusFault, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PowerSubmitPoRepForBulkVerify(params *abi.SealVerifyInfo) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPower.SubmitPoRepForBulkVerify, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PowerCurrentTotalPower(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPower.CurrentTotalPower, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | REWARD
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func RewardConstructor(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsReward.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func RewardAwardBlockReward(params *reward.AwardBlockRewardParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsReward.AwardBlockReward, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func RewardThisEpochReward(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsReward.ThisEpochReward, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func RewardUpdateNetworkKPI(params *big.Int) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsReward.UpdateNetworkKPI, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | PAYCH
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func PaychConstructor(params *paych.ConstructorParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPaych.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PaychUpdateChannelState(params *paych.UpdateChannelStateParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPaych.UpdateChannelState, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PaychSettle(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPaych.Settle, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func PaychCollect(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsPaych.Collect, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | CRON
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func CronConstructor(params *cron.ConstructorParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsCron.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func CronEpochTick(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsCron.EpochTick, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | INIT
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func InitConstructor(params *init_.ConstructorParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsInit.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
func InitExec(params *init_.ExecParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return builtin.MethodsInit.Exec, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// | PUPPET
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func PuppetConstructor(params *adt.EmptyValue) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return puppet.MethodsPuppet.Constructor, MustSerialize(params)
|
||||
}
|
||||
}
|
||||
|
||||
func PuppetSend(params *puppet.SendParams) TypedCall {
|
||||
return func() (abi.MethodNum, []byte) {
|
||||
return puppet.MethodsPuppet.Send, MustSerialize(params)
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/vm"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
)
|
||||
|
||||
// ApplyRetPredicate evaluates a given condition against the result of a
|
||||
// message application.
|
||||
type ApplyRetPredicate func(ret *vm.ApplyRet) error
|
||||
|
||||
// OptionalActor is a marker type to warn that the value can be nil.
|
||||
type OptionalActor = types.Actor
|
||||
|
||||
// ActorPredicate evaluates whether the actor that participates in the provided
|
||||
// messages satisfies a given condition. The initial state (after preconditions)
|
||||
// and final state (after applies) are supplied.
|
||||
type ActorPredicate func(handle AddressHandle, initial *OptionalActor, final *OptionalActor, amss []*ApplicableMessage) error
|
||||
|
||||
// ExitCode returns an ApplyRetPredicate that passes if the exit code of the
|
||||
// message execution matches the argument.
|
||||
func ExitCode(expect exitcode.ExitCode) ApplyRetPredicate {
|
||||
return func(ret *vm.ApplyRet) error {
|
||||
if ret.ExitCode == expect {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("message exit code was %d; expected %d", ret.ExitCode, expect)
|
||||
}
|
||||
}
|
||||
|
||||
// BalanceUpdated returns a ActorPredicate that checks whether the balance
|
||||
// of the actor has been deducted the gas cost and the outgoing value transfers,
|
||||
// and has been increased by the offset (or decreased, if the argument is negative).
|
||||
func BalanceUpdated(offset abi.TokenAmount) ActorPredicate {
|
||||
return func(handle AddressHandle, initial *types.Actor, final *OptionalActor, amss []*ApplicableMessage) error {
|
||||
if initial == nil || final == nil {
|
||||
return fmt.Errorf("BalanceUpdated predicate expected non-nil state")
|
||||
}
|
||||
|
||||
// accumulate all balance deductions: ∑(burnt + premium + transferred value)
|
||||
deducted := big.Zero()
|
||||
for _, am := range amss {
|
||||
d := CalculateDeduction(am)
|
||||
deducted = big.Add(deducted, d)
|
||||
}
|
||||
|
||||
expected := big.Sub(initial.Balance, deducted)
|
||||
expected = big.Add(expected, offset)
|
||||
if !final.Balance.Equals(expected) {
|
||||
return fmt.Errorf("expected balance %s, was: %s", expected, final.Balance)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NonceUpdated returns a ActorPredicate that checks whether the nonce
|
||||
// of the actor has been updated to the nonce of the last message + 1.
|
||||
func NonceUpdated() ActorPredicate {
|
||||
return func(handle AddressHandle, initial *types.Actor, final *OptionalActor, amss []*ApplicableMessage) error {
|
||||
if initial == nil || final == nil {
|
||||
return fmt.Errorf("BalanceUpdated predicate expected non-nil state")
|
||||
}
|
||||
|
||||
// the nonce should be equal to the nonce of the last message + 1.
|
||||
last := amss[len(amss)-1]
|
||||
if expected, actual := last.Message.Nonce+1, final.Nonce; expected != actual {
|
||||
return fmt.Errorf("for actor: %s: expected nonce %d, got %d", handle, expected, actual)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
)
|
||||
|
||||
func MustSerialize(i cbg.CBORMarshaler) []byte {
|
||||
out, err := Serialize(i)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func Serialize(i cbg.CBORMarshaler) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := i.MarshalCBOR(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func MustDeserialize(b []byte, out interface{}) {
|
||||
if err := Deserialize(b, out); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Deserialize(b []byte, out interface{}) error {
|
||||
um, ok := out.(cbg.CBORUnmarshaler)
|
||||
if !ok {
|
||||
return fmt.Errorf("type %T does not implement UnmarshalCBOR", out)
|
||||
}
|
||||
return um.UnmarshalCBOR(bytes.NewReader(b))
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-bitfield"
|
||||
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"
|
||||
account_spec "github.com/filecoin-project/specs-actors/actors/builtin/account"
|
||||
cron_spec "github.com/filecoin-project/specs-actors/actors/builtin/cron"
|
||||
init_spec "github.com/filecoin-project/specs-actors/actors/builtin/init"
|
||||
market_spec "github.com/filecoin-project/specs-actors/actors/builtin/market"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
|
||||
power_spec "github.com/filecoin-project/specs-actors/actors/builtin/power"
|
||||
reward_spec "github.com/filecoin-project/specs-actors/actors/builtin/reward"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/system"
|
||||
runtime_spec "github.com/filecoin-project/specs-actors/actors/runtime"
|
||||
adt_spec "github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
"github.com/ipfs/go-cid"
|
||||
)
|
||||
|
||||
const (
|
||||
totalFilecoin = 2_000_000_000
|
||||
filecoinPrecision = 1_000_000_000_000_000_000
|
||||
)
|
||||
|
||||
var (
|
||||
TotalNetworkBalance = big_spec.Mul(big_spec.NewInt(totalFilecoin), big_spec.NewInt(filecoinPrecision))
|
||||
EmptyReturnValue = []byte{}
|
||||
)
|
||||
|
||||
var (
|
||||
// initialized by calling initializeStoreWithAdtRoots
|
||||
EmptyArrayCid cid.Cid
|
||||
EmptyDeadlinesCid cid.Cid
|
||||
EmptyMapCid cid.Cid
|
||||
EmptyMultiMapCid cid.Cid
|
||||
EmptyBitfieldCid cid.Cid
|
||||
EmptyVestingFundsCid cid.Cid
|
||||
)
|
||||
|
||||
const (
|
||||
TestSealProofType = abi_spec.RegisteredSealProof_StackedDrg2KiBV1
|
||||
)
|
||||
|
||||
func (b *Builder) initializeZeroState() {
|
||||
if err := insertEmptyStructures(b.Stores.ADTStore); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
type ActorState struct {
|
||||
Addr address.Address
|
||||
Balance abi_spec.TokenAmount
|
||||
Code cid.Cid
|
||||
State runtime_spec.CBORMarshaler
|
||||
}
|
||||
|
||||
var actors []ActorState
|
||||
|
||||
actors = append(actors, ActorState{
|
||||
Addr: builtin_spec.InitActorAddr,
|
||||
Balance: big_spec.Zero(),
|
||||
Code: builtin_spec.InitActorCodeID,
|
||||
State: init_spec.ConstructState(EmptyMapCid, "chain-validation"),
|
||||
})
|
||||
|
||||
zeroRewardState := reward_spec.ConstructState(big_spec.Zero())
|
||||
zeroRewardState.ThisEpochReward = big_spec.NewInt(1e17)
|
||||
|
||||
actors = append(actors, ActorState{
|
||||
Addr: builtin_spec.RewardActorAddr,
|
||||
Balance: TotalNetworkBalance,
|
||||
Code: builtin_spec.RewardActorCodeID,
|
||||
State: zeroRewardState,
|
||||
})
|
||||
|
||||
actors = append(actors, ActorState{
|
||||
Addr: builtin_spec.BurntFundsActorAddr,
|
||||
Balance: big_spec.Zero(),
|
||||
Code: builtin_spec.AccountActorCodeID,
|
||||
State: &account_spec.State{Address: builtin_spec.BurntFundsActorAddr},
|
||||
})
|
||||
|
||||
actors = append(actors, ActorState{
|
||||
Addr: builtin_spec.StoragePowerActorAddr,
|
||||
Balance: big_spec.Zero(),
|
||||
Code: builtin_spec.StoragePowerActorCodeID,
|
||||
State: power_spec.ConstructState(EmptyMapCid, EmptyMultiMapCid),
|
||||
})
|
||||
|
||||
actors = append(actors, ActorState{
|
||||
Addr: builtin_spec.StorageMarketActorAddr,
|
||||
Balance: big_spec.Zero(),
|
||||
Code: builtin_spec.StorageMarketActorCodeID,
|
||||
State: &market_spec.State{
|
||||
Proposals: EmptyArrayCid,
|
||||
States: EmptyArrayCid,
|
||||
PendingProposals: EmptyMapCid,
|
||||
EscrowTable: EmptyMapCid,
|
||||
LockedTable: EmptyMapCid,
|
||||
NextID: abi_spec.DealID(0),
|
||||
DealOpsByEpoch: EmptyMultiMapCid,
|
||||
LastCron: 0,
|
||||
},
|
||||
})
|
||||
|
||||
actors = append(actors, ActorState{
|
||||
Addr: builtin_spec.SystemActorAddr,
|
||||
Balance: big_spec.Zero(),
|
||||
Code: builtin_spec.SystemActorCodeID,
|
||||
State: &system.State{},
|
||||
})
|
||||
|
||||
actors = append(actors, ActorState{
|
||||
Addr: builtin_spec.CronActorAddr,
|
||||
Balance: big_spec.Zero(),
|
||||
Code: builtin_spec.CronActorCodeID,
|
||||
State: &cron_spec.State{Entries: []cron_spec.Entry{
|
||||
{
|
||||
Receiver: builtin_spec.StoragePowerActorAddr,
|
||||
MethodNum: builtin_spec.MethodsPower.OnEpochTickEnd,
|
||||
},
|
||||
}},
|
||||
})
|
||||
|
||||
for _, act := range actors {
|
||||
_ = b.Actors.CreateActor(act.Code, act.Addr, act.Balance, act.State)
|
||||
}
|
||||
}
|
||||
|
||||
func insertEmptyStructures(store adt_spec.Store) error {
|
||||
var err error
|
||||
_, err = store.Put(context.TODO(), []struct{}{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
EmptyArrayCid, err = adt_spec.MakeEmptyArray(store).Root()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
EmptyMapCid, err = adt_spec.MakeEmptyMap(store).Root()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
EmptyMultiMapCid, err = adt_spec.MakeEmptyMultimap(store).Root()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
EmptyDeadlinesCid, err = store.Put(context.TODO(), miner.ConstructDeadline(EmptyArrayCid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
emptyBitfield := bitfield.NewFromSet(nil)
|
||||
EmptyBitfieldCid, err = store.Put(context.TODO(), emptyBitfield)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
EmptyVestingFundsCid, err = store.Put(context.Background(), miner.ConstructVestingFunds())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package builders
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/minio/blake2b-simd"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-crypto"
|
||||
acrypto "github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/wallet"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
)
|
||||
|
||||
type Wallet struct {
|
||||
// Private keys by address
|
||||
keys map[address.Address]*wallet.Key
|
||||
|
||||
// Seed for deterministic secp key generation.
|
||||
secpSeed int64
|
||||
// Seed for deterministic bls key generation.
|
||||
blsSeed int64 // nolint: structcheck
|
||||
}
|
||||
|
||||
func newWallet() *Wallet {
|
||||
return &Wallet{
|
||||
keys: make(map[address.Address]*wallet.Key),
|
||||
secpSeed: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Wallet) NewSECP256k1Account() address.Address {
|
||||
secpKey := w.newSecp256k1Key()
|
||||
w.keys[secpKey.Address] = secpKey
|
||||
return secpKey.Address
|
||||
}
|
||||
|
||||
func (w *Wallet) NewBLSAccount() address.Address {
|
||||
blsKey := w.newBLSKey()
|
||||
w.keys[blsKey.Address] = blsKey
|
||||
return blsKey.Address
|
||||
}
|
||||
|
||||
func (w *Wallet) Sign(addr address.Address, data []byte) (acrypto.Signature, error) {
|
||||
ki, ok := w.keys[addr]
|
||||
if !ok {
|
||||
return acrypto.Signature{}, fmt.Errorf("unknown address %v", addr)
|
||||
}
|
||||
var sigType acrypto.SigType
|
||||
if ki.Type == wallet.KTSecp256k1 {
|
||||
sigType = acrypto.SigTypeBLS
|
||||
hashed := blake2b.Sum256(data)
|
||||
sig, err := crypto.Sign(ki.PrivateKey, hashed[:])
|
||||
if err != nil {
|
||||
return acrypto.Signature{}, err
|
||||
}
|
||||
|
||||
return acrypto.Signature{
|
||||
Type: sigType,
|
||||
Data: sig,
|
||||
}, nil
|
||||
} else if ki.Type == wallet.KTBLS {
|
||||
panic("lotus validator cannot sign BLS messages")
|
||||
} else {
|
||||
panic("unknown signature type")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (w *Wallet) newSecp256k1Key() *wallet.Key {
|
||||
randSrc := rand.New(rand.NewSource(w.secpSeed))
|
||||
prv, err := crypto.GenerateKeyFromSeed(randSrc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.secpSeed++
|
||||
key, err := wallet.NewKey(types.KeyInfo{
|
||||
Type: wallet.KTSecp256k1,
|
||||
PrivateKey: prv,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func (w *Wallet) newBLSKey() *wallet.Key {
|
||||
// FIXME: bls needs deterministic key generation
|
||||
//sk := ffi.PrivateKeyGenerate(s.blsSeed)
|
||||
// s.blsSeed++
|
||||
sk := [32]byte{}
|
||||
sk[0] = uint8(w.blsSeed) // hack to keep gas values determinist
|
||||
w.blsSeed++
|
||||
key, err := wallet.NewKey(types.KeyInfo{
|
||||
Type: wallet.KTBLS,
|
||||
PrivateKey: sk[:],
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return key
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
)
|
||||
|
||||
func sequentialAddresses(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
initial := big.NewInt(1_000_000_000_000_000)
|
||||
|
||||
// Set up sender and receiver accounts.
|
||||
var sender, receiver AddressHandle
|
||||
v.Actors.AccountN(address.SECP256K1, initial, &sender, &receiver)
|
||||
v.CommitPreconditions()
|
||||
|
||||
// Create 10 payment channels.
|
||||
for i := uint64(0); i < 10; i++ {
|
||||
v.Messages.Sugar().CreatePaychActor(sender.Robust, receiver.Robust, Value(big.NewInt(1000)), Nonce(i))
|
||||
}
|
||||
v.CommitApplies()
|
||||
|
||||
for i, am := range v.Messages.All() {
|
||||
expectedActorAddr := AddressHandle{
|
||||
ID: MustNewIDAddr(MustIDFromAddress(receiver.ID) + uint64(i) + 1),
|
||||
Robust: sender.NextActorAddress(am.Message.Nonce, 0),
|
||||
}
|
||||
|
||||
// Verify that the return contains the expected addresses.
|
||||
var ret init_.ExecReturn
|
||||
MustDeserialize(am.Result.Return, &ret)
|
||||
v.Assert.Equal(expectedActorAddr.Robust, ret.RobustAddress)
|
||||
v.Assert.Equal(expectedActorAddr.ID, ret.IDAddress)
|
||||
}
|
||||
|
||||
v.Assert.EveryMessageSenderSatisfies(BalanceUpdated(big.Zero()))
|
||||
v.Assert.EveryMessageSenderSatisfies(NonceUpdated())
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
"github.com/filecoin-project/oni/tvx/schema"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g := NewGenerator()
|
||||
|
||||
g.MessageVectorGroup("addresses",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "sequential-10",
|
||||
Version: "v1",
|
||||
Desc: "actor addresses are sequential",
|
||||
},
|
||||
Func: sequentialAddresses,
|
||||
},
|
||||
)
|
||||
|
||||
g.MessageVectorGroup("on_transfer",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "ok-create-secp256k1",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: actorCreationOnTransfer(actorCreationOnTransferParams{
|
||||
senderType: address.SECP256K1,
|
||||
senderBal: abi.NewTokenAmount(1_000_000_000_000_000),
|
||||
receiverAddr: MustNewSECP256K1Addr("publickeyfoo"),
|
||||
amount: abi.NewTokenAmount(10_000),
|
||||
exitCode: exitcode.Ok,
|
||||
}),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "ok-create-bls",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: actorCreationOnTransfer(actorCreationOnTransferParams{
|
||||
senderType: address.SECP256K1,
|
||||
senderBal: abi.NewTokenAmount(1_000_000_000_000_000),
|
||||
receiverAddr: MustNewBLSAddr(1),
|
||||
amount: abi.NewTokenAmount(10_000),
|
||||
exitCode: exitcode.Ok,
|
||||
}),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-secp256k1-insufficient-balance",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: actorCreationOnTransfer(actorCreationOnTransferParams{
|
||||
senderType: address.SECP256K1,
|
||||
senderBal: abi.NewTokenAmount(9_999),
|
||||
receiverAddr: MustNewSECP256K1Addr("publickeyfoo"),
|
||||
amount: abi.NewTokenAmount(10_000),
|
||||
exitCode: exitcode.SysErrSenderStateInvalid,
|
||||
}),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-bls-insufficient-balance",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: actorCreationOnTransfer(actorCreationOnTransferParams{
|
||||
senderType: address.SECP256K1,
|
||||
senderBal: abi.NewTokenAmount(9_999),
|
||||
receiverAddr: MustNewBLSAddr(1),
|
||||
amount: abi.NewTokenAmount(10_000),
|
||||
exitCode: exitcode.SysErrSenderStateInvalid,
|
||||
}),
|
||||
},
|
||||
)
|
||||
|
||||
g.Wait()
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
)
|
||||
|
||||
type actorCreationOnTransferParams struct {
|
||||
senderType address.Protocol
|
||||
senderBal abi.TokenAmount
|
||||
receiverAddr address.Address
|
||||
amount abi.TokenAmount
|
||||
exitCode exitcode.ExitCode
|
||||
}
|
||||
|
||||
func actorCreationOnTransfer(params actorCreationOnTransferParams) func(v *Builder) {
|
||||
return func(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
// Set up sender account.
|
||||
sender := v.Actors.Account(params.senderType, params.senderBal)
|
||||
v.CommitPreconditions()
|
||||
|
||||
// Perform the transfer.
|
||||
v.Messages.Sugar().Transfer(sender.ID, params.receiverAddr, Value(params.amount), Nonce(0))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(params.exitCode))
|
||||
v.Assert.EveryMessageSenderSatisfies(BalanceUpdated(big.Zero()))
|
||||
|
||||
if params.exitCode.IsSuccess() {
|
||||
v.Assert.EveryMessageSenderSatisfies(NonceUpdated())
|
||||
v.Assert.BalanceEq(params.receiverAddr, params.amount)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"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/paych"
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
)
|
||||
|
||||
func failActorExecutionAborted(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
// Set up sender and receiver accounts.
|
||||
var sender, receiver AddressHandle
|
||||
var paychAddr AddressHandle
|
||||
|
||||
v.Actors.AccountN(address.SECP256K1, balance1T, &sender, &receiver)
|
||||
paychAddr = AddressHandle{
|
||||
ID: MustNewIDAddr(MustIDFromAddress(receiver.ID) + 1),
|
||||
Robust: sender.NextActorAddress(0, 0),
|
||||
}
|
||||
v.CommitPreconditions()
|
||||
|
||||
// Construct the payment channel.
|
||||
createMsg := v.Messages.Sugar().CreatePaychActor(sender.Robust, receiver.Robust, Value(abi.NewTokenAmount(10_000)))
|
||||
|
||||
// Update the payment channel.
|
||||
updateMsg := v.Messages.Typed(sender.Robust, paychAddr.Robust, PaychUpdateChannelState(&paych.UpdateChannelStateParams{
|
||||
Sv: paych.SignedVoucher{
|
||||
ChannelAddr: paychAddr.Robust,
|
||||
TimeLockMin: abi.ChainEpoch(10),
|
||||
Lane: 123,
|
||||
Nonce: 1,
|
||||
Amount: big.NewInt(10),
|
||||
Signature: &crypto.Signature{
|
||||
Type: crypto.SigTypeBLS,
|
||||
Data: []byte("Grrr im an invalid signature, I cause panics in the payment channel actor"),
|
||||
},
|
||||
}}), Nonce(1), Value(big.Zero()))
|
||||
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.Equal(exitcode.Ok, createMsg.Result.ExitCode)
|
||||
v.Assert.Equal(exitcode.ErrIllegalArgument, updateMsg.Result.ExitCode)
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
)
|
||||
|
||||
func failCoverReceiptGasCost(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
alice := v.Actors.Account(address.SECP256K1, balance1T)
|
||||
v.CommitPreconditions()
|
||||
|
||||
v.Messages.Sugar().Transfer(alice.ID, alice.ID, Value(transferAmnt), Nonce(0), GasPremium(1), GasLimit(8))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrOutOfGas))
|
||||
}
|
||||
|
||||
func failCoverOnChainSizeGasCost(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
alice := v.Actors.Account(address.SECP256K1, balance1T)
|
||||
v.CommitPreconditions()
|
||||
|
||||
v.Messages.Sugar().Transfer(alice.ID, alice.ID, Value(transferAmnt), Nonce(0), GasPremium(10), GasLimit(1))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrOutOfGas))
|
||||
}
|
||||
|
||||
func failCoverTransferAccountCreationGasStepwise(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
var alice, bob, charlie AddressHandle
|
||||
alice = v.Actors.Account(address.SECP256K1, balance1T)
|
||||
bob.Robust, charlie.Robust = MustNewSECP256K1Addr("1"), MustNewSECP256K1Addr("2")
|
||||
v.CommitPreconditions()
|
||||
|
||||
var nonce uint64
|
||||
ref := v.Messages.Sugar().Transfer(alice.Robust, bob.Robust, Value(transferAmnt), Nonce(nonce))
|
||||
nonce++
|
||||
v.Messages.ApplyOne(ref)
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.Ok))
|
||||
|
||||
// decrease the gas cost by `gasStep` for each apply and ensure `SysErrOutOfGas` is always returned.
|
||||
trueGas := ref.Result.GasUsed
|
||||
gasStep := trueGas / 100
|
||||
for tryGas := trueGas - gasStep; tryGas > 0; tryGas -= gasStep {
|
||||
v.Messages.Sugar().Transfer(alice.Robust, charlie.Robust, Value(transferAmnt), Nonce(nonce), GasPremium(1), GasLimit(tryGas))
|
||||
nonce++
|
||||
}
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrOutOfGas), ref)
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
)
|
||||
|
||||
func failInvalidActorNonce(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
alice := v.Actors.Account(address.SECP256K1, balance1T)
|
||||
v.CommitPreconditions()
|
||||
|
||||
// invalid nonce from known account.
|
||||
msg1 := v.Messages.Sugar().Transfer(alice.ID, alice.ID, Value(transferAmnt), Nonce(1))
|
||||
|
||||
// invalid nonce from an unknown account.
|
||||
msg2 := v.Messages.Sugar().Transfer(unknown, alice.ID, Value(transferAmnt), Nonce(1))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.Equal(msg1.Result.ExitCode, exitcode.SysErrSenderStateInvalid)
|
||||
v.Assert.Equal(msg2.Result.ExitCode, exitcode.SysErrSenderInvalid)
|
||||
}
|
||||
|
||||
func failInvalidReceiverMethod(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
alice := v.Actors.Account(address.SECP256K1, balance1T)
|
||||
v.CommitPreconditions()
|
||||
|
||||
v.Messages.Typed(alice.ID, alice.ID, MarketComputeDataCommitment(nil), Nonce(0), Value(big.Zero()))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrInvalidMethod))
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
"github.com/filecoin-project/oni/tvx/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
unknown = MustNewIDAddr(10000000)
|
||||
balance1T = abi.NewTokenAmount(1_000_000_000_000)
|
||||
transferAmnt = abi.NewTokenAmount(10)
|
||||
)
|
||||
|
||||
func main() {
|
||||
g := NewGenerator()
|
||||
|
||||
g.MessageVectorGroup("gas_cost",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "msg-apply-fail-receipt-gas",
|
||||
Version: "v1",
|
||||
Desc: "fail to cover gas cost for message receipt on chain",
|
||||
},
|
||||
Func: failCoverReceiptGasCost,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "msg-apply-fail-onchainsize-gas",
|
||||
Version: "v1",
|
||||
Desc: "not enough gas to pay message on-chain-size cost",
|
||||
},
|
||||
Func: failCoverOnChainSizeGasCost,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "msg-apply-fail-transfer-accountcreation-gas",
|
||||
Version: "v1",
|
||||
Desc: "fail not enough gas to cover account actor creation on transfer",
|
||||
},
|
||||
Func: failCoverTransferAccountCreationGasStepwise,
|
||||
})
|
||||
|
||||
g.MessageVectorGroup("invalid_msgs",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "msg-apply-fail-invalid-nonce",
|
||||
Version: "v1",
|
||||
Desc: "invalid actor nonce",
|
||||
},
|
||||
Func: failInvalidActorNonce,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "msg-apply-fail-invalid-receiver-method",
|
||||
Version: "v1",
|
||||
Desc: "invalid receiver method",
|
||||
},
|
||||
Func: failInvalidReceiverMethod,
|
||||
},
|
||||
)
|
||||
|
||||
g.MessageVectorGroup("unknown_actors",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "msg-apply-fail-unknown-sender",
|
||||
Version: "v1",
|
||||
Desc: "fail due to lack of gas when sender is unknown",
|
||||
},
|
||||
Func: failUnknownSender,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "msg-apply-fail-unknown-receiver",
|
||||
Version: "v1",
|
||||
Desc: "inexistent receiver",
|
||||
Comment: `Note that this test is not a valid message, since it is using
|
||||
an unknown actor. However in the event that an invalid message isn't filtered by
|
||||
block validation we need to ensure behaviour is consistent across VM implementations.`,
|
||||
},
|
||||
Func: failUnknownReceiver,
|
||||
},
|
||||
)
|
||||
|
||||
g.MessageVectorGroup("actor_exec",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "msg-apply-fail-actor-execution-illegal-arg",
|
||||
Version: "v1",
|
||||
Desc: "abort during actor execution due to illegal argument",
|
||||
},
|
||||
Func: failActorExecutionAborted,
|
||||
},
|
||||
)
|
||||
|
||||
g.Wait()
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
)
|
||||
|
||||
func failUnknownSender(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
alice := v.Actors.Account(address.SECP256K1, balance1T)
|
||||
v.CommitPreconditions()
|
||||
|
||||
v.Messages.Sugar().Transfer(unknown, alice.ID, Value(transferAmnt), Nonce(0))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrSenderInvalid))
|
||||
}
|
||||
|
||||
func failUnknownReceiver(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
alice := v.Actors.Account(address.SECP256K1, balance1T)
|
||||
v.CommitPreconditions()
|
||||
|
||||
// Sending a message to non-existent ID address must produce an error.
|
||||
unknownID := MustNewIDAddr(10000000)
|
||||
v.Messages.Sugar().Transfer(alice.ID, unknownID, Value(transferAmnt), Nonce(0))
|
||||
|
||||
unknownActor := MustNewActorAddr("1234")
|
||||
v.Messages.Sugar().Transfer(alice.ID, unknownActor, Value(transferAmnt), Nonce(1))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrInvalidReceiver))
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
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,
|
||||
},
|
||||
)
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
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
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
"github.com/filecoin-project/oni/tvx/schema"
|
||||
)
|
||||
|
||||
func main() {
|
||||
g := NewGenerator()
|
||||
|
||||
g.MessageVectorGroup("nested_sends",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "ok-basic",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_OkBasic,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "ok-to-new-actor",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_OkToNewActor,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "ok-to-new-actor-with-invoke",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_OkToNewActorWithInvoke,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "ok-recursive",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_OkRecursive,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "ok-non-cbor-params-with-transfer",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_OKNonCBORParamsWithTransfer,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-non-existent-id-address",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_FailNonexistentIDAddress,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-non-existent-actor-address",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_FailNonexistentActorAddress,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-invalid-method-num-new-actor",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_FailInvalidMethodNumNewActor,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-invalid-method-num-for-actor",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_FailInvalidMethodNumForActor,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-missing-params",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_FailMissingParams,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-mismatch-params",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_FailMismatchParams,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-inner-abort",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_FailInnerAbort,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-aborted-exec",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_FailAbortedExec,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-insufficient-funds-for-transfer-in-inner-send",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: nestedSends_FailInsufficientFundsForTransferInInnerSend,
|
||||
},
|
||||
)
|
||||
|
||||
g.Wait()
|
||||
}
|
@ -1,358 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"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/builtin/paych"
|
||||
"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"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||
typegen "github.com/whyrusleeping/cbor-gen"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
)
|
||||
|
||||
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 nestedSends_OkBasic(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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.Result.Receipt.GasUsed.Big()), nonce+1)
|
||||
v.Assert.NonceEq(stage.creator, nonce+1)
|
||||
v.Assert.BalanceEq(stage.creator, big.Sub(big.Add(balanceBefore, amtSent), CalculateDeduction(result)))
|
||||
}
|
||||
|
||||
func nestedSends_OkToNewActor(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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, CalculateDeduction(result)))
|
||||
v.Assert.BalanceEq(newAddr, amtSent)
|
||||
}
|
||||
|
||||
func nestedSends_OkToNewActorWithInvoke(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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.Result.Receipt.ReturnValue)
|
||||
|
||||
v.Assert.BalanceEq(stage.msAddr, big.Sub(multisigBalance, amtSent))
|
||||
v.Assert.BalanceEq(stage.creator, big.Sub(balanceBefore, CalculateDeduction(result)))
|
||||
v.Assert.BalanceEq(newAddr, amtSent)
|
||||
}
|
||||
|
||||
func nestedSends_OkRecursive(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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, CalculateDeduction(result)), 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)
|
||||
}
|
||||
|
||||
func nestedSends_OKNonCBORParamsWithTransfer(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func nestedSends_FailNonexistentIDAddress(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func nestedSends_FailNonexistentActorAddress(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func nestedSends_FailInvalidMethodNumNewActor(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func nestedSends_FailInvalidMethodNumForActor(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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, CalculateDeduction(result))) // Pay gas, don't receive funds.
|
||||
}
|
||||
|
||||
func nestedSends_FailMissingParams(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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, CalculateDeduction(result)))
|
||||
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
||||
v.Assert.Equal(1, len(stage.state().Signers)) // No new signers
|
||||
}
|
||||
|
||||
func nestedSends_FailMismatchParams(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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, CalculateDeduction(result)))
|
||||
v.Assert.BalanceEq(stage.msAddr, multisigBalance) // No change.
|
||||
v.Assert.Equal(1, len(stage.state().Signers)) // No new signers
|
||||
}
|
||||
|
||||
func nestedSends_FailInnerAbort(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func nestedSends_FailAbortedExec(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
func nestedSends_FailInsufficientFundsForTransferInInnerSend(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
// 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.MessageSendersSatisfy(BalanceUpdated(big.Zero()), msg)
|
||||
v.Assert.BalanceEq(bob.ID, big.Zero())
|
||||
}
|
||||
|
||||
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, &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)
|
||||
|
||||
// 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) *ApplicableMessage {
|
||||
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
|
||||
}
|
||||
|
||||
func (s *msStage) state() *multisig.State {
|
||||
var msState multisig.State
|
||||
s.v.Actors.ActorState(s.msAddr, &msState)
|
||||
return &msState
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
"github.com/filecoin-project/oni/tvx/schema"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
)
|
||||
|
||||
var (
|
||||
initialBal = abi.NewTokenAmount(1_000_000_000_000)
|
||||
toSend = abi.NewTokenAmount(10_000)
|
||||
)
|
||||
|
||||
func main() {
|
||||
g := NewGenerator()
|
||||
|
||||
g.MessageVectorGroup("paych",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "create-ok",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: happyPathCreate,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "update-ok",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: happyPathUpdate,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "collect-ok",
|
||||
Version: "v1",
|
||||
Desc: "",
|
||||
},
|
||||
Func: happyPathCollect,
|
||||
},
|
||||
)
|
||||
|
||||
g.Wait()
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
init_ "github.com/filecoin-project/specs-actors/actors/builtin/init"
|
||||
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
|
||||
"github.com/filecoin-project/specs-actors/actors/crypto"
|
||||
"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"
|
||||
)
|
||||
|
||||
func happyPathCreate(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
// Set up sender and receiver accounts.
|
||||
var sender, receiver AddressHandle
|
||||
v.Actors.AccountN(address.SECP256K1, initialBal, &sender, &receiver)
|
||||
v.CommitPreconditions()
|
||||
|
||||
// Add the constructor message.
|
||||
createMsg := v.Messages.Sugar().CreatePaychActor(sender.Robust, receiver.Robust, Value(toSend))
|
||||
v.CommitApplies()
|
||||
|
||||
expectedActorAddr := AddressHandle{
|
||||
ID: MustNewIDAddr(MustIDFromAddress(receiver.ID) + 1),
|
||||
Robust: sender.NextActorAddress(0, 0),
|
||||
}
|
||||
|
||||
// Verify init actor return.
|
||||
var ret init_.ExecReturn
|
||||
MustDeserialize(createMsg.Result.Return, &ret)
|
||||
v.Assert.Equal(expectedActorAddr.Robust, ret.RobustAddress)
|
||||
v.Assert.Equal(expectedActorAddr.ID, ret.IDAddress)
|
||||
|
||||
// Verify the paych state.
|
||||
var state paych.State
|
||||
actor := v.Actors.ActorState(ret.IDAddress, &state)
|
||||
v.Assert.Equal(sender.ID, state.From)
|
||||
v.Assert.Equal(receiver.ID, state.To)
|
||||
v.Assert.Equal(toSend, actor.Balance)
|
||||
|
||||
v.Assert.EveryMessageSenderSatisfies(NonceUpdated())
|
||||
}
|
||||
|
||||
func happyPathUpdate(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
var (
|
||||
timelock = abi.ChainEpoch(0)
|
||||
lane = uint64(123)
|
||||
nonce = uint64(1)
|
||||
amount = big.NewInt(10)
|
||||
)
|
||||
|
||||
// Set up sender and receiver accounts.
|
||||
var sender, receiver AddressHandle
|
||||
var paychAddr AddressHandle
|
||||
|
||||
v.Actors.AccountN(address.SECP256K1, initialBal, &sender, &receiver)
|
||||
paychAddr = AddressHandle{
|
||||
ID: MustNewIDAddr(MustIDFromAddress(receiver.ID) + 1),
|
||||
Robust: sender.NextActorAddress(0, 0),
|
||||
}
|
||||
v.CommitPreconditions()
|
||||
|
||||
// Construct the payment channel.
|
||||
createMsg := v.Messages.Sugar().CreatePaychActor(sender.Robust, receiver.Robust, Value(toSend))
|
||||
|
||||
// Update the payment channel.
|
||||
v.Messages.Typed(sender.Robust, paychAddr.Robust, PaychUpdateChannelState(&paych.UpdateChannelStateParams{
|
||||
Sv: paych.SignedVoucher{
|
||||
ChannelAddr: paychAddr.Robust,
|
||||
TimeLockMin: timelock,
|
||||
TimeLockMax: 0, // TimeLockMax set to 0 means no timeout
|
||||
Lane: lane,
|
||||
Nonce: nonce,
|
||||
Amount: amount,
|
||||
MinSettleHeight: 0,
|
||||
Signature: &crypto.Signature{
|
||||
Type: crypto.SigTypeBLS,
|
||||
Data: []byte("signature goes here"), // TODO may need to generate an actual signature
|
||||
},
|
||||
}}), Nonce(1), Value(big.Zero()))
|
||||
|
||||
v.CommitApplies()
|
||||
|
||||
// all messages succeeded.
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.Ok))
|
||||
|
||||
// Verify init actor return.
|
||||
var ret init_.ExecReturn
|
||||
MustDeserialize(createMsg.Result.Return, &ret)
|
||||
|
||||
// Verify the paych state.
|
||||
var state paych.State
|
||||
v.Actors.ActorState(ret.RobustAddress, &state)
|
||||
|
||||
arr, err := adt.AsArray(v.Stores.ADTStore, state.LaneStates)
|
||||
v.Assert.NoError(err)
|
||||
v.Assert.EqualValues(1, arr.Length())
|
||||
|
||||
var ls paych.LaneState
|
||||
found, err := arr.Get(lane, &ls)
|
||||
v.Assert.NoError(err)
|
||||
v.Assert.True(found)
|
||||
|
||||
v.Assert.Equal(amount, ls.Redeemed)
|
||||
v.Assert.Equal(nonce, ls.Nonce)
|
||||
|
||||
v.Assert.EveryMessageSenderSatisfies(NonceUpdated())
|
||||
}
|
||||
|
||||
func happyPathCollect(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
// Set up sender and receiver accounts.
|
||||
var sender, receiver AddressHandle
|
||||
var paychAddr AddressHandle
|
||||
v.Actors.AccountN(address.SECP256K1, initialBal, &sender, &receiver)
|
||||
paychAddr = AddressHandle{
|
||||
ID: MustNewIDAddr(MustIDFromAddress(receiver.ID) + 1),
|
||||
Robust: sender.NextActorAddress(0, 0),
|
||||
}
|
||||
|
||||
v.CommitPreconditions()
|
||||
|
||||
// Construct the payment channel.
|
||||
createMsg := v.Messages.Sugar().CreatePaychActor(sender.Robust, receiver.Robust, Value(toSend))
|
||||
|
||||
// Update the payment channel.
|
||||
updateMsg := v.Messages.Typed(sender.Robust, paychAddr.Robust, PaychUpdateChannelState(&paych.UpdateChannelStateParams{
|
||||
Sv: paych.SignedVoucher{
|
||||
ChannelAddr: paychAddr.Robust,
|
||||
TimeLockMin: 0,
|
||||
TimeLockMax: 0, // TimeLockMax set to 0 means no timeout
|
||||
Lane: 1,
|
||||
Nonce: 1,
|
||||
Amount: toSend,
|
||||
MinSettleHeight: 0,
|
||||
Signature: &crypto.Signature{
|
||||
Type: crypto.SigTypeBLS,
|
||||
Data: []byte("signature goes here"), // TODO may need to generate an actual signature
|
||||
},
|
||||
}}), Nonce(1), Value(big.Zero()))
|
||||
|
||||
settleMsg := v.Messages.Typed(receiver.Robust, paychAddr.Robust, PaychSettle(nil), Value(big.Zero()), Nonce(0))
|
||||
|
||||
// advance the epoch so the funds may be redeemed.
|
||||
collectMsg := v.Messages.Typed(receiver.Robust, paychAddr.Robust, PaychCollect(nil), Value(big.Zero()), Nonce(1), Epoch(paych.SettleDelay))
|
||||
|
||||
v.CommitApplies()
|
||||
|
||||
// all messages succeeded.
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.Ok))
|
||||
|
||||
v.Assert.MessageSendersSatisfy(BalanceUpdated(big.Zero()), createMsg, updateMsg)
|
||||
v.Assert.MessageSendersSatisfy(BalanceUpdated(toSend), settleMsg, collectMsg)
|
||||
v.Assert.EveryMessageSenderSatisfies(NonceUpdated())
|
||||
|
||||
// the paych actor should have been deleted after the collect
|
||||
v.Assert.ActorMissing(paychAddr.Robust)
|
||||
v.Assert.ActorMissing(paychAddr.ID)
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
)
|
||||
|
||||
type basicTransferParams struct {
|
||||
senderType address.Protocol
|
||||
senderBal abi.TokenAmount
|
||||
receiverType address.Protocol
|
||||
amount abi.TokenAmount
|
||||
exitCode exitcode.ExitCode
|
||||
}
|
||||
|
||||
func basicTransfer(params basicTransferParams) func(v *Builder) {
|
||||
return func(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(gasLimit), GasPremium(1), GasFeeCap(gasFeeCap))
|
||||
|
||||
// Set up sender and receiver accounts.
|
||||
var sender, receiver AddressHandle
|
||||
sender = v.Actors.Account(params.senderType, params.senderBal)
|
||||
receiver = v.Actors.Account(params.receiverType, big.Zero())
|
||||
v.CommitPreconditions()
|
||||
|
||||
// Perform the transfer.
|
||||
v.Messages.Sugar().Transfer(sender.ID, receiver.ID, Value(params.amount), Nonce(0))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(params.exitCode))
|
||||
v.Assert.EveryMessageSenderSatisfies(BalanceUpdated(big.Zero()))
|
||||
|
||||
if params.exitCode.IsSuccess() {
|
||||
v.Assert.EveryMessageSenderSatisfies(NonceUpdated())
|
||||
v.Assert.BalanceEq(receiver.ID, params.amount)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
. "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",
|
||||
Version: "v1",
|
||||
Desc: "successfully transfer funds from sender to receiver",
|
||||
},
|
||||
Func: basicTransfer(basicTransferParams{
|
||||
senderType: address.SECP256K1,
|
||||
senderBal: abi.NewTokenAmount(10 * gasLimit * gasFeeCap),
|
||||
receiverType: address.SECP256K1,
|
||||
amount: abi.NewTokenAmount(50),
|
||||
exitCode: exitcode.Ok,
|
||||
}),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "ok-zero",
|
||||
Version: "v1",
|
||||
Desc: "successfully transfer zero funds from sender to receiver",
|
||||
},
|
||||
Func: basicTransfer(basicTransferParams{
|
||||
senderType: address.SECP256K1,
|
||||
senderBal: abi.NewTokenAmount(10 * gasFeeCap * gasLimit),
|
||||
receiverType: address.SECP256K1,
|
||||
amount: abi.NewTokenAmount(0),
|
||||
exitCode: exitcode.Ok,
|
||||
}),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-exceed-balance",
|
||||
Version: "v1",
|
||||
Desc: "fail to transfer more funds than sender balance > 0",
|
||||
},
|
||||
Func: basicTransfer(basicTransferParams{
|
||||
senderType: address.SECP256K1,
|
||||
senderBal: abi.NewTokenAmount(10 * gasFeeCap * gasLimit),
|
||||
receiverType: address.SECP256K1,
|
||||
amount: abi.NewTokenAmount(10*gasFeeCap*gasLimit - gasFeeCap*gasLimit + 1),
|
||||
exitCode: exitcode.SysErrInsufficientFunds,
|
||||
}),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-balance-equal-gas",
|
||||
Version: "v1",
|
||||
Desc: "fail to transfer more funds than sender has when sender balance matches gas limit",
|
||||
},
|
||||
Func: basicTransfer(basicTransferParams{
|
||||
senderType: address.SECP256K1,
|
||||
senderBal: abi.NewTokenAmount(gasFeeCap * gasLimit),
|
||||
receiverType: address.SECP256K1,
|
||||
amount: abi.NewTokenAmount(1),
|
||||
exitCode: exitcode.SysErrInsufficientFunds,
|
||||
}),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-balance-under-gaslimit",
|
||||
Version: "v1",
|
||||
Desc: "fail to transfer when sender balance under gas limit",
|
||||
},
|
||||
Func: basicTransfer(basicTransferParams{
|
||||
senderType: address.SECP256K1,
|
||||
senderBal: abi.NewTokenAmount(gasFeeCap*gasLimit - 1),
|
||||
receiverType: address.SECP256K1,
|
||||
amount: abi.NewTokenAmount(0),
|
||||
exitCode: exitcode.SysErrSenderStateInvalid,
|
||||
}),
|
||||
},
|
||||
)
|
||||
|
||||
g.MessageVectorGroup("self_transfer",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "secp-to-secp-addresses",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: selfTransfer(AddressHandle.RobustAddr, AddressHandle.RobustAddr),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "secp-to-id-addresses",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: selfTransfer(AddressHandle.RobustAddr, AddressHandle.IDAddr),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "id-to-secp-addresses",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: selfTransfer(AddressHandle.IDAddr, AddressHandle.RobustAddr),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "id-to-id-addresses",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: selfTransfer(AddressHandle.IDAddr, AddressHandle.IDAddr),
|
||||
},
|
||||
)
|
||||
|
||||
g.MessageVectorGroup("unknown_accounts",
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-unknown-sender-known-receiver",
|
||||
Version: "v1",
|
||||
Desc: "fail to transfer from unknown account to known address",
|
||||
},
|
||||
Func: failTransferUnknownSenderKnownReceiver,
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "fail-unknown-sender-unknown-receiver",
|
||||
Version: "v1",
|
||||
Desc: "fail to transfer from unknown address to unknown address",
|
||||
},
|
||||
Func: failTransferUnknownSenderUnknownReceiver,
|
||||
},
|
||||
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "secp-to-id-addresses",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: selfTransfer(AddressHandle.RobustAddr, AddressHandle.IDAddr),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "id-to-secp-addresses",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: selfTransfer(AddressHandle.IDAddr, AddressHandle.RobustAddr),
|
||||
},
|
||||
&MessageVectorGenItem{
|
||||
Metadata: &schema.Metadata{
|
||||
ID: "id-to-id-addresses",
|
||||
Version: "v1",
|
||||
},
|
||||
Func: selfTransfer(AddressHandle.IDAddr, AddressHandle.IDAddr),
|
||||
},
|
||||
)
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
)
|
||||
|
||||
func selfTransfer(from, to func(h AddressHandle) address.Address) func(v *Builder) {
|
||||
return func(v *Builder) {
|
||||
initial := abi.NewTokenAmount(1_000_000_000_000)
|
||||
transfer := abi.NewTokenAmount(10)
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
// Set up sender account.
|
||||
account := v.Actors.Account(address.SECP256K1, initial)
|
||||
v.CommitPreconditions()
|
||||
|
||||
// Perform the transfer.
|
||||
msg := v.Messages.Sugar().Transfer(from(account), to(account), Value(transfer), Nonce(0))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.Equal(exitcode.Ok, msg.Result.ExitCode)
|
||||
|
||||
// the transfer balance comes back to us.
|
||||
v.Assert.EveryMessageSenderSatisfies(BalanceUpdated(transfer))
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/specs-actors/actors/abi"
|
||||
"github.com/filecoin-project/specs-actors/actors/runtime/exitcode"
|
||||
|
||||
. "github.com/filecoin-project/oni/tvx/builders"
|
||||
)
|
||||
|
||||
var (
|
||||
initial = abi.NewTokenAmount(1_000_000_000_000)
|
||||
transfer = Value(abi.NewTokenAmount(10))
|
||||
)
|
||||
|
||||
func failTransferUnknownSenderKnownReceiver(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
// Set up receiver account.
|
||||
receiver := v.Actors.Account(address.SECP256K1, initial)
|
||||
v.CommitPreconditions()
|
||||
|
||||
// create a new random sender.
|
||||
sender := v.Wallet.NewSECP256k1Account()
|
||||
|
||||
// perform the transfer.
|
||||
v.Messages.Sugar().Transfer(sender, receiver.Robust, transfer, Nonce(0))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrSenderInvalid))
|
||||
v.Assert.ActorMissing(sender)
|
||||
v.Assert.ActorExists(receiver.Robust)
|
||||
v.Assert.BalanceEq(receiver.Robust, initial)
|
||||
}
|
||||
|
||||
func failTransferUnknownSenderUnknownReceiver(v *Builder) {
|
||||
v.Messages.SetDefaults(GasLimit(1_000_000_000), GasPremium(1), GasFeeCap(200))
|
||||
|
||||
// no accounts in the system.
|
||||
v.CommitPreconditions()
|
||||
|
||||
// create new random senders and resceivers.
|
||||
sender, receiver := v.Wallet.NewSECP256k1Account(), v.Wallet.NewSECP256k1Account()
|
||||
|
||||
// perform the transfer.
|
||||
v.Messages.Sugar().Transfer(sender, receiver, transfer, Nonce(0))
|
||||
v.CommitApplies()
|
||||
|
||||
v.Assert.EveryMessageResultSatisfies(ExitCode(exitcode.SysErrSenderInvalid))
|
||||
v.Assert.ActorMissing(sender)
|
||||
v.Assert.ActorMissing(receiver)
|
||||
}
|
Loading…
Reference in New Issue
Block a user