diff --git a/payment-channel-ckb/.gitignore b/payment-channel-ckb/.gitignore
new file mode 100644
index 0000000..336fc31
--- /dev/null
+++ b/payment-channel-ckb/.gitignore
@@ -0,0 +1,28 @@
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# log files
+*.log
+
+perun-ckb-demo
+
+# Dependency directories (remove the comment below to include it)
+# vendor/
+devnet/accounts
+devnet/data
+devnet/specs
+devnet/ckb-miner.toml
+devnet/ckb.toml
+devnet/default.db-options
+devnet/system_scripts
+.vscode/
diff --git a/payment-channel-ckb/.gitmodules b/payment-channel-ckb/.gitmodules
new file mode 100644
index 0000000..5bc5ff8
--- /dev/null
+++ b/payment-channel-ckb/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "devnet/contracts"]
+ path = devnet/contracts
+ url = git@github.com:perun-network/perun-ckb-contract
diff --git a/payment-channel-ckb/client/balances.go b/payment-channel-ckb/client/balances.go
new file mode 100644
index 0000000..a322278
--- /dev/null
+++ b/payment-channel-ckb/client/balances.go
@@ -0,0 +1,89 @@
+package client
+
+import (
+ "context"
+ "encoding/binary"
+ "fmt"
+ "log"
+ "math"
+ "math/big"
+ "strconv"
+ "time"
+
+ "github.com/nervosnetwork/ckb-sdk-go/v2/indexer"
+ "github.com/nervosnetwork/ckb-sdk-go/v2/types"
+ "perun.network/perun-ckb-backend/wallet/address"
+)
+
+type BalanceExtractor func(*indexer.LiveCell) *big.Int
+
+func ckbBalanceExtractor(cell *indexer.LiveCell) *big.Int {
+ return new(big.Int).SetUint64(cell.Output.Capacity)
+}
+
+func sudtBalanceExtractor(cell *indexer.LiveCell) *big.Int {
+ if len(cell.OutputData) != 16 {
+ return big.NewInt(0)
+ }
+ return new(big.Int).SetUint64(binary.LittleEndian.Uint64(cell.OutputData))
+}
+
+func (p *PaymentClient) PollBalances() {
+ defer log.Println("PollBalances: stopped")
+ pollingInterval := time.Second
+ searchKey := &indexer.SearchKey{
+ Script: address.AsParticipant(p.Account.Address()).PaymentScript,
+ ScriptType: types.ScriptTypeLock,
+ ScriptSearchMode: types.ScriptSearchModeExact,
+ Filter: nil,
+ WithData: true,
+ }
+ log.Println("PollBalances")
+ updateBalance := func() {
+ ctx, _ := context.WithTimeout(context.Background(), pollingInterval)
+
+ cells, err := p.rpcClient.GetCells(ctx, searchKey, indexer.SearchOrderDesc, math.MaxUint32, "")
+ if err != nil {
+ log.Println("balance poll error: ", err)
+ return
+ }
+ ckbBalance := big.NewInt(0)
+ sudtBalance := big.NewInt(0)
+ for _, cell := range cells.Objects {
+ ckbBalance = new(big.Int).Add(ckbBalance, ckbBalanceExtractor(cell))
+ sudtBalance = new(big.Int).Add(sudtBalance, sudtBalanceExtractor(cell))
+ }
+
+ p.balanceMutex.Lock()
+ if ckbBalance.Cmp(p.balance) != 0 || sudtBalance.Cmp(p.sudtBalance) != 0 {
+ // Update ckb balance.
+ p.balance = ckbBalance
+ //ckbBal := p.balance.Int64()
+
+ // Update sudt balance.
+ p.sudtBalance = sudtBalance
+
+ p.balanceMutex.Unlock()
+ //p.NotifyAllBalance(ckbBal) // TODO: Update demo tui to allow for big.Int balances
+ } else {
+ p.balanceMutex.Unlock()
+ }
+ }
+ updateBalance()
+ //return "CKbytes: " + p.balance.String() + ", SUDT: " + p.sudtBalance.String()
+ /*
+ // Poll the balance every 5 seconds.
+ for {
+ updateBalance()
+ time.Sleep(pollingInterval)
+ }
+ */
+}
+
+func FormatBalance(ckbBal, sudtBal *big.Int) string {
+ log.Printf("balances: ckb = %s || sudt = %s", ckbBal.String(), sudtBal.String())
+ balCKByte, _ := ShannonToCKByte(ckbBal).Float64()
+ return fmt.Sprintf("[green]%s\t[yellow]%s[white]",
+ strconv.FormatFloat(balCKByte, 'f', 2, 64)+" CKByte",
+ fmt.Sprintf("%v", sudtBal.Int64())+" SUDT")
+}
diff --git a/payment-channel-ckb/client/channel.go b/payment-channel-ckb/client/channel.go
new file mode 100644
index 0000000..05a5b61
--- /dev/null
+++ b/payment-channel-ckb/client/channel.go
@@ -0,0 +1,91 @@
+package client
+
+import (
+ "context"
+ "fmt"
+ "math/big"
+
+ "perun.network/go-perun/channel"
+ "perun.network/go-perun/client"
+)
+
+type PaymentChannel struct {
+ ch *client.Channel
+ assets []channel.Asset
+}
+
+// newPaymentChannel creates a new payment channel.
+func newPaymentChannel(ch *client.Channel, assets []channel.Asset) *PaymentChannel {
+ return &PaymentChannel{
+ ch: ch,
+ assets: assets,
+ }
+}
+
+func (c PaymentChannel) State() *channel.State {
+ return c.ch.State().Clone()
+}
+
+func (c PaymentChannel) SendPayment(amounts map[channel.Asset]float64) {
+ // Transfer the given amount from us to peer.
+ // Use UpdateBy to update the channel state.
+ err := c.ch.Update(context.TODO(), func(state *channel.State) {
+ actor := c.ch.Idx()
+ peer := 1 - actor
+ //fmt.Println("Send payment handler called")
+ for a, amount := range amounts {
+ fmt.Println(a)
+ fmt.Println(amount)
+ if amount < 0 {
+ continue
+ }
+ /*
+ switch a := a.(type) {
+ case *asset.Asset:
+ if a.IsCKBytes {
+ fmt.Println("inside condition isCKBytes")
+ shannonAmount := CKByteToShannon(big.NewFloat(amount))
+ state.Allocation.TransferBalance(actor, peer, a, shannonAmount)
+ } else {
+ fmt.Println("inside if conditional !isCKBytes")
+ intAmount := new(big.Int).SetUint64(uint64(amount))
+ state.Allocation.TransferBalance(actor, peer, a, intAmount)
+ }
+ }
+ */
+
+ shannonAmount := CKByteToShannon(big.NewFloat(amount))
+ state.Allocation.TransferBalance(actor, peer, a, shannonAmount)
+
+ }
+
+ })
+ if err != nil {
+ panic(err)
+ }
+ if err != nil {
+ panic(err) // We panic on error to keep the code simple.
+ }
+}
+
+// Settle settles the payment channel and withdraws the funds.
+func (c PaymentChannel) Settle() {
+ // Finalize the channel to enable fast settlement.
+ if !c.ch.State().IsFinal {
+ err := c.ch.Update(context.TODO(), func(state *channel.State) {
+ state.IsFinal = true
+ })
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ // Settle concludes the channel and withdraws the funds.
+ err := c.ch.Settle(context.TODO())
+ if err != nil {
+ panic(err)
+ }
+
+ // Close frees up channel resources.
+ c.ch.Close()
+}
diff --git a/payment-channel-ckb/client/client.go b/payment-channel-ckb/client/client.go
new file mode 100644
index 0000000..7564e30
--- /dev/null
+++ b/payment-channel-ckb/client/client.go
@@ -0,0 +1,261 @@
+package client
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "math/big"
+
+ "github.com/decred/dcrd/dcrec/secp256k1/v4"
+ "github.com/nervosnetwork/ckb-sdk-go/v2/rpc"
+ "github.com/nervosnetwork/ckb-sdk-go/v2/types"
+ "github.com/perun-network/perun-libp2p-wire/p2p"
+ "perun.network/go-perun/channel"
+ gpchannel "perun.network/go-perun/channel"
+ "perun.network/go-perun/channel/persistence"
+ "perun.network/go-perun/client"
+ gpwallet "perun.network/go-perun/wallet"
+ "perun.network/go-perun/watcher/local"
+ "perun.network/go-perun/wire"
+ "perun.network/perun-ckb-backend/backend"
+ "perun.network/perun-ckb-backend/channel/adjudicator"
+ "perun.network/perun-ckb-backend/channel/asset"
+ "perun.network/perun-ckb-backend/channel/funder"
+ ckbclient "perun.network/perun-ckb-backend/client"
+ "perun.network/perun-ckb-backend/wallet"
+ "perun.network/perun-ckb-backend/wallet/address"
+ "polycry.pt/poly-go/sync"
+)
+
+type PaymentClient struct {
+ balanceMutex sync.Mutex
+ Name string
+ balance *big.Int
+ sudtBalance *big.Int
+ Account *wallet.Account
+ wAddr wire.Address
+ Network types.Network
+ PerunClient *client.Client
+ net *p2p.Net
+ channels chan *PaymentChannel
+ rpcClient rpc.Client
+}
+
+func NewPaymentClient(
+ name string,
+ network types.Network,
+ deployment backend.Deployment,
+ rpcUrl string,
+ account *wallet.Account,
+ key secp256k1.PrivateKey,
+ wallet *wallet.EphemeralWallet,
+ persistRestorer persistence.PersistRestorer,
+ wAddr wire.Address,
+ net *p2p.Net,
+
+) (*PaymentClient, error) {
+ backendRPCClient, err := rpc.Dial(rpcUrl)
+ if err != nil {
+ return nil, err
+ }
+ signer := backend.NewSignerInstance(address.AsParticipant(account.Address()).ToCKBAddress(network), key, network)
+
+ ckbClient, err := ckbclient.NewClient(backendRPCClient, *signer, deployment)
+ if err != nil {
+ return nil, err
+ }
+ f := funder.NewDefaultFunder(ckbClient, deployment)
+ a := adjudicator.NewAdjudicator(ckbClient)
+ watcher, err := local.NewWatcher(a)
+ if err != nil {
+ return nil, err
+ }
+ /*
+ wireAcc := p2p.NewRandomAccount(rand.New(rand.NewSource(time.Now().UnixNano())))
+ net, err := p2p.NewP2PBus(wireAcc)
+ if err != nil {
+ panic(errors.Wrap(err, "creating p2p net"))
+ }
+ bus := net.Bus
+ listener := net.Listener
+
+ //wAddr := simple.NewAddress(account.Address().String())
+ wAddr := wireAcc.Address()
+ */
+ perunClient, err := client.New(wAddr, net.Bus, f, a, wallet, watcher)
+ if err != nil {
+ return nil, err
+ }
+ perunClient.EnablePersistence(persistRestorer)
+
+ balanceRPC, err := rpc.Dial(rpcUrl)
+ if err != nil {
+ return nil, err
+ }
+ p := &PaymentClient{
+ Name: name,
+ balance: big.NewInt(0),
+ sudtBalance: big.NewInt(0),
+ Account: account,
+ wAddr: wAddr,
+ Network: network,
+ PerunClient: perunClient,
+ channels: make(chan *PaymentChannel, 1),
+ rpcClient: balanceRPC,
+ net: net,
+ }
+
+ //go p.PollBalances()
+ go perunClient.Handle(p, p)
+ return p, nil
+}
+
+// WalletAddress returns the wallet address of the client.
+func (p *PaymentClient) WalletAddress() gpwallet.Address {
+ return p.Account.Address()
+}
+
+func (p *PaymentClient) WireAddress() wire.Address {
+ return p.wAddr
+}
+
+func (p *PaymentClient) PeerID() string {
+ walletAddr := p.wAddr.(*p2p.Address)
+ return walletAddr.ID.String()
+}
+
+func (p *PaymentClient) GetSudtBalance() *big.Int {
+ p.balanceMutex.Lock()
+ defer p.balanceMutex.Unlock()
+ return new(big.Int).Set(p.sudtBalance)
+}
+
+// TODO: Remove as probably not required
+/*
+func (p *PaymentClient) NotifyAllBalance(ckbBal int64) string {
+ str := FormatBalance(new(big.Int).SetInt64(ckbBal), p.GetSudtBalance())
+ return str
+}
+*/
+
+// GetBalances retrieves the current balances of the client.
+func (p *PaymentClient) GetBalances() string {
+ p.PollBalances()
+ return FormatBalance(p.balance, p.sudtBalance)
+}
+
+// OpenChannel opens a new channel with the specified peer and funding.
+func (p *PaymentClient) OpenChannel(peer wire.Address, peerID string, amounts map[gpchannel.Asset]float64) *PaymentChannel {
+ // We define the channel participants. The proposer always has index 0. Here
+ // we use the on-chain addresses as off-chain addresses, but we could also
+ // use different ones.
+ log.Println("OpenChannel called")
+ participants := []wire.Address{p.WireAddress(), peer}
+ p.net.Dialer.Register(peer, peerID)
+
+ assets := make([]gpchannel.Asset, len(amounts))
+ i := 0
+ for a := range amounts {
+ assets[i] = a
+ i++
+ }
+
+ // We create an initial allocation which defines the starting balances.
+ initAlloc := gpchannel.NewAllocation(2, assets...)
+ log.Println(initAlloc.Assets)
+ for a, amount := range amounts {
+ switch a := a.(type) {
+ case *asset.Asset:
+ if a.IsCKBytes {
+ initAlloc.SetAssetBalances(a, []gpchannel.Bal{
+ CKByteToShannon(big.NewFloat(amount)), // Our initial balance.
+ CKByteToShannon(big.NewFloat(amount)), // Peer's initial balance.
+ })
+ } else {
+ intAmount := new(big.Int).SetUint64(uint64(amount))
+ initAlloc.SetAssetBalances(a, []gpchannel.Bal{
+ intAmount, // Our initial balance.
+ intAmount, // Peer's initial balance.
+ })
+ }
+ default:
+ panic("Asset is not of type *asset.Asset")
+ }
+
+ }
+ log.Println("Created Allocation")
+
+ // Prepare the channel proposal by defining the channel parameters.
+ challengeDuration := uint64(10) // On-chain challenge duration in seconds.
+ proposal, err := client.NewLedgerChannelProposal(
+ challengeDuration,
+ p.Account.Address(),
+ initAlloc,
+ participants,
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ log.Println("Created Proposal")
+
+ // Send the proposal.
+ ch, err := p.PerunClient.ProposeChannel(context.TODO(), proposal)
+ if err != nil {
+ panic(err)
+ }
+
+ log.Println("Sent Channel")
+
+ // Start the on-chain event watcher. It automatically handles disputes.
+ p.startWatching(ch)
+
+ log.Println("Started Watching")
+
+ //p.Channel = newPaymentChannel(ch, assets)
+ return newPaymentChannel(ch, assets)
+}
+
+// startWatching starts the dispute watcher for the specified channel.
+func (p *PaymentClient) startWatching(ch *client.Channel) {
+ go func() {
+ err := ch.Watch(p)
+ if err != nil {
+ fmt.Printf("Watcher returned with error: %v", err)
+ }
+ }()
+}
+
+func (p *PaymentClient) AcceptedChannel() *PaymentChannel {
+ return <-p.channels
+}
+
+func (p *PaymentClient) Shutdown() {
+ p.PerunClient.Close()
+}
+
+func (c *PaymentClient) Restore(peer wire.Address, peerID string) []*PaymentChannel {
+ var restoredChannels []*client.Channel
+ //c.net.Dialer.Register(peer, peerID)
+ //TODO: Remove this hack. Find why asset is not found upon restoring
+ c.PerunClient.OnNewChannel(func(ch *client.Channel) {
+ restoredChannels = append(restoredChannels, ch)
+ })
+
+ err := c.PerunClient.Restore(context.TODO())
+ if err != nil {
+ fmt.Println("Error restoring channels")
+ }
+
+ paymentChannels := make([]*PaymentChannel, len(restoredChannels))
+ assets := make([]channel.Asset, 1)
+ assets = append(assets, &asset.Asset{
+ IsCKBytes: true,
+ SUDT: nil,
+ })
+ for i, ch := range restoredChannels {
+ paymentChannels[i] = newPaymentChannel(ch, assets)
+ }
+
+ return paymentChannels
+}
diff --git a/payment-channel-ckb/client/handle.go b/payment-channel-ckb/client/handle.go
new file mode 100644
index 0000000..b509ac1
--- /dev/null
+++ b/payment-channel-ckb/client/handle.go
@@ -0,0 +1,92 @@
+package client
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "perun.network/go-perun/channel"
+ "perun.network/go-perun/client"
+)
+
+// HandleProposal is the callback for incoming channel proposals.
+func (p *PaymentClient) HandleProposal(prop client.ChannelProposal, r *client.ProposalResponder) {
+ lcp, err := func() (*client.LedgerChannelProposalMsg, error) {
+ // Ensure that we got a ledger channel proposal.
+ lcp, ok := prop.(*client.LedgerChannelProposalMsg)
+ if !ok {
+ return nil, fmt.Errorf("invalid proposal type: %T", p)
+ }
+
+ // Check that we have the correct number of participants.
+ if lcp.NumPeers() != 2 {
+ return nil, fmt.Errorf("invalid number of participants: %d", lcp.NumPeers())
+ }
+ // Check that the channel has the expected assets and funding balances.
+ for i, assetAlloc := range lcp.FundingAgreement {
+ if assetAlloc[0].Cmp(assetAlloc[1]) != 0 {
+ return nil, fmt.Errorf("invalid funding balance for asset %d: %v", i, assetAlloc)
+ }
+
+ }
+ return lcp, nil
+ }()
+ if err != nil {
+ _ = r.Reject(context.TODO(), err.Error())
+ }
+
+ // Create a channel accept message and send it.
+ accept := lcp.Accept(
+ p.WalletAddress(), // The Account we use in the channel.
+ client.WithRandomNonce(), // Our share of the channel nonce.
+ )
+ ch, err := r.Accept(context.TODO(), accept)
+ if err != nil {
+ log.Printf("Error accepting channel proposal: %v", err)
+ return
+ }
+
+ //TODO: startWatching
+ // Start the on-chain event watcher. It automatically handles disputes.
+ p.startWatching(ch)
+
+ // Store channel.
+ p.channels <- newPaymentChannel(ch, lcp.InitBals.Clone().Assets)
+ //p.AcceptedChannel()
+}
+
+// HandleUpdate is the callback for incoming channel updates.
+func (p *PaymentClient) HandleUpdate(cur *channel.State, next client.ChannelUpdate, r *client.UpdateResponder) {
+ // We accept every update that increases our balance.
+ err := func() error {
+ err := channel.AssertAssetsEqual(cur.Assets, next.State.Assets)
+ if err != nil {
+ return fmt.Errorf("invalid assets: %v", err)
+ }
+
+ receiverIdx := 1 - next.ActorIdx // This works because we are in a two-party channel.
+ for _, a := range cur.Assets {
+ curBal := cur.Allocation.Balance(receiverIdx, a)
+ nextBal := next.State.Allocation.Balance(receiverIdx, a)
+ if nextBal.Cmp(curBal) < 0 {
+ return fmt.Errorf("invalid balance: %v", nextBal)
+ }
+ }
+
+ return nil
+ }()
+ if err != nil {
+ _ = r.Reject(context.TODO(), err.Error())
+ }
+
+ // Send the acceptance message.
+ err = r.Accept(context.TODO())
+ if err != nil {
+ panic(err)
+ }
+}
+
+// HandleAdjudicatorEvent is the callback for smart contract events.
+func (p *PaymentClient) HandleAdjudicatorEvent(e channel.AdjudicatorEvent) {
+ log.Printf("Adjudicator event: type = %T, client = %v", e, p.Account)
+}
diff --git a/payment-channel-ckb/client/util.go b/payment-channel-ckb/client/util.go
new file mode 100644
index 0000000..e0d8c11
--- /dev/null
+++ b/payment-channel-ckb/client/util.go
@@ -0,0 +1,22 @@
+package client
+
+import (
+ "math/big"
+)
+
+// CKByteToShannon converts a given amount in CKByte to Shannon.
+func CKByteToShannon(ckbyteAmount *big.Float) (shannonAmount *big.Int) {
+ shannonPerCKByte := new(big.Int).Exp(big.NewInt(10), big.NewInt(8), nil)
+ shannonPerCKByteFloat := new(big.Float).SetInt(shannonPerCKByte)
+ shannonAmountFloat := new(big.Float).Mul(ckbyteAmount, shannonPerCKByteFloat)
+ shannonAmount, _ = shannonAmountFloat.Int(nil)
+ return shannonAmount
+}
+
+// ShannonToCKByte converts a given amount in Shannon to CKByte.
+func ShannonToCKByte(shannonAmount *big.Int) *big.Float {
+ shannonPerCKByte := new(big.Int).Exp(big.NewInt(10), big.NewInt(8), nil)
+ shannonPerCKByteFloat := new(big.Float).SetInt(shannonPerCKByte)
+ shannonAmountFloat := new(big.Float).SetInt(shannonAmount)
+ return new(big.Float).Quo(shannonAmountFloat, shannonPerCKByteFloat)
+}
diff --git a/payment-channel-ckb/deployment/deployment.go b/payment-channel-ckb/deployment/deployment.go
new file mode 100644
index 0000000..376c050
--- /dev/null
+++ b/payment-channel-ckb/deployment/deployment.go
@@ -0,0 +1,159 @@
+package deployment
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "path"
+ "strings"
+
+ "github.com/nervosnetwork/ckb-sdk-go/v2/types"
+ "perun.network/perun-ckb-backend/backend"
+)
+
+const PFLSMinCapacity = 4100000032
+
+type SUDTInfo struct {
+ Script *types.Script
+ CellDep *types.CellDep
+}
+
+type Migration struct {
+ CellRecipes []struct {
+ Name string `json:"name"`
+ TxHash string `json:"tx_hash"`
+ Index uint32 `json:"index"`
+ OccupiedCapacity int64 `json:"occupied_capacity"`
+ DataHash string `json:"data_hash"`
+ TypeId interface{} `json:"type_id"`
+ } `json:"cell_recipes"`
+ DepGroupRecipes []interface{} `json:"dep_group_recipes"`
+}
+
+func (m Migration) MakeDeployment(systemScripts SystemScripts, sudtOwnerLockArg string) (backend.Deployment, SUDTInfo, error) {
+ pcts := m.CellRecipes[0]
+ if pcts.Name != "pcts" {
+ return backend.Deployment{}, SUDTInfo{}, fmt.Errorf("first cell recipe must be pcts")
+ }
+ pcls := m.CellRecipes[1]
+ if pcls.Name != "pcls" {
+ return backend.Deployment{}, SUDTInfo{}, fmt.Errorf("second cell recipe must be pcls")
+ }
+ pfls := m.CellRecipes[2]
+ if pfls.Name != "pfls" {
+ return backend.Deployment{}, SUDTInfo{}, fmt.Errorf("third cell recipe must be pfls")
+ }
+ sudtInfo, err := m.GetSUDT()
+ if err != nil {
+ return backend.Deployment{}, SUDTInfo{}, err
+ }
+ // NOTE: The SUDT lock-arg always contains a newline character at the end.
+ hexString := strings.ReplaceAll(sudtOwnerLockArg[2:], "\n", "")
+ hexString = strings.ReplaceAll(hexString, "\r", "")
+ hexString = strings.ReplaceAll(hexString, " ", "")
+ sudtInfo.Script.Args, err = hex.DecodeString(hexString)
+ if err != nil {
+ return backend.Deployment{}, SUDTInfo{}, fmt.Errorf("invalid sudt owner lock arg: %v", err)
+ }
+
+ return backend.Deployment{
+ Network: types.NetworkTest,
+ PCTSDep: types.CellDep{
+ OutPoint: &types.OutPoint{
+ TxHash: types.HexToHash(pcts.TxHash),
+ Index: m.CellRecipes[0].Index,
+ },
+ DepType: types.DepTypeCode,
+ },
+ PCLSDep: types.CellDep{
+ OutPoint: &types.OutPoint{
+ TxHash: types.HexToHash(pcls.TxHash),
+ Index: m.CellRecipes[0].Index,
+ },
+ DepType: types.DepTypeCode,
+ },
+ PFLSDep: types.CellDep{
+ OutPoint: &types.OutPoint{
+ TxHash: types.HexToHash(pfls.TxHash),
+ Index: m.CellRecipes[0].Index,
+ },
+ DepType: types.DepTypeCode,
+ },
+ PCTSCodeHash: types.HexToHash(pcts.DataHash),
+ PCTSHashType: types.HashTypeData1,
+ PCLSCodeHash: types.HexToHash(pcls.DataHash),
+ PCLSHashType: types.HashTypeData1,
+ PFLSCodeHash: types.HexToHash(pfls.DataHash),
+ PFLSHashType: types.HashTypeData1,
+ PFLSMinCapacity: PFLSMinCapacity,
+ DefaultLockScript: types.Script{
+ CodeHash: systemScripts.Secp256k1Blake160SighashAll.ScriptID.CodeHash,
+ HashType: systemScripts.Secp256k1Blake160SighashAll.ScriptID.HashType,
+ Args: make([]byte, 32),
+ },
+ DefaultLockScriptDep: systemScripts.Secp256k1Blake160SighashAll.CellDep,
+ SUDTDeps: map[types.Hash]types.CellDep{
+ sudtInfo.Script.Hash(): *sudtInfo.CellDep,
+ },
+ SUDTs: map[types.Hash]types.Script{
+ sudtInfo.Script.Hash(): *sudtInfo.Script,
+ },
+ }, *sudtInfo, nil
+}
+
+func (m Migration) GetSUDT() (*SUDTInfo, error) {
+ sudt := m.CellRecipes[3]
+ if sudt.Name != "sudt" {
+ return nil, fmt.Errorf("fourth cell recipe must be sudt")
+ }
+
+ sudtScript := types.Script{
+ CodeHash: types.HexToHash(sudt.DataHash),
+ HashType: types.HashTypeData1,
+ Args: []byte{},
+ }
+ sudtCellDep := types.CellDep{
+ OutPoint: &types.OutPoint{
+ TxHash: types.HexToHash(sudt.TxHash),
+ Index: sudt.Index,
+ },
+ DepType: types.DepTypeCode,
+ }
+ return &SUDTInfo{
+ Script: &sudtScript,
+ CellDep: &sudtCellDep,
+ }, nil
+}
+
+func GetDeployment(migrationDir, systemScriptsDir, sudtOwnerLockArg string) (backend.Deployment, SUDTInfo, error) {
+ dir, err := os.ReadDir(migrationDir)
+ if err != nil {
+ return backend.Deployment{}, SUDTInfo{}, err
+ }
+ if len(dir) != 1 {
+ return backend.Deployment{}, SUDTInfo{}, fmt.Errorf("migration dir must contain exactly one file")
+ }
+ migrationName := dir[0].Name()
+ migrationFile, err := os.Open(path.Join(migrationDir, migrationName))
+ defer migrationFile.Close()
+ if err != nil {
+ return backend.Deployment{}, SUDTInfo{}, err
+ }
+ migrationData, err := io.ReadAll(migrationFile)
+ if err != nil {
+ return backend.Deployment{}, SUDTInfo{}, err
+ }
+ var migration Migration
+ err = json.Unmarshal(migrationData, &migration)
+ if err != nil {
+ return backend.Deployment{}, SUDTInfo{}, err
+ }
+
+ ss, err := GetSystemScripts(systemScriptsDir)
+ if err != nil {
+ return backend.Deployment{}, SUDTInfo{}, err
+ }
+ return migration.MakeDeployment(ss, sudtOwnerLockArg)
+}
diff --git a/payment-channel-ckb/deployment/keys.go b/payment-channel-ckb/deployment/keys.go
new file mode 100644
index 0000000..1d7d6bc
--- /dev/null
+++ b/payment-channel-ckb/deployment/keys.go
@@ -0,0 +1,33 @@
+package deployment
+
+import (
+ "encoding/hex"
+ "fmt"
+ "github.com/decred/dcrd/dcrec/secp256k1/v4"
+ "io"
+ "os"
+ "strings"
+)
+
+func GetKey(path string) (*secp256k1.PrivateKey, error) {
+ keyFile, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer keyFile.Close()
+
+ rawBytes, err := io.ReadAll(keyFile)
+ if err != nil {
+ return nil, err
+ }
+ lines := strings.Split(string(rawBytes), "\n")
+ if len(lines) != 2 {
+ return nil, fmt.Errorf("key file must contain exactly two lines")
+ }
+ x := strings.Trim(lines[0], " \n")
+ xBytes, err := hex.DecodeString(x)
+ if err != nil {
+ return nil, err
+ }
+ return secp256k1.PrivKeyFromBytes(xBytes), nil
+}
diff --git a/payment-channel-ckb/deployment/system_scripts.go b/payment-channel-ckb/deployment/system_scripts.go
new file mode 100644
index 0000000..77ee117
--- /dev/null
+++ b/payment-channel-ckb/deployment/system_scripts.go
@@ -0,0 +1,58 @@
+package deployment
+
+import (
+ "encoding/json"
+ "io"
+ "os"
+ "path"
+
+ "github.com/nervosnetwork/ckb-sdk-go/v2/types"
+)
+
+type SystemScripts struct {
+ DAO struct {
+ CellDep types.CellDep `json:"cell_dep"`
+ ScriptID ScriptID `json:"script_id"`
+ } `json:"dao"`
+ Secp256k1Blake160MultisigAll struct {
+ CellDep types.CellDep `json:"cell_dep"`
+ ScriptID ScriptID `json:"script_id"`
+ } `json:"secp256k1_blake160_multisig_all"`
+ Secp256k1Blake160SighashAll struct {
+ CellDep types.CellDep `json:"cell_dep"`
+ ScriptID ScriptID `json:"script_id"`
+ } `json:"secp256k1_blake160_sighash_all"`
+ Secp256k1Data types.OutPoint `json:"secp256k1_data"`
+ TypeID struct {
+ ScriptID ScriptID `json:"script_id"`
+ } `json:"type_id"`
+}
+
+type ScriptID struct {
+ CodeHash types.Hash `json:"code_hash"`
+ HashType types.ScriptHashType `json:"hash_type"`
+}
+
+const systemScriptName = "default_scripts.json"
+
+func GetSystemScripts(systemScriptDir string) (SystemScripts, error) {
+ var ss SystemScripts
+ err := readJSON(systemScriptDir, &ss)
+ if err != nil {
+ return SystemScripts{}, err
+ }
+ return ss, nil
+}
+
+func readJSON(systemScriptDir string, systemScripts *SystemScripts) error {
+ systemScriptFile, err := os.Open(path.Join(systemScriptDir, systemScriptName))
+ defer func() { _ = systemScriptFile.Close() }()
+ if err != nil {
+ return err
+ }
+ systemScriptContent, err := io.ReadAll(systemScriptFile)
+ if err != nil {
+ return err
+ }
+ return json.Unmarshal(systemScriptContent, systemScripts)
+}
diff --git a/payment-channel-ckb/deployment/system_scripts_test.go b/payment-channel-ckb/deployment/system_scripts_test.go
new file mode 100644
index 0000000..d7246a0
--- /dev/null
+++ b/payment-channel-ckb/deployment/system_scripts_test.go
@@ -0,0 +1,21 @@
+package deployment_test
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "perun.network/perun-ckb-demo/deployment"
+)
+
+func TestSystemScripts(t *testing.T) {
+ var ss deployment.SystemScripts
+ require.NoError(t, json.Unmarshal([]byte(systemScriptCase), &ss))
+ msg, err := json.Marshal(ss)
+ require.NoError(t, err)
+ recovered := new(deployment.SystemScripts)
+ require.NoError(t, json.Unmarshal(msg, recovered))
+ require.Equal(t, ss, *recovered)
+}
+
+var systemScriptCase string = "{\"dao\":{\"cell_dep\":{\"dep_type\":\"code\",\"out_point\":{\"index\":\"0x2\",\"tx_hash\":\"0x297d19805fee99a53a6274a976df562d678beeff286776e1cd5ac9d8e1870780\"}},\"script_id\":{\"code_hash\":\"0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e\",\"hash_type\":\"type\"}},\"secp256k1_blake160_multisig_all\":{\"cell_dep\":{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x1\",\"tx_hash\":\"0xad69fbce31c6d8a8516789dec3cd4ddecbeb63619b4fa6cd3a7d00cdc788bf33\"}},\"script_id\":{\"code_hash\":\"0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8\",\"hash_type\":\"type\"}},\"secp256k1_blake160_sighash_all\":{\"cell_dep\":{\"dep_type\":\"dep_group\",\"out_point\":{\"index\":\"0x0\",\"tx_hash\":\"0xad69fbce31c6d8a8516789dec3cd4ddecbeb63619b4fa6cd3a7d00cdc788bf33\"}},\"script_id\":{\"code_hash\":\"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8\",\"hash_type\":\"type\"}},\"secp256k1_data\":{\"out_point\":{\"index\":\"0x3\",\"tx_hash\":\"0x297d19805fee99a53a6274a976df562d678beeff286776e1cd5ac9d8e1870780\"}},\"type_id\":{\"script_id\":{\"code_hash\":\"0x00000000000000000000000000000000000000000000000000545950455f4944\",\"hash_type\":\"type\"}}}"
diff --git a/payment-channel-ckb/devnet/Makefile b/payment-channel-ckb/devnet/Makefile
new file mode 100644
index 0000000..621fcad
--- /dev/null
+++ b/payment-channel-ckb/devnet/Makefile
@@ -0,0 +1,2 @@
+dev:
+ tmuxp load ./devnet-session.yaml
diff --git a/payment-channel-ckb/devnet/contracts/.assets/go-perun.png b/payment-channel-ckb/devnet/contracts/.assets/go-perun.png
new file mode 100644
index 0000000..6d8787b
Binary files /dev/null and b/payment-channel-ckb/devnet/contracts/.assets/go-perun.png differ
diff --git a/payment-channel-ckb/devnet/contracts/.github/workflows/rust.yml b/payment-channel-ckb/devnet/contracts/.github/workflows/rust.yml
new file mode 100644
index 0000000..948acce
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/.github/workflows/rust.yml
@@ -0,0 +1,38 @@
+name: Rust
+
+on:
+ push:
+ branches: [ "dev" ]
+ pull_request:
+ branches: [ "dev" ]
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/cache@v2
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ target
+ tests/target
+ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ - name: Capsule
+ run: cargo install --version 0.9.2 ckb-capsule
+ - name: Build perun-common
+ run: cargo build
+ working-directory: contracts/perun-common
+ - name: Test perun-common
+ run: cargo test
+ working-directory: contracts/perun-common
+ - name: Build contracts
+ run: capsule build
+ - name: Test contracts
+ run: capsule test
diff --git a/payment-channel-ckb/devnet/contracts/.gitignore b/payment-channel-ckb/devnet/contracts/.gitignore
new file mode 100644
index 0000000..8ce9103
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/.gitignore
@@ -0,0 +1,14 @@
+# Rust
+target/*
+.cache/.cargo/*
+contracts/**/target/*
+tests/target/*
+migrations/**
+
+# C
+contracts/c/build/*
+
+# others
+build/*
+!.gitkeep
+.tmp/
diff --git a/payment-channel-ckb/devnet/contracts/Cargo.lock b/payment-channel-ckb/devnet/contracts/Cargo.lock
new file mode 100644
index 0000000..d54baf9
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/Cargo.lock
@@ -0,0 +1,842 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
+
+[[package]]
+name = "base16ct"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "blake2b-ref"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95916998c798756098a4eb1b3f2cd510659705a9817bf203d61abd30fbec3e7b"
+
+[[package]]
+name = "blake2b-rs"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89a8565807f21b913288968e391819e7f9b2f0f46c7b89549c051cccf3a2771"
+dependencies = [
+ "cc",
+ "cty",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "buddy-alloc"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3240a4cb09cf0da6a51641bd40ce90e96ea6065e3a1adc46434029254bcc2d09"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "ckb-channel"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "920f26cc48cadcaf6f7bcc3960fde9f9f355633b6361da8ef31e1e1c00fc8858"
+dependencies = [
+ "crossbeam-channel",
+]
+
+[[package]]
+name = "ckb-error"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446a519d8a847d97f1c8ece739dc1748751a9a2179249c96c45cced0825a7aa5"
+dependencies = [
+ "anyhow",
+ "ckb-occupied-capacity",
+ "derive_more",
+ "thiserror",
+]
+
+[[package]]
+name = "ckb-fixed-hash"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00cbbc455b23748b32e06d16628a03e30d56ffa057f17093fdf5b42d4fb6c879"
+dependencies = [
+ "ckb-fixed-hash-core",
+ "ckb-fixed-hash-macros",
+]
+
+[[package]]
+name = "ckb-fixed-hash-core"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4e644a4e026625b4be5a04cdf6c02043080e79feaf77d9cdbb2f0e6553f751"
+dependencies = [
+ "faster-hex",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "ckb-fixed-hash-macros"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cfc980ef88c217825172eb46df269f47890f5e78a38214416f13b3bd17a4b4"
+dependencies = [
+ "ckb-fixed-hash-core",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ckb-hash"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d9b683e89ae4ffdd5aaf4172eab00b6bbe7ea24e2abf77d3eb850ba36e8983"
+dependencies = [
+ "blake2b-ref",
+ "blake2b-rs",
+]
+
+[[package]]
+name = "ckb-merkle-mountain-range"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ccb671c5921be8a84686e6212ca184cb1d7c51cadcdbfcbd1cc3f042f5dfb8"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "ckb-occupied-capacity"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2a1dd0d4ba5dafba1e30d437c1148b20f42edb76b6794323e05bda626754eb"
+dependencies = [
+ "ckb-occupied-capacity-core",
+ "ckb-occupied-capacity-macros",
+]
+
+[[package]]
+name = "ckb-occupied-capacity-core"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ebba3d564098a84c83f4740e1dce48a5e2da759becdb47e3c7965f0808e6e92"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "ckb-occupied-capacity-macros"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6321bba85cdf9724029d8c906851dd4a90906869b42f9100b16645a1261d4c"
+dependencies = [
+ "ckb-occupied-capacity-core",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ckb-rational"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2519249f8d47fa758d3fb3cf3049327c69ce0f2acd79d61427482c8661d3dbd"
+dependencies = [
+ "numext-fixed-uint",
+ "serde",
+]
+
+[[package]]
+name = "ckb-standalone-types"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22d7cbbdab96e6b809a102cf88bfec28795a0a3c06bfdea4abe4de89777801cd"
+dependencies = [
+ "cfg-if",
+ "molecule",
+]
+
+[[package]]
+name = "ckb-std"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47a6ad40455c446ad6fbb303dae24827fc309f43558f59d1f1b863a9de3e9f81"
+dependencies = [
+ "buddy-alloc",
+ "cc",
+ "ckb-standalone-types",
+ "cstr_core",
+]
+
+[[package]]
+name = "ckb-types"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c22b3b1ca8f88a8f48e2f73321c0605281c9c6f1e1c4d651c6138265c22291e"
+dependencies = [
+ "bit-vec",
+ "bytes",
+ "ckb-channel",
+ "ckb-error",
+ "ckb-fixed-hash",
+ "ckb-hash",
+ "ckb-merkle-mountain-range",
+ "ckb-occupied-capacity",
+ "ckb-rational",
+ "derive_more",
+ "merkle-cbt",
+ "molecule",
+ "numext-fixed-uint",
+ "once_cell",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-bigint"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cstr_core"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd98742e4fdca832d40cab219dc2e3048de17d873248f83f17df47c1bea70956"
+dependencies = [
+ "cty",
+ "memchr",
+]
+
+[[package]]
+name = "cty"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
+
+[[package]]
+name = "der"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
+dependencies = [
+ "const-oid",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "ecdsa"
+version = "0.14.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
+dependencies = [
+ "der",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+]
+
+[[package]]
+name = "elliptic-curve"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "der",
+ "digest",
+ "ff",
+ "generic-array",
+ "group",
+ "rand_core 0.6.4",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "faster-hex"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51e2ce894d53b295cf97b05685aa077950ff3e8541af83217fc720a6437169f8"
+
+[[package]]
+name = "ff"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
+dependencies = [
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "group"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7"
+dependencies = [
+ "ff",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "heapsize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "k256"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b"
+dependencies = [
+ "cfg-if",
+ "ecdsa",
+ "elliptic-curve",
+ "sha2",
+ "sha3",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768"
+dependencies = [
+ "cpufeatures",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.141"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "merkle-cbt"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "171d2f700835121c3b04ccf0880882987a050fd5c7ae88148abf537d33dd3a56"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "molecule"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edc8276c02a006bddad7d1c28c1a88f30421e1b5f0ba0ca96ceb8077c7d20c01"
+dependencies = [
+ "bytes",
+ "cfg-if",
+ "faster-hex",
+]
+
+[[package]]
+name = "numext-constructor"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "621fe0f044729f810c6815cdd77e8f5e0cd803ce4f6a38380ebfc1322af98661"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "numext-fixed-uint"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c68c76f96d589d1009a666c5072f37f3114d682696505f2cf445f27766c7d70"
+dependencies = [
+ "numext-fixed-uint-core",
+ "numext-fixed-uint-hack",
+]
+
+[[package]]
+name = "numext-fixed-uint-core"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6aab1d6457b97b49482f22a92f0f58a2f39bdd7f3b2f977eae67e8bc206aa980"
+dependencies = [
+ "heapsize",
+ "numext-constructor",
+ "rand",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "numext-fixed-uint-hack"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0200f8d55c36ec1b6a8cf810115be85d4814f045e0097dfd50033ba25adb4c9e"
+dependencies = [
+ "numext-fixed-uint-core",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "perun-channel-lockscript"
+version = "0.1.0"
+dependencies = [
+ "ckb-std",
+ "perun-common",
+]
+
+[[package]]
+name = "perun-channel-typescript"
+version = "0.1.0"
+dependencies = [
+ "ckb-std",
+ "perun-common",
+]
+
+[[package]]
+name = "perun-common"
+version = "0.1.0"
+dependencies = [
+ "blake2b-rs",
+ "buddy-alloc",
+ "ckb-occupied-capacity",
+ "ckb-standalone-types",
+ "ckb-std",
+ "ckb-types",
+ "k256",
+ "molecule",
+ "rustc-std-workspace-alloc",
+ "rustc-std-workspace-core",
+]
+
+[[package]]
+name = "perun-funds-lockscript"
+version = "0.1.0"
+dependencies = [
+ "ckb-std",
+ "perun-common",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core 0.5.1",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rfc6979"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
+dependencies = [
+ "crypto-bigint",
+ "hmac",
+ "zeroize",
+]
+
+[[package]]
+name = "rustc-std-workspace-alloc"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff66d57013a5686e1917ed6a025d54dd591fcda71a41fe07edf4d16726aefa86"
+
+[[package]]
+name = "rustc-std-workspace-core"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1956f5517128a2b6f23ab2dadf1a976f4f5b27962e7724c2bf3d45e539ec098c"
+
+[[package]]
+name = "sample-udt"
+version = "0.1.0"
+dependencies = [
+ "ckb-std",
+ "perun-common",
+]
+
+[[package]]
+name = "sec1"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.14",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha3"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c"
+dependencies = [
+ "digest",
+ "keccak",
+]
+
+[[package]]
+name = "signature"
+version = "1.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
+dependencies = [
+ "digest",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.14",
+]
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "zeroize"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
diff --git a/payment-channel-ckb/devnet/contracts/Cargo.toml b/payment-channel-ckb/devnet/contracts/Cargo.toml
new file mode 100644
index 0000000..bd68a6e
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/Cargo.toml
@@ -0,0 +1,15 @@
+[workspace]
+members = [ "contracts/perun-channel-lockscript"
+ , "contracts/perun-channel-typescript"
+ , "contracts/perun-funds-lockscript"
+ , "contracts/perun-common"
+ , "contracts/sample-udt"
+ ]
+exclude = ["tests"]
+
+[profile.release]
+overflow-checks = true
+opt-level = 's'
+lto = false
+codegen-units = 1
+panic = 'abort'
diff --git a/payment-channel-ckb/devnet/contracts/README.md b/payment-channel-ckb/devnet/contracts/README.md
new file mode 100644
index 0000000..0f60dfb
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/README.md
@@ -0,0 +1,45 @@
+
+
+
+
+Perun CKB Contracts
+
+
+
+
+
+
+# [Perun](https://perun.network/) CKB contracts
+
+This repository contains the scripts used to realize Perun channels on CKB.
+There are three scripts available:
+
+## perun-channel-lockscript
+This script is used to handle access-rights to the live Perun channel cell.
+It ensures that only participants of the Perun channel in question are able to
+consume the live channel cell.
+
+## perun-channel-typescript
+This script is used to handle a Perun channel's state progression on-chain.
+Basically a NFT script with extra functionality.
+
+## perun-funds-lockscript
+This script handle access rights to all funds belonging to a Perun channel.
+It ensures that only channel participants are able to consume said funds.
+
+Build contracts:
+
+``` sh
+capsule build
+```
+
+Run tests:
+
+``` sh
+capsule test
+```
+
+## perun-common
+Additionally to the available contracts we extracted common functionality into
+its own `perun-common` crate which gives some additional helpers and
+convenience functions when interacting with types used in Perun contracts.
diff --git a/payment-channel-ckb/devnet/contracts/build/.gitkeep b/payment-channel-ckb/devnet/contracts/build/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/payment-channel-ckb/devnet/contracts/capsule.toml b/payment-channel-ckb/devnet/contracts/capsule.toml
new file mode 100644
index 0000000..a6549a9
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/capsule.toml
@@ -0,0 +1,27 @@
+# [rust]
+# # path of rust contracts workspace directory,
+# # a `Cargo.toml` file is expected under the directory.
+# workspace_dir = "."
+# toolchain = "nightly-2022-08-01"
+# docker_image = "thewawar/ckb-capsule:2022-08-01"
+
+# capsule version
+version = "0.9.2"
+# path of deployment config file
+deployment = "deployment/dev/deployment.toml"
+
+[[contracts]]
+name = "perun-channel-lockscript"
+template_type = "Rust"
+
+[[contracts]]
+name = "perun-channel-typescript"
+template_type = "Rust"
+
+[[contracts]]
+name = "perun-funds-lockscript"
+template_type = "Rust"
+
+[[contracts]]
+name = "sample-udt"
+template_type = "Rust"
diff --git a/payment-channel-ckb/devnet/contracts/contracts/.gitkeep b/payment-channel-ckb/devnet/contracts/contracts/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-channel-lockscript/Cargo.toml b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-lockscript/Cargo.toml
new file mode 100644
index 0000000..049cf5b
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-lockscript/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "perun-channel-lockscript"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+ckb-std = "0.10.0"
+perun-common = { path = "../perun-common", default-features = false, features = ["contract"] }
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-channel-lockscript/src/entry.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-lockscript/src/entry.rs
new file mode 100644
index 0000000..d8ea048
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-lockscript/src/entry.rs
@@ -0,0 +1,72 @@
+// Import from `core` instead of from `std` since we are in no-std mode
+use core::result::Result;
+
+// Import CKB syscalls and structures
+// https://docs.rs/ckb-std/
+use ckb_std::{
+ ckb_constants::Source,
+ ckb_types::{bytes::Bytes, prelude::*},
+ high_level::{load_cell_lock_hash, load_cell_type, load_script},
+ syscalls::SysError,
+};
+use perun_common::{error::Error, perun_types::ChannelConstants};
+
+// The perun-channel-lockscript (pcls) is used to lock access to interacting with a channel and is attached as lock script
+// to the channel-cell (the cell which uses the perun-channel-type-script (pcts) as its type script).
+// A channel defines two participants, each of which has their own unlock_script_hash (also defined in the ChannelConstants.params.{party_a,party_b}).
+// The pcls allows a transaction to interact with the channel, if at least one input cell is present with:
+// - cell's lock script hash == unlock_script_hash of party_a or
+// - cell's lock script hash == unlock_script_hash of party_b
+// We recommend using the secp256k1_blake160_sighash_all script as unlock script and corresponding payment args for the participants.
+//
+// Note: This means, that each participant needs to use a secp256k1_blake160_sighash_all as input to interact with the channel.
+// This should not be a substantial restriction, since a payment input will likely be used anyway (e.g. for funding or fees).
+
+pub fn main() -> Result<(), Error> {
+ let script = load_script()?;
+ let args: Bytes = script.args().unpack();
+ // return an error if args is invalid
+ if !args.is_empty() {
+ return Err(Error::PCLSWithArgs);
+ }
+
+ // locate the ChannelConstants in the type script of the input cell.
+ let type_script = load_cell_type(0, Source::GroupInput)?.expect("type script not found");
+ let type_script_args: Bytes = type_script.args().unpack();
+
+ let constants = ChannelConstants::from_slice(&type_script_args)
+ .expect("unable to parse args as channel parameters");
+
+ let is_participant = verify_is_participant(
+ &constants.params().party_a().unlock_script_hash().unpack(),
+ &constants.params().party_b().unlock_script_hash().unpack(),
+ )?;
+
+ if !is_participant {
+ return Err(Error::NotParticipant);
+ }
+
+ return Ok(());
+}
+
+/// check_is_participant checks if the current transaction is executed by a channel participant.
+/// It does so by looking for an input cell with the same lock script hash as the unlock_script_hash
+pub fn verify_is_participant(
+ unlock_script_hash_a: &[u8; 32],
+ unlock_script_hash_b: &[u8; 32],
+) -> Result {
+ for i in 0.. {
+ // Loop over all input cells.
+ let cell_lock_script_hash = match load_cell_lock_hash(i, Source::Input) {
+ Ok(lock_hash) => lock_hash,
+ Err(SysError::IndexOutOfBound) => return Ok(false),
+ Err(err) => return Err(err.into()),
+ };
+ if cell_lock_script_hash[..] == unlock_script_hash_a[..]
+ || cell_lock_script_hash[..] == unlock_script_hash_b[..]
+ {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-channel-lockscript/src/main.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-lockscript/src/main.rs
new file mode 100644
index 0000000..9306fc4
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-lockscript/src/main.rs
@@ -0,0 +1,32 @@
+//! Generated by capsule
+//!
+//! `main.rs` is used to define rust lang items and modules.
+//! See `entry.rs` for the `main` function.
+//! See `error.rs` for the `Error` type.
+
+#![no_std]
+#![no_main]
+#![feature(asm_sym)]
+#![feature(lang_items)]
+#![feature(alloc_error_handler)]
+#![feature(panic_info_message)]
+
+// define modules
+mod entry;
+
+use ckb_std::default_alloc;
+use core::arch::asm;
+
+ckb_std::entry!(program_entry);
+default_alloc!();
+
+/// program entry
+///
+/// Both `argc` and `argv` can be omitted.
+fn program_entry(_argc: u64, _argv: *const *const u8) -> i8 {
+ // Call main function and return error code
+ match entry::main() {
+ Ok(_) => 0,
+ Err(err) => err as i8,
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-channel-typescript/Cargo.toml b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-typescript/Cargo.toml
new file mode 100644
index 0000000..85ccd63
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-typescript/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "perun-channel-typescript"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+ckb-std = "0.10.0"
+perun-common = { path = "../perun-common", default-features = false, features = ["contract"] }
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-channel-typescript/src/entry.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-typescript/src/entry.rs
new file mode 100644
index 0000000..e581e4d
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-typescript/src/entry.rs
@@ -0,0 +1,904 @@
+// Import from `core` instead of from `std` since we are in no-std mode
+use core::result::Result;
+// Import heap related library from `alloc`
+// https://doc.rust-lang.org/alloc/index.html
+use alloc::{self, vec};
+
+// Import CKB syscalls and structures
+// https://docs.rs/ckb-std/
+use ckb_std::{
+ ckb_constants::Source,
+ ckb_types::{
+ bytes::Bytes,
+ packed::{Byte32, Script},
+ prelude::*,
+ },
+ debug,
+ high_level::{
+ load_cell_capacity, load_cell_data, load_cell_lock, load_cell_lock_hash, load_header,
+ load_script, load_script_hash, load_transaction, load_witness_args,
+ },
+ syscalls::{self, SysError},
+};
+use perun_common::{
+ error::Error,
+ helpers::blake2b256,
+ perun_types::{
+ Balances, ChannelConstants, ChannelParameters, ChannelState, ChannelStatus, ChannelToken,
+ ChannelWitness, ChannelWitnessUnion, SEC1EncodedPubKey,
+ },
+ sig::verify_signature,
+};
+
+const SUDT_MIN_LEN: usize = 16;
+
+/// ChannelAction describes what kind of interaction with the channel is currently happening.
+///
+/// If there is an old ChannelStatus, it is the status of the channel before the interaction.
+/// The old ChannelStatus lives in the cell data of the pcts input cell.
+/// It is stored in the parallel outputs_data array of the transaction that produced the consumed
+/// channel output cell.
+///
+/// If there is a new ChannelStatus, it is the status of the channel after the interaction.
+/// The new ChannelStatus lives in the cell data of the pcts output cell. It is stored in the
+/// parallel outputs_data array of the consuming transaction
+pub enum ChannelAction {
+ /// Progress indicates that a channel is being progressed. This means that a channel cell is consumed
+ /// in the inputs and the same channel with updated state is progressed in the outputs.
+ /// The possible redeemers associated with the Progress action are Fund and Dispute.
+ Progress {
+ old_status: ChannelStatus,
+ new_status: ChannelStatus,
+ }, // one PCTS input, one PCTS output
+ /// Start indicates that a channel is being started. This means that a **new channel** lives in the
+ /// output cells of this transaction. No channel cell is consumes as an input.
+ /// As Start does not consume a channel cell, there is no Witness associated with the Start action.
+ Start { new_status: ChannelStatus }, // no PCTS input, one PCTS output
+ /// Close indicates that a channel is being closed. This means that a channel's cell is consumed without being
+ /// recreated in the outputs with updated state. The possible redeemers associated with the Close action are
+ /// Close, Abort and ForceClose.
+ /// The channel type script assures that all funds are payed out to the correct parties upon closing.
+ Close { old_status: ChannelStatus }, // one PCTS input , no PCTS output
+}
+
+pub fn main() -> Result<(), Error> {
+ let script = load_script()?;
+ let args: Bytes = script.args().unpack();
+
+ // return an error if args is empty
+ if args.is_empty() {
+ return Err(Error::NoArgs);
+ }
+
+ // We verify that there is at most one channel in the GroupInputs and GroupOutputs respectively.
+ verify_max_one_channel()?;
+ debug!("verify_max_one_channel passed");
+
+ // The channel constants do not change during the lifetime of a channel. They are located in the
+ // args field of the pcts.
+ let channel_constants =
+ ChannelConstants::from_slice(&args).expect("unable to parse args as ChannelConstants");
+ debug!("parsing channel constants passed");
+
+ // Verify that the channel parameters are compatible with the currently supported
+ // features of perun channels.
+ verify_channel_params_compatibility(&channel_constants.params())?;
+ debug!("verify_channel_params_compatibility passed");
+
+ // Next, we determine whether the transaction starts, progresses or closes the channel and fetch
+ // the respective old and/or new channel status.
+ let channel_action = get_channel_action()?;
+ debug!("get_channel_action passed");
+
+ match channel_action {
+ ChannelAction::Start { new_status } => check_valid_start(&new_status, &channel_constants),
+ ChannelAction::Progress {
+ old_status,
+ new_status,
+ } => {
+ let channel_witness = load_witness()?;
+ debug!("load_witness passed");
+ check_valid_progress(
+ &old_status,
+ &new_status,
+ &channel_witness,
+ &channel_constants,
+ )
+ }
+ ChannelAction::Close { old_status } => {
+ let channel_witness = load_witness()?;
+ debug!("load_witness passed");
+ check_valid_close(&old_status, &channel_witness, &channel_constants)
+ }
+ }
+}
+
+pub fn check_valid_start(
+ new_status: &ChannelStatus,
+ channel_constants: &ChannelConstants,
+) -> Result<(), Error> {
+ const FUNDER_INDEX: usize = 0;
+
+ debug!("check_valid_start");
+
+ // Upon start of a channel, the channel constants are stored in the args field of the pcts output.
+ // We uniquely identify a channel through the combination of the channel id (hash of ChannelParameters,
+ // which is part of the ChannelConstants) and the "thread token".
+ // The thread token contains an OutPoint and the channel type script verifies, that that outpoint is
+ // consumed in the inputs of the transaction that starts the channel.
+ // This means: Once a (pcts-hash, channel-id, thread-token) tuple appears once on chain and is recognized
+ // as the on-chain representation of this channel by all peers, no other "copy" or "fake" of that channel
+ // can be created on chain, as an OutPoint can only be consumed once.
+
+ // here, we verify that the OutPoint in the thread token is actually consumed.
+ verify_thread_token_integrity(&channel_constants.thread_token())?;
+ debug!("verify_thread_token_integrity passed");
+
+ // We verify that the channel id is the hash of the channel parameters.
+ verify_channel_id_integrity(
+ &new_status.state().channel_id(),
+ &channel_constants.params(),
+ )?;
+ debug!("verify_channel_id_integrity passed");
+
+ // We verify that the pcts is guarded by the pcls script specified in the channel constants
+ verify_valid_lock_script(channel_constants)?;
+ debug!("verify_valid_lock_script passed");
+
+ // We verify that the channel participants have different payment addresses
+ // For this purpose we consider a payment address to be the script hash of the lock script used for payments to that party
+ verify_different_payment_addresses(channel_constants)?;
+ debug!("verify_different_payment_addresses passed");
+
+ // We verify that there are no funds locked by the pfls hash of this channel in the inputs of the transaction.
+ // This check is not strictly necessary for the current implementation of the pfls, but it is good practice to
+ // verify this anyway, as there is no reason to include funds locked for any channel in the input of a transaction
+ // that creates a new channel besides trying some kind of attack.
+ verify_no_funds_in_inputs(channel_constants)?;
+ debug!("verify_no_funds_in_inputs passed");
+
+ // We verify that the state the channel starts with is valid according to the utxo-adaption of the perun protocol.
+ // For example, the channel must not be final and the version number must be 0.
+ verify_state_valid_as_start(
+ &new_status.state(),
+ channel_constants.pfls_min_capacity().unpack(),
+ )?;
+ debug!("verify_state_valid_as_start passed");
+
+ // Here we verify that the first party completes its funding and that itsfunds are actually locked to the pfls with correct args.
+ verify_funding_in_outputs(
+ FUNDER_INDEX,
+ &new_status.state().balances(),
+ channel_constants,
+ )?;
+ debug!("verify_funding_in_outputs passed");
+
+ // We check that the funded bit in the channel status is set to true, exactly if the funding is complete.
+ verify_funded_status(new_status, true)?;
+ debug!("verify_funded_status passed");
+
+ // We verify that the channel status is not disputed upon start.
+ verify_status_not_disputed(new_status)?;
+ debug!("verify_status_not_disputed passed");
+ Ok(())
+}
+
+pub fn check_valid_progress(
+ old_status: &ChannelStatus,
+ new_status: &ChannelStatus,
+ witness: &ChannelWitness,
+ channel_constants: &ChannelConstants,
+) -> Result<(), Error> {
+ debug!("check_valid_progress");
+
+ // At this point we know that the transaction progresses the channel. There are two different
+ // kinds of channel progression: Funding and Dispute. Which kind of progression is performed
+ // depends on the witness.
+
+ // Some checks are common to both kinds of progression and are performed here.
+ // We check that both the old and the new state have the same channel id.
+ verify_equal_channel_id(&old_status.state(), &new_status.state())?;
+ debug!("verify_equal_channel_id passed");
+
+ // No kind of channel progression should pay out any funds locked by the pfls, so we just check
+ // that there are no funds locked by the pfls in the inputs of the transaction.
+ verify_no_funds_in_inputs(channel_constants)?;
+ debug!("verify_no_funds_in_inputs passed");
+ // Here we verify that the cell with the PCTS in the outputs is locked by the same lock script
+ // as the input channel cell.
+ verify_channel_continues_locked()?;
+ debug!("verify_channel_continues_locked passed");
+
+ match witness.to_enum() {
+ ChannelWitnessUnion::Fund(_) => {
+ const FUNDER_INDEX: usize = 1;
+ debug!("ChannelWitnessUnion::Fund");
+
+ // The funding array in a channel status reflects how much each party has funded up to that point.
+ // Funding must not alter the channel's state.
+ verify_equal_channel_state(&old_status.state(), &new_status.state())?;
+ debug!("verify_equal_channel_state passed");
+
+ // Funding an already funded status is invalid.
+ verify_status_not_funded(&old_status)?;
+ debug!("verify_status_not_funded passed");
+
+ verify_funding_in_outputs(
+ FUNDER_INDEX,
+ &old_status.state().balances(),
+ channel_constants,
+ )?;
+ debug!("verify_funding_in_outputs passed");
+
+ // Funding a disputed status is invalid. This should not be able to happen anyway, but we check
+ // it nontheless.
+ verify_status_not_disputed(new_status)?;
+ debug!("verify_status_not_disputed passed");
+
+ // We check that the funded bit in the channel status is set to true, iff the funding is complete.
+ verify_funded_status(&new_status, false)?;
+ debug!("verify_funded_status passed");
+ Ok(())
+ }
+ ChannelWitnessUnion::Dispute(d) => {
+ debug!("ChannelWitnessUnion::Dispute");
+
+ // An honest party will dispute a channel, e.g. if its peer does not respond and it wants to close
+ // the channel. For this, the honest party needs to provide the latest state (in the "new" channel status)
+ // as well as a valid signature by each party on that state (in the witness). After the expiration of the
+ // relative time lock (challenge duration), the honest party can forcibly close the channel.
+ // If a malicious party disputes with an old channel state, an honest party can dispute again with
+ // the latest state (with higher version number) and the corresponding signatures within the challenge
+ // duration.
+
+ // First, we verify the integrity of the channel state. For this, the following must hold:
+ // - channel id is equal
+ // - version number is increasing (see verify_increasing_version_number)
+ // - sum of balances is equal
+ // - old state is not final
+ verify_channel_state_progression(old_status, &new_status.state())?;
+ debug!("verify_channel_state_progression passed");
+
+ // One cannot dispute if funding is not complete.
+ verify_status_funded(old_status)?;
+ debug!("verify_status_funded passed");
+
+ // The disputed flag in the new status must be set. This indicates that the channel can be closed
+ // forcibly after the expiration of the challenge duration in a later transaction.
+ verify_status_disputed(new_status)?;
+ debug!("verify_status_disputed passed");
+
+ // We verify that the signatures of both parties are valid on the new channel state.
+ verify_valid_state_sigs(
+ &d.sig_a().unpack(),
+ &d.sig_b().unpack(),
+ &new_status.state(),
+ &channel_constants.params().party_a().pub_key(),
+ &channel_constants.params().party_b().pub_key(),
+ )?;
+ debug!("verify_valid_state_sigs passed");
+ Ok(())
+ }
+ // Close, ForceClose and Abort may not happen as channel progression (if there is a continuing channel output).
+ ChannelWitnessUnion::Close(_) => Err(Error::ChannelCloseWithChannelOutput),
+ ChannelWitnessUnion::ForceClose(_) => Err(Error::ChannelForceCloseWithChannelOutput),
+ ChannelWitnessUnion::Abort(_) => Err(Error::ChannelAbortWithChannelOutput),
+ }
+}
+
+pub fn check_valid_close(
+ old_status: &ChannelStatus,
+ channel_witness: &ChannelWitness,
+ channel_constants: &ChannelConstants,
+) -> Result<(), Error> {
+ debug!("check_valid_close");
+
+ // At this point we know that this transaction closes the channel. There are three different kinds of
+ // closing: Abort, ForceClose and Close. Which kind of closing is performed depends on the witness.
+ // Every channel closing transaction must pay out all funds the the channel participants. The amount
+ // to be payed to each party
+ let channel_capacity = load_cell_capacity(0, Source::GroupInput)?;
+ match channel_witness.to_enum() {
+ ChannelWitnessUnion::Abort(_) => {
+ const PARTY_B_INDEX: usize = 1;
+
+ debug!("ChannelWitnessUnion::Abort");
+
+ // An abort can be performed at any time by a channel participant on a channel for which funding
+ // is not yet complete. It allows the initial party to reclaim its funds if e.g. the other party
+ // refuses to fund the channel.
+ verify_status_not_funded(old_status)?;
+ debug!("verify_status_not_funded passed");
+
+ // We verify that every party is payed the amount of funds that it has locked to the channel so far.
+ // If abourt is called, Party A must have fully funded the channel and Party B can not have funded
+ // the channel because of our funding protocol.
+ verify_all_payed(
+ &old_status.state().balances().clear_index(PARTY_B_INDEX)?,
+ channel_capacity,
+ channel_constants,
+ true,
+ )?;
+ debug!("verify_all_payed passed");
+ Ok(())
+ }
+ ChannelWitnessUnion::ForceClose(_) => {
+ debug!("ChannelWitnessUnion::ForceClose");
+ // A force close can be performed after the channel was disputed and the challenge duration has
+ // expired. Upon force close, each party is payed according to the balance distribution in the
+ // latest state.
+ verify_status_funded(old_status)?;
+ debug!("verify_status_funded passed");
+ verify_time_lock_expired(channel_constants.params().challenge_duration().unpack())?;
+ debug!("verify_time_lock_expired passed");
+ verify_status_disputed(old_status)?;
+ debug!("verify_status_disputed passed");
+ verify_all_payed(
+ &old_status.state().balances(),
+ channel_capacity,
+ channel_constants,
+ false,
+ )?;
+ debug!("verify_all_payed passed");
+ Ok(())
+ }
+ ChannelWitnessUnion::Close(c) => {
+ debug!("check_valid_close: Close");
+
+ // A channel can be closed by either party at any time after funding is complete.
+ // For this the party needs to provide a final state (final bit set) and signatures
+ // by all peers on that state.
+ verify_equal_channel_id(&old_status.state(), &c.state())?;
+ debug!("check_valid_close: Channel id verified");
+ verify_status_funded(old_status)?;
+ debug!("check_valid_close: Status funded verified");
+ verify_state_finalized(&c.state())?;
+ debug!("check_valid_close: State finalized verified");
+ verify_valid_state_sigs(
+ &c.sig_a().unpack(),
+ &c.sig_b().unpack(),
+ &c.state(),
+ &channel_constants.params().party_a().pub_key(),
+ &channel_constants.params().party_b().pub_key(),
+ )?;
+ // We verify that each party is payed according to the balance distribution in the final state.
+ verify_all_payed(
+ &c.state().balances(),
+ channel_capacity,
+ channel_constants,
+ false,
+ )?;
+ debug!("verify_all_payed passed");
+ Ok(())
+ }
+ ChannelWitnessUnion::Fund(_) => Err(Error::ChannelFundWithoutChannelOutput),
+ ChannelWitnessUnion::Dispute(_) => Err(Error::ChannelDisputeWithoutChannelOutput),
+ }
+}
+
+pub fn load_witness() -> Result {
+ debug!("load_witness");
+
+ let witness_args = load_witness_args(0, Source::GroupInput)?;
+ let witness_bytes: Bytes = witness_args
+ .input_type()
+ .to_opt()
+ .ok_or(Error::NoWitness)?
+ .unpack();
+ let channel_witness = ChannelWitness::from_slice(&witness_bytes)?;
+ Ok(channel_witness)
+}
+
+pub fn verify_increasing_version_number(
+ old_status: &ChannelStatus,
+ new_state: &ChannelState,
+) -> Result<(), Error> {
+ debug!(
+ "verify_increasing_version_number old_state disputed: {}",
+ old_status.disputed().to_bool()
+ );
+ debug!(
+ "verify_increasing_version_number old: {}, new: {}",
+ old_status.state().version().unpack(),
+ new_state.version().unpack()
+ );
+ // Allow registering initial state
+ if !old_status.disputed().to_bool()
+ && old_status.state().version().unpack() == 0
+ && new_state.version().unpack() == 0
+ {
+ return Ok(());
+ }
+ if old_status.state().version().unpack() < new_state.version().unpack() {
+ return Ok(());
+ }
+ Err(Error::VersionNumberNotIncreasing)
+}
+
+pub fn verify_valid_state_sigs(
+ sig_a: &Bytes,
+ sig_b: &Bytes,
+ state: &ChannelState,
+ pub_key_a: &SEC1EncodedPubKey,
+ pub_key_b: &SEC1EncodedPubKey,
+) -> Result<(), Error> {
+ let msg_hash = blake2b256(state.as_slice());
+ verify_signature(&msg_hash, sig_a, pub_key_a.as_slice())?;
+ debug!("verify_valid_state_sigs: Signature A verified");
+ verify_signature(&msg_hash, sig_b, pub_key_b.as_slice())?;
+ debug!("verify_valid_state_sigs: Signature B verified");
+ Ok(())
+}
+
+pub fn verify_state_not_finalized(state: &ChannelState) -> Result<(), Error> {
+ if state.is_final().to_bool() {
+ return Err(Error::StateIsFinal);
+ }
+ Ok(())
+}
+
+pub fn verify_status_funded(status: &ChannelStatus) -> Result<(), Error> {
+ if !status.funded().to_bool() {
+ return Err(Error::ChannelNotFunded);
+ }
+ Ok(())
+}
+
+pub fn verify_equal_sum_of_balances(
+ old_balances: &Balances,
+ new_balances: &Balances,
+) -> Result<(), Error> {
+ if !old_balances.equal_in_sum(new_balances)? {
+ return Err(Error::SumOfBalancesNotEqual);
+ }
+ Ok(())
+}
+
+pub fn verify_channel_continues_locked() -> Result<(), Error> {
+ let input_lock_script = load_cell_lock(0, Source::GroupInput)?;
+ let output_lock_script = load_cell_lock(0, Source::GroupOutput)?;
+ if input_lock_script.as_slice()[..] != output_lock_script.as_slice()[..] {
+ return Err(Error::ChannelDoesNotContinue);
+ }
+ Ok(())
+}
+
+pub fn verify_no_funds_in_inputs(channel_constants: &ChannelConstants) -> Result<(), Error> {
+ let num_inputs = load_transaction()?.raw().inputs().len();
+ for i in 0..num_inputs {
+ let cell_lock_hash = load_cell_lock(i, Source::Input)?;
+ if cell_lock_hash.code_hash().unpack()[..]
+ == channel_constants.pfls_code_hash().unpack()[..]
+ {
+ return Err(Error::FundsInInputs);
+ }
+ }
+ Ok(())
+}
+
+pub fn verify_equal_channel_state(
+ old_state: &ChannelState,
+ new_state: &ChannelState,
+) -> Result<(), Error> {
+ if old_state.as_slice()[..] == new_state.as_slice()[..] {
+ return Ok(());
+ }
+ Err(Error::ChannelStateNotEqual)
+}
+
+pub fn verify_funding_in_outputs(
+ idx: usize,
+ initial_balance: &Balances,
+ channel_constants: &ChannelConstants,
+) -> Result<(), Error> {
+ let ckbytes_locked_for_sudts = initial_balance.sudts().get_locked_ckbytes();
+ let to_fund = initial_balance.ckbytes().get(idx)? + ckbytes_locked_for_sudts;
+ if to_fund == 0 {
+ return Ok(());
+ }
+
+ let mut udt_sum =
+ vec![0u128, initial_balance.sudts().len().try_into().unwrap()].into_boxed_slice();
+
+ let expected_pcts_script_hash = load_script_hash()?;
+ let outputs = load_transaction()?.raw().outputs();
+ let expected_pfls_code_hash = channel_constants.pfls_code_hash().unpack();
+ let expected_pfls_hash_type = channel_constants.pfls_hash_type();
+ let mut capacity_sum: u64 = 0;
+ for (i, output) in outputs.into_iter().enumerate() {
+ if output.lock().code_hash().unpack()[..] == expected_pfls_code_hash[..]
+ && output.lock().hash_type().eq(&expected_pfls_hash_type)
+ {
+ let output_lock_args: Bytes = output.lock().args().unpack();
+ let script_hash_in_pfls_args = Byte32::from_slice(&output_lock_args)?.unpack();
+ if script_hash_in_pfls_args[..] == expected_pcts_script_hash[..] {
+ capacity_sum += output.capacity().unpack();
+ } else {
+ return Err(Error::InvalidPFLSInOutputs);
+ }
+ if output.type_().is_some() {
+ let (sudt_idx, amount) = get_sudt_amout(
+ initial_balance,
+ i,
+ &output.type_().to_opt().expect("checked above"),
+ )?;
+ udt_sum[sudt_idx] += amount;
+ }
+ }
+ }
+ if capacity_sum != to_fund {
+ debug!(
+ "verify_funding_in_outputs: capacity_sum: {}, to_fund: {}",
+ capacity_sum, to_fund
+ );
+ return Err(Error::OwnFundingNotInOutputs);
+ }
+ if !initial_balance.sudts().fully_represented(idx, &udt_sum)? {
+ return Err(Error::OwnFundingNotInOutputs);
+ }
+
+ Ok(())
+}
+
+pub fn verify_funded_status(status: &ChannelStatus, is_start: bool) -> Result<(), Error> {
+ if !is_start {
+ if !status.funded().to_bool() {
+ return Err(Error::FundedBitStatusNotCorrect);
+ }
+ return Ok(());
+ }
+ if status.state().balances().ckbytes().get(1)? != 0 {
+ if status.funded().to_bool() {
+ return Err(Error::FundedBitStatusNotCorrect);
+ }
+ return Ok(());
+ }
+ if status.state().balances().sudts().len() != 0 {
+ if status.funded().to_bool() {
+ return Err(Error::FundedBitStatusNotCorrect);
+ }
+ return Ok(());
+ }
+ if !status.funded().to_bool() {
+ return Err(Error::FundedBitStatusNotCorrect);
+ }
+ Ok(())
+}
+
+pub fn verify_status_not_funded(status: &ChannelStatus) -> Result<(), Error> {
+ if status.funded().to_bool() {
+ return Err(Error::StateIsFunded);
+ }
+ Ok(())
+}
+
+pub fn verify_channel_params_compatibility(params: &ChannelParameters) -> Result<(), Error> {
+ if params.app().to_opt().is_some() {
+ return Err(Error::AppChannelsNotSupported);
+ }
+ if !params.is_ledger_channel().to_bool() {
+ return Err(Error::NonLedgerChannelsNotSupported);
+ }
+ if params.is_virtual_channel().to_bool() {
+ return Err(Error::VirtualChannelsNotSupported);
+ }
+ Ok(())
+}
+
+pub fn verify_equal_channel_id(
+ old_state: &ChannelState,
+ new_state: &ChannelState,
+) -> Result<(), Error> {
+ if old_state.channel_id().unpack()[..] != new_state.channel_id().unpack()[..] {
+ return Err(Error::ChannelIdMismatch);
+ }
+ Ok(())
+}
+
+pub fn verify_channel_state_progression(
+ old_status: &ChannelStatus,
+ new_state: &ChannelState,
+) -> Result<(), Error> {
+ verify_equal_channel_id(&old_status.state(), new_state)?;
+ verify_increasing_version_number(old_status, new_state)?;
+ verify_equal_sum_of_balances(&old_status.state().balances(), &new_state.balances())?;
+ verify_state_not_finalized(&old_status.state())?;
+ Ok(())
+}
+
+pub fn verify_thread_token_integrity(thread_token: &ChannelToken) -> Result<(), Error> {
+ let inputs = load_transaction()?.raw().inputs();
+ for input in inputs.into_iter() {
+ if input.previous_output().as_slice()[..] == thread_token.out_point().as_slice()[..] {
+ return Ok(());
+ }
+ }
+ Err(Error::InvalidThreadToken)
+}
+
+pub fn verify_channel_id_integrity(
+ channel_id: &Byte32,
+ params: &ChannelParameters,
+) -> Result<(), Error> {
+ let digest = blake2b256(params.as_slice());
+ if digest[..] != channel_id.unpack()[..] {
+ return Err(Error::InvalidChannelId);
+ }
+ Ok(())
+}
+
+pub fn verify_state_valid_as_start(
+ state: &ChannelState,
+ pfls_min_capacity: u64,
+) -> Result<(), Error> {
+ if state.version().unpack() != 0 {
+ return Err(Error::StartWithNonZeroVersion);
+ }
+ if state.is_final().to_bool() {
+ return Err(Error::StartWithFinalizedState);
+ }
+
+ // We verify that each participant's initial balance is at least the minimum capacity of a PFLS (or zero),
+ // to ensure that funding is possible for the initial balance distribution.
+ let balance_a = state.balances().ckbytes().get(0)?;
+ let balance_b = state.balances().ckbytes().get(1)?;
+ if balance_a < pfls_min_capacity && balance_a != 0 {
+ return Err(Error::BalanceBelowPFLSMinCapacity);
+ }
+ if balance_b < pfls_min_capacity && balance_b != 0 {
+ return Err(Error::BalanceBelowPFLSMinCapacity);
+ }
+ Ok(())
+}
+
+pub fn verify_valid_lock_script(channel_constants: &ChannelConstants) -> Result<(), Error> {
+ let lock_script = load_cell_lock(0, Source::GroupOutput)?;
+ if lock_script.code_hash().unpack()[..] != channel_constants.pcls_code_hash().unpack()[..] {
+ return Err(Error::InvalidPCLSCodeHash);
+ }
+ if !lock_script
+ .hash_type()
+ .eq(&channel_constants.pcls_hash_type())
+ {
+ return Err(Error::InvalidPCLSHashType);
+ }
+
+ if !lock_script.args().is_empty() {
+ return Err(Error::PCLSWithArgs);
+ }
+ Ok(())
+}
+
+pub fn verify_status_not_disputed(status: &ChannelStatus) -> Result<(), Error> {
+ if status.disputed().to_bool() {
+ return Err(Error::StatusDisputed);
+ }
+ Ok(())
+}
+
+pub fn verify_status_disputed(status: &ChannelStatus) -> Result<(), Error> {
+ if !status.disputed().to_bool() {
+ return Err(Error::StatusNotDisputed);
+ }
+ Ok(())
+}
+
+pub fn verify_all_payed(
+ final_balance: &Balances,
+ channel_capacity: u64,
+ channel_constants: &ChannelConstants,
+ is_abort: bool,
+) -> Result<(), Error> {
+ debug!("verify_all_payed");
+ debug!("is_abort: {}", is_abort);
+ let minimum_payment_a = channel_constants
+ .params()
+ .party_a()
+ .payment_min_capacity()
+ .unpack();
+ let minimum_payment_b: u64 = channel_constants
+ .params()
+ .party_b()
+ .payment_min_capacity()
+ .unpack();
+
+ let reimburse_a = final_balance.sudts().get_locked_ckbytes();
+ let mut reimburse_b = 0u64;
+ if !is_abort {
+ reimburse_b = reimburse_a;
+ }
+
+ let ckbytes_balance_a = final_balance.ckbytes().get(0)? + channel_capacity + reimburse_a;
+ let payment_script_hash_a = channel_constants
+ .params()
+ .party_a()
+ .payment_script_hash()
+ .unpack();
+
+ let ckbytes_balance_b = final_balance.ckbytes().get(1)? + reimburse_b;
+ let payment_script_hash_b = channel_constants
+ .params()
+ .party_b()
+ .payment_script_hash()
+ .unpack();
+
+ debug!("ckbytes_balance_a: {}", ckbytes_balance_a);
+ debug!("ckbytes_balance_b: {}", ckbytes_balance_b);
+
+ let mut ckbytes_outputs_a = 0;
+ let mut ckbytes_outputs_b = 0;
+
+ let mut udt_outputs_a =
+ vec![0u128; final_balance.sudts().len().try_into().unwrap()].into_boxed_slice();
+ let mut udt_outputs_b =
+ vec![0u128; final_balance.sudts().len().try_into().unwrap()].into_boxed_slice();
+
+ let outputs = load_transaction()?.raw().outputs();
+
+ // Note: Currently it is allowed to pay out a party's CKBytes in the capacity field of an
+ // output, that is used as SUDT payment.
+ for (i, output) in outputs.into_iter().enumerate() {
+ let output_lock_script_hash = load_cell_lock_hash(i, Source::Output)?;
+
+ if output_lock_script_hash[..] == payment_script_hash_a[..] {
+ if output.type_().is_some() {
+ let (sudt_idx, amount) = get_sudt_amout(
+ final_balance,
+ i,
+ &output.type_().to_opt().expect("checked above"),
+ )?;
+ udt_outputs_a[sudt_idx] += amount;
+ }
+ ckbytes_outputs_a += output.capacity().unpack();
+ }
+ if output_lock_script_hash[..] == payment_script_hash_b[..] {
+ if output.type_().is_some() {
+ let (sudt_idx, amount) = get_sudt_amout(
+ final_balance,
+ i,
+ &output.type_().to_opt().expect("checked above"),
+ )?;
+ udt_outputs_b[sudt_idx] += amount;
+ }
+ ckbytes_outputs_b += output.capacity().unpack();
+ }
+ }
+ debug!("ckbytes_outputs_a: {}", ckbytes_outputs_a);
+ debug!("ckbytes_outputs_b: {}", ckbytes_outputs_b);
+
+ // Parties with balances below the minimum capacity of the payment script
+ // are not required to be payed.
+ if (ckbytes_balance_a > ckbytes_outputs_a && ckbytes_balance_a >= minimum_payment_a)
+ || (ckbytes_balance_b > ckbytes_outputs_b && ckbytes_balance_b >= minimum_payment_b)
+ {
+ return Err(Error::NotAllPayed);
+ }
+
+ debug!("udt_outputs_a: {:?}", udt_outputs_a);
+ debug!("udt_outputs_b: {:?}", udt_outputs_b);
+
+ if !final_balance.sudts().fully_represented(0, &udt_outputs_a)? {
+ return Err(Error::NotAllPayed);
+ }
+ if !final_balance.sudts().fully_represented(1, &udt_outputs_b)? {
+ return Err(Error::NotAllPayed);
+ }
+ Ok(())
+}
+
+// TODO: We might want to verify that the capacity of the sudt output is at least the max_capacity of the SUDT asset.
+// Not doing so may result in the ability to steal funds up to the
+// (max_capacity of the SUDT asset - actual occupied capacity of the SUDT type script), if the SUDT asset's max_capacity
+// is smaller than the payment_min_capacity of the participant. We do not do this for now, because it is an extreme edge case
+// and the max_capacity of an SUDT should never be set that low.
+pub fn get_sudt_amout(
+ balances: &Balances,
+ output_idx: usize,
+ type_script: &Script,
+) -> Result<(usize, u128), Error> {
+ let mut buf = [0u8; SUDT_MIN_LEN];
+
+ let (sudt_idx, _) = balances.sudts().get_distribution(type_script)?;
+ let sudt_data = load_cell_data(output_idx, Source::Output)?;
+ if sudt_data.len() < SUDT_MIN_LEN {
+ return Err(Error::InvalidSUDTDataLength);
+ }
+ buf.copy_from_slice(&sudt_data[..SUDT_MIN_LEN]);
+ return Ok((sudt_idx, u128::from_le_bytes(buf)));
+}
+
+pub fn verify_time_lock_expired(time_lock: u64) -> Result<(), Error> {
+ let old_header = load_header(0, Source::GroupInput)?;
+ let old_timestamp = old_header.raw().timestamp().unpack();
+ let current_time = find_closest_current_time();
+ if old_timestamp + time_lock > current_time {
+ return Err(Error::TimeLockNotExpired);
+ }
+ Ok(())
+}
+
+pub fn find_closest_current_time() -> u64 {
+ let mut latest_time = 0;
+ for i in 0.. {
+ match load_header(i, Source::HeaderDep) {
+ Ok(header) => {
+ let timestamp = header.raw().timestamp().unpack();
+ if timestamp > latest_time {
+ latest_time = timestamp;
+ }
+ }
+ Err(_) => break,
+ }
+ }
+ latest_time
+}
+
+pub fn verify_state_finalized(state: &ChannelState) -> Result<(), Error> {
+ if !state.is_final().to_bool() {
+ return Err(Error::StateNotFinal);
+ }
+ Ok(())
+}
+
+pub fn get_channel_action() -> Result {
+ let input_status_opt = load_cell_data(0, Source::GroupInput)
+ .ok()
+ .map(|data| ChannelStatus::from_slice(data.as_slice()))
+ .map_or(Ok(None), |v| v.map(Some))?;
+
+ let output_status_opt = load_cell_data(0, Source::GroupOutput)
+ .ok()
+ .map(|data| ChannelStatus::from_slice(data.as_slice()))
+ .map_or(Ok(None), |v| v.map(Some))?;
+
+ match (input_status_opt, output_status_opt) {
+ (Some(old_status), Some(new_status)) => Ok(ChannelAction::Progress {
+ old_status,
+ new_status,
+ }),
+ (Some(old_status), None) => Ok(ChannelAction::Close { old_status }),
+ (None, Some(new_status)) => Ok(ChannelAction::Start { new_status }),
+ (None, None) => Err(Error::UnableToLoadAnyChannelStatus),
+ }
+}
+
+/// verify_max_one_channel verifies that there is at most one channel in the group input and group output respectively.
+pub fn verify_max_one_channel() -> Result<(), Error> {
+ if count_cells(Source::GroupInput)? > 1 || count_cells(Source::GroupOutput)? > 1 {
+ return Err(Error::MoreThanOneChannel);
+ } else {
+ return Ok(());
+ }
+}
+
+pub fn count_cells(source: Source) -> Result {
+ let mut null_buf: [u8; 0] = [];
+ for i in 0.. {
+ match syscalls::load_cell(&mut null_buf, 0, i, source) {
+ Ok(_) => continue,
+ Err(SysError::LengthNotEnough(_)) => continue,
+ Err(SysError::IndexOutOfBound) => return Ok(i),
+ Err(err) => return Err(err.into()),
+ }
+ }
+ Ok(0)
+}
+
+pub fn verify_different_payment_addresses(
+ channel_constants: &ChannelConstants,
+) -> Result<(), Error> {
+ if channel_constants
+ .params()
+ .party_a()
+ .payment_script_hash()
+ .unpack()[..]
+ == channel_constants
+ .params()
+ .party_b()
+ .payment_script_hash()
+ .unpack()[..]
+ {
+ return Err(Error::SamePaymentAddress);
+ }
+ Ok(())
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-channel-typescript/src/main.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-typescript/src/main.rs
new file mode 100644
index 0000000..9306fc4
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-channel-typescript/src/main.rs
@@ -0,0 +1,32 @@
+//! Generated by capsule
+//!
+//! `main.rs` is used to define rust lang items and modules.
+//! See `entry.rs` for the `main` function.
+//! See `error.rs` for the `Error` type.
+
+#![no_std]
+#![no_main]
+#![feature(asm_sym)]
+#![feature(lang_items)]
+#![feature(alloc_error_handler)]
+#![feature(panic_info_message)]
+
+// define modules
+mod entry;
+
+use ckb_std::default_alloc;
+use core::arch::asm;
+
+ckb_std::entry!(program_entry);
+default_alloc!();
+
+/// program entry
+///
+/// Both `argc` and `argv` can be omitted.
+fn program_entry(_argc: u64, _argv: *const *const u8) -> i8 {
+ // Call main function and return error code
+ match entry::main() {
+ Ok(_) => 0,
+ Err(err) => err as i8,
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-common/Cargo.toml b/payment-channel-ckb/devnet/contracts/contracts/perun-common/Cargo.toml
new file mode 100644
index 0000000..fb7ec7b
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-common/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "perun-common"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+ckb-std = "0.10.0"
+blake2b-rs = "0.2.0"
+ckb-standalone-types = { version = "0.1.2", default-features = false, optional = true }
+ckb-types = { version = "=0.108.0", optional = true }
+k256 = { version = "0.11.6", default-features = false, features = ["ecdsa", "keccak256", "arithmetic"]}
+alloc = { version = "1.0.0", optional = true, package = "rustc-std-workspace-alloc" }
+core = { version = "1.0.0", optional = true, package = "rustc-std-workspace-core" }
+buddy-alloc = { version = "0.4.2", optional = true }
+ckb-occupied-capacity = { version = "0.108.0", optional = true }
+
+[dependencies.molecule]
+version = "0.7.3"
+default-features = false
+
+[features]
+default = ["contract"]
+testing = ["std", "ckb-types", "ckb-occupied-capacity"]
+std = []
+contract = ["ckb-standalone-types"]
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-common/blockchain.mol b/payment-channel-ckb/devnet/contracts/contracts/perun-common/blockchain.mol
new file mode 100644
index 0000000..091441c
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-common/blockchain.mol
@@ -0,0 +1,108 @@
+/* Basic Types */
+
+array Uint32 [byte; 4];
+array Uint64 [byte; 8];
+array Uint128 [byte; 16];
+array Byte32 [byte; 32];
+array Uint256 [byte; 32];
+
+vector Bytes ;
+option BytesOpt (Bytes);
+
+vector BytesVec ;
+vector Byte32Vec ;
+
+/* Types for Chain */
+
+option ScriptOpt (Script);
+
+array ProposalShortId [byte; 10];
+
+vector UncleBlockVec ;
+vector TransactionVec ;
+vector ProposalShortIdVec ;
+vector CellDepVec ;
+vector CellInputVec ;
+vector CellOutputVec ;
+
+table Script {
+ code_hash: Byte32,
+ hash_type: byte,
+ args: Bytes,
+}
+
+struct OutPoint {
+ tx_hash: Byte32,
+ index: Uint32,
+}
+
+struct CellInput {
+ since: Uint64,
+ previous_output: OutPoint,
+}
+
+table CellOutput {
+ capacity: Uint64,
+ lock: Script,
+ type_: ScriptOpt,
+}
+
+struct CellDep {
+ out_point: OutPoint,
+ dep_type: byte,
+}
+
+table RawTransaction {
+ version: Uint32,
+ cell_deps: CellDepVec,
+ header_deps: Byte32Vec,
+ inputs: CellInputVec,
+ outputs: CellOutputVec,
+ outputs_data: BytesVec,
+}
+
+table Transaction {
+ raw: RawTransaction,
+ witnesses: BytesVec,
+}
+
+struct RawHeader {
+ version: Uint32,
+ compact_target: Uint32,
+ timestamp: Uint64,
+ number: Uint64,
+ epoch: Uint64,
+ parent_hash: Byte32,
+ transactions_root: Byte32,
+ proposals_hash: Byte32,
+ uncles_hash: Byte32,
+ dao: Byte32,
+}
+
+struct Header {
+ raw: RawHeader,
+ nonce: Uint128,
+}
+
+table UncleBlock {
+ header: Header,
+ proposals: ProposalShortIdVec,
+}
+
+table Block {
+ header: Header,
+ uncles: UncleBlockVec,
+ transactions: TransactionVec,
+ proposals: ProposalShortIdVec,
+}
+
+table CellbaseWitness {
+ lock: Script,
+ message: Bytes,
+}
+
+table WitnessArgs {
+ lock: BytesOpt, // Lock args
+ input_type: BytesOpt, // Type args for input
+ output_type: BytesOpt, // Type args for output
+}
\ No newline at end of file
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-common/offchain_types.mol b/payment-channel-ckb/devnet/contracts/contracts/perun-common/offchain_types.mol
new file mode 100644
index 0000000..ba4758e
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-common/offchain_types.mol
@@ -0,0 +1,8 @@
+import blockchain;
+import types;
+
+table OffChainParticipant {
+ pub_key: SEC1EncodedPubKey,
+ payment_script: Script,
+ unlock_script: Script,
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/error.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/error.rs
new file mode 100644
index 0000000..c40a0e7
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/error.rs
@@ -0,0 +1,115 @@
+use core::fmt::Debug;
+
+use ckb_std::error::SysError;
+use k256::ecdsa::Error as SigError;
+use molecule::error::VerificationError;
+
+/// Error
+#[derive(Debug)]
+#[repr(i8)]
+pub enum Error {
+ // System Errors
+ IndexOutOfBound = 1,
+ ItemMissing,
+ LengthNotEnough,
+ Encoding,
+ // Verification Errors
+ TotalSizeNotMatch,
+ HeaderIsBroken,
+ UnknownItem,
+ OffsetsNotMatch,
+ FieldCountNotMatch,
+
+ // Signature Errors
+ SignatureVerificationError,
+
+ // Add customized errors here...
+ NoArgs,
+ NoWitness,
+ ChannelIdMismatch,
+ VersionNumberNotIncreasing,
+ StateIsFinal,
+ StateNotFinal,
+ ChannelNotFunded,
+ NotParticipant,
+ SumOfBalancesNotEqual,
+ OwnIndexNotFound,
+ ChannelDoesNotContinue,
+ MultipleMatchingOutputs,
+ FundsInInputs,
+ AppChannelsNotSupported,
+ NonLedgerChannelsNotSupported,
+ VirtualChannelsNotSupported,
+ ChannelStateNotEqual,
+ FundingChanged,
+ FundingNotInStatus,
+ OwnFundingNotInOutputs,
+ FundedBitStatusNotCorrect,
+ StateIsFunded,
+
+ ChannelFundWithoutChannelOutput,
+ ChannelDisputeWithoutChannelOutput,
+ ChannelCloseWithChannelOutput,
+ ChannelForceCloseWithChannelOutput,
+ ChannelAbortWithChannelOutput,
+
+ InvalidThreadToken,
+ InvalidChannelId,
+ StartWithNonZeroVersion,
+ StartWithFinalizedState,
+ InvalidPCLSCodeHash,
+ InvalidPCLSHashType,
+ PCLSWithArgs,
+ StatusDisputed,
+ StatusNotDisputed,
+ FundingNotZero,
+ NotAllPayed,
+ TimeLockNotExpired,
+ InvalidTimestamp,
+ UnableToLoadAnyChannelStatus,
+ InvalidSignature,
+ InvalidMessage,
+ InvalidPFLSInOutputs,
+ PCTSNotFound,
+ FoundDifferentChannel,
+ MoreThanOneChannel,
+ BalanceBelowPFLSMinCapacity,
+ SamePaymentAddress,
+ TypeScriptInPaymentOutput,
+ TypeScriptInPFLSOutput,
+ InvalidSUDT,
+ InvalidSUDTDataLength,
+ DecreasingAmount,
+}
+
+impl From for Error {
+ fn from(err: SysError) -> Self {
+ use SysError::*;
+ match err {
+ IndexOutOfBound => Self::IndexOutOfBound,
+ ItemMissing => Self::ItemMissing,
+ LengthNotEnough(_) => Self::LengthNotEnough,
+ Encoding => Self::Encoding,
+ Unknown(err_code) => panic!("unexpected sys error {}", err_code),
+ }
+ }
+}
+
+impl From for Error {
+ fn from(err: VerificationError) -> Self {
+ use VerificationError::*;
+ match err {
+ TotalSizeNotMatch(_, _, _) => Self::TotalSizeNotMatch,
+ HeaderIsBroken(_, _, _) => Self::HeaderIsBroken,
+ UnknownItem(_, _, _) => Self::UnknownItem,
+ OffsetsNotMatch(_) => Self::OffsetsNotMatch,
+ FieldCountNotMatch(_, _, _) => Self::FieldCountNotMatch,
+ }
+ }
+}
+
+impl From for Error {
+ fn from(_: SigError) -> Self {
+ return Self::SignatureVerificationError;
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/helpers.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/helpers.rs
new file mode 100644
index 0000000..e5f6e79
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/helpers.rs
@@ -0,0 +1,448 @@
+use blake2b_rs::Blake2bBuilder;
+
+#[cfg(feature = "std")]
+use {
+ crate::perun_types::ChannelState, ckb_types::bytes, ckb_types::packed::*,
+ ckb_types::prelude::*, std::vec::Vec,
+};
+
+#[cfg(not(feature = "std"))]
+use {
+ ckb_standalone_types::packed::*,
+ ckb_standalone_types::prelude::*,
+ molecule::prelude::{vec, Vec},
+};
+
+use crate::perun_types::{
+ Balances, Bool, BoolUnion, ChannelParameters, ChannelStatus, SEC1EncodedPubKey,
+};
+use crate::{
+ error::Error,
+ perun_types::{CKByteDistribution, SUDTAllocation, SUDTBalances, SUDTDistribution},
+};
+
+impl Bool {
+ pub fn to_bool(&self) -> bool {
+ match self.to_enum() {
+ BoolUnion::True(_) => true,
+ BoolUnion::False(_) => false,
+ }
+ }
+ pub fn from_bool(b: bool) -> Self {
+ if b {
+ return ctrue!();
+ } else {
+ return cfalse!();
+ }
+ }
+}
+
+#[macro_export]
+macro_rules! ctrue {
+ () => {
+ $crate::perun_types::BoolBuilder::default()
+ .set($crate::perun_types::BoolUnion::True(
+ $crate::perun_types::True::default(),
+ ))
+ .build()
+ };
+}
+pub(crate) use ctrue;
+
+#[macro_export]
+macro_rules! cfalse {
+ () => {
+ $crate::perun_types::BoolBuilder::default()
+ .set($crate::perun_types::BoolUnion::False(
+ $crate::perun_types::False::default(),
+ ))
+ .build()
+ };
+}
+pub(crate) use cfalse;
+
+#[macro_export]
+macro_rules! redeemer {
+ ($name:ident) => {
+ $crate::perun_types::ChannelWitnessBuilder::default()
+ .set($crate::perun_types::ChannelWitnessUnion::$name(
+ Default::default(),
+ ))
+ .build()
+ };
+ ($x:expr) => {
+ $crate::perun_types::ChannelWitnessBuilder::default()
+ .set($x)
+ .build()
+ };
+}
+
+#[macro_export]
+macro_rules! fund {
+ () => {
+ $crate::perun_types::ChannelWitnessUnion::Fund($crate::perun_types::Fund::default())
+ };
+}
+
+#[macro_export]
+macro_rules! close {
+ ($state:expr, $siga:expr, $sigb:expr) => {
+ $crate::perun_types::ChannelWitnessUnion::Close(
+ $crate::perun_types::Close::new_builder()
+ .state($state)
+ .sig_a($siga)
+ .sig_b($sigb)
+ .build(),
+ )
+ };
+}
+
+#[macro_export]
+macro_rules! dispute {
+ ($siga:expr, $sigb:expr) => {
+ $crate::perun_types::ChannelWitnessUnion::Dispute(
+ $crate::perun_types::Dispute::new_builder()
+ .sig_a($siga)
+ .sig_b($sigb)
+ .build(),
+ )
+ };
+}
+
+impl SUDTDistribution {
+ pub fn sum(&self) -> u128 {
+ let a: u128 = self.nth0().unpack();
+ let b: u128 = self.nth1().unpack();
+ a + b
+ }
+
+ pub fn equal(&self, other: &Balances) -> bool {
+ self.as_slice()[..] == other.as_slice()[..]
+ }
+
+ pub fn get(&self, i: usize) -> Result {
+ match i {
+ 0 => Ok(self.nth0().unpack()),
+ 1 => Ok(self.nth1().unpack()),
+ _ => Err(Error::IndexOutOfBound),
+ }
+ }
+
+ pub fn clear_index(&self, idx: usize) -> Result {
+ match idx {
+ 0 => Ok(self.clone().as_builder().nth0(0u128.pack()).build()),
+ 1 => Ok(self.clone().as_builder().nth1(0u128.pack()).build()),
+ _ => Err(Error::IndexOutOfBound),
+ }
+ }
+
+ pub fn from_array(a: [u128; 2]) -> Self {
+ SUDTDistribution::new_builder()
+ .nth0(a[0].pack())
+ .nth1(a[1].pack())
+ .build()
+ }
+
+ pub fn to_array(&self) -> [u128; 2] {
+ [self.nth0().unpack(), self.nth1().unpack()]
+ }
+}
+
+impl Balances {
+ pub fn clear_index(&self, idx: usize) -> Result {
+ let ckbytes = self.ckbytes().clear_index(idx)?;
+ let mut sudts: Vec = Vec::new();
+ for sb in self.sudts().into_iter() {
+ sudts.push(
+ sb.clone()
+ .as_builder()
+ .distribution(sb.distribution().clear_index(idx)?)
+ .build(),
+ );
+ }
+ Ok(self
+ .clone()
+ .as_builder()
+ .ckbytes(ckbytes)
+ .sudts(SUDTAllocation::new_builder().set(sudts).build())
+ .build())
+ }
+
+ pub fn zero_at_index(&self, idx: usize) -> Result {
+ if self.ckbytes().get(idx)? != 0u64 {
+ return Ok(false);
+ }
+ for sb in self.sudts().into_iter() {
+ if sb.distribution().get(idx)? != 0u128 {
+ return Ok(false);
+ }
+ }
+ return Ok(true);
+ }
+
+ pub fn equal_at_index(&self, other: &Balances, idx: usize) -> Result {
+ if self.ckbytes().get(idx)? != other.ckbytes().get(idx)? {
+ return Ok(false);
+ }
+ if self.sudts().len() != other.sudts().len() {
+ return Ok(false);
+ }
+ for (i, sb) in self.sudts().into_iter().enumerate() {
+ let other_sb = other.sudts().get(i).ok_or(Error::IndexOutOfBound)?;
+ if sb.asset().as_slice() != other_sb.as_slice() {
+ return Ok(false);
+ }
+ if sb.distribution().get(idx)? != other_sb.distribution().get(idx)? {
+ return Ok(false);
+ }
+ }
+ return Ok(true);
+ }
+
+ pub fn equal_in_sum(&self, other: &Balances) -> Result {
+ if self.ckbytes().sum() != other.ckbytes().sum() {
+ return Ok(false);
+ }
+ if self.sudts().len() != other.sudts().len() {
+ return Ok(false);
+ }
+ for (i, sb) in self.sudts().into_iter().enumerate() {
+ let other_sb = other.sudts().get(i).ok_or(Error::IndexOutOfBound)?;
+ if sb.asset().as_slice() != other_sb.asset().as_slice() {
+ return Ok(false);
+ }
+ if sb.distribution().sum() != other_sb.distribution().sum() {
+ return Ok(false);
+ }
+ }
+ return Ok(true);
+ }
+
+ pub fn equal(&self, other: &Balances) -> bool {
+ self.as_slice()[..] == other.as_slice()[..]
+ }
+}
+
+impl SUDTAllocation {
+ pub fn get_locked_ckbytes(&self) -> u64 {
+ let mut sum: u64 = 0u64;
+ for sudt in self.clone().into_iter() {
+ let min_cap: u64 = sudt.asset().max_capacity().unpack();
+ sum += min_cap;
+ }
+ return sum;
+ }
+
+ pub fn get_distribution(&self, sudt: &Script) -> Result<(usize, SUDTDistribution), Error> {
+ for (i, sb) in self.clone().into_iter().enumerate() {
+ if sb.asset().type_script().as_slice() == sudt.as_slice() {
+ return Ok((i, sb.distribution()));
+ }
+ }
+ return Err(Error::InvalidSUDT);
+ }
+
+ pub fn fully_represented(&self, idx: usize, values: &[u128]) -> Result {
+ if values.len() < self.len() {
+ return Ok(false);
+ }
+ for (i, sb) in self.clone().into_iter().enumerate() {
+ let v = sb.distribution().get(idx)?;
+ if values[i] < v {
+ return Ok(false);
+ }
+ }
+ return Ok(true);
+ }
+}
+
+impl CKByteDistribution {
+ pub fn sum(&self) -> u64 {
+ let a: u64 = self.nth0().unpack();
+ let b: u64 = self.nth1().unpack();
+ a + b
+ }
+
+ pub fn equal(&self, other: &Balances) -> bool {
+ self.as_slice()[..] == other.as_slice()[..]
+ }
+
+ pub fn get(&self, i: usize) -> Result {
+ match i {
+ 0 => Ok(self.nth0().unpack()),
+ 1 => Ok(self.nth1().unpack()),
+ _ => Err(Error::IndexOutOfBound),
+ }
+ }
+
+ pub fn clear_index(&self, idx: usize) -> Result {
+ match idx {
+ 0 => Ok(self.clone().as_builder().nth0(0u64.pack()).build()),
+ 1 => Ok(self.clone().as_builder().nth1(0u64.pack()).build()),
+ _ => Err(Error::IndexOutOfBound),
+ }
+ }
+
+ pub fn from_array(array: [u64; 2]) -> Self {
+ CKByteDistribution::new_builder()
+ .nth0(array[0].pack())
+ .nth1(array[1].pack())
+ .build()
+ }
+
+ pub fn to_array(&self) -> [u64; 2] {
+ [self.nth0().unpack(), self.nth1().unpack()]
+ }
+}
+
+pub fn geq_components(fst: &CKByteDistribution, snd: &CKByteDistribution) -> bool {
+ let a_fst: u64 = fst.nth0().unpack();
+ let a_snd: u64 = snd.nth0().unpack();
+ let b_fst: u64 = fst.nth1().unpack();
+ let b_snd: u64 = snd.nth1().unpack();
+ a_fst >= a_snd && b_fst >= b_snd
+}
+
+pub const CKB_HASH_PERSONALIZATION: &[u8] = b"ckb-default-hash";
+
+pub fn blake2b256(data: &[u8]) -> [u8; 32] {
+ let mut result = [0u8; 32];
+ let mut blake2b = Blake2bBuilder::new(32)
+ //.personal(CKB_HASH_PERSONALIZATION)
+ .build();
+ blake2b.update(data);
+ blake2b.finalize(&mut result);
+ result
+}
+
+impl ChannelStatus {
+ // mk_funded creates a new ChannelStatus with the funded flag set to true.
+ pub fn mk_funded(self) -> ChannelStatus {
+ self.clone().as_builder().funded(ctrue!()).build()
+ }
+
+ #[cfg(feature = "std")]
+ /// mk_close_outputs creates the outputs for a close transaction according to the current
+ /// channel state. It does not matter whether the ChannelState in question is finalized or not.
+ pub fn mk_close_outputs(
+ self,
+ mk_lock_script: impl FnMut(u8) -> Script,
+ ) -> Vec<(CellOutput, bytes::Bytes)> {
+ self.state().mk_outputs(mk_lock_script)
+ }
+}
+
+#[cfg(feature = "std")]
+impl ChannelState {
+ pub fn mk_outputs(
+ self,
+ mk_lock_script: impl FnMut(u8) -> Script,
+ ) -> Vec<(CellOutput, bytes::Bytes)> {
+ return self.balances().mk_outputs(mk_lock_script, vec![0, 1]);
+ }
+}
+
+#[cfg(feature = "std")]
+impl Balances {
+ pub fn mk_outputs(
+ self,
+ mut mk_lock_script: impl FnMut(u8) -> Script,
+ indices: Vec,
+ ) -> Vec<(CellOutput, bytes::Bytes)> {
+ let mut ckbytes = self
+ .ckbytes()
+ .mk_outputs(&mut mk_lock_script, indices.clone());
+ let mut sudts = self.sudts().mk_outputs(mk_lock_script, indices);
+ ckbytes.append(&mut sudts);
+ return ckbytes;
+ }
+}
+
+#[cfg(feature = "std")]
+impl CKByteDistribution {
+ pub fn mk_outputs(
+ self,
+ mut mk_lock_script: impl FnMut(u8) -> Script,
+ indices: Vec,
+ ) -> Vec<(CellOutput, bytes::Bytes)> {
+ // TODO: Outputs should contain min-capacity for script size...
+ indices
+ .iter()
+ .fold(vec![], |mut acc: Vec<(CellOutput, bytes::Bytes)>, index| {
+ let cap = self.get(index.clone() as usize).expect("invalid index");
+ acc.push((
+ CellOutput::new_builder()
+ .capacity(cap.pack())
+ .lock(mk_lock_script(*index))
+ .build(),
+ bytes::Bytes::new(),
+ ));
+ acc
+ })
+ }
+}
+
+#[cfg(feature = "std")]
+impl SUDTAllocation {
+ pub fn mk_outputs(
+ self,
+ mut mk_lock_script: impl FnMut(u8) -> Script,
+ indices: Vec,
+ ) -> Vec<(CellOutput, bytes::Bytes)> {
+ let mut outputs: Vec<(CellOutput, bytes::Bytes)> = Vec::new();
+ for (i, balance) in self.into_iter().enumerate() {
+ let udt_type = balance.asset().type_script();
+ let udt_type_opt = ScriptOpt::new_builder().set(Some(udt_type)).build();
+ let cap: u64 = balance.asset().max_capacity().unpack();
+ for f in indices.iter() {
+ if balance
+ .distribution()
+ .get(*f as usize)
+ .expect("invalid index")
+ == 0u128
+ {
+ outputs.push((
+ CellOutput::new_builder()
+ .capacity(cap.pack())
+ .lock(mk_lock_script(*f))
+ .build(),
+ bytes::Bytes::new(),
+ ));
+ } else {
+ outputs.push((
+ CellOutput::new_builder()
+ .capacity(cap.pack())
+ .lock(mk_lock_script(*f))
+ .type_(udt_type_opt.clone())
+ .build(),
+ bytes::Bytes::from(
+ balance
+ .distribution()
+ .get(*f as usize)
+ .expect("invalid index")
+ .to_le_bytes()
+ .to_vec(),
+ ),
+ ));
+ }
+ }
+ }
+ return outputs;
+ }
+}
+
+impl ChannelParameters {
+ /// mk_party_pubkeys creates a vector of each participants public key in the correct order.
+ pub fn mk_party_pubkeys(self) -> Vec> {
+ vec![
+ self.party_a().pub_key().to_vec(),
+ self.party_b().pub_key().to_vec(),
+ ]
+ }
+}
+
+impl SEC1EncodedPubKey {
+ pub fn to_vec(&self) -> Vec {
+ self.as_bytes().to_vec()
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/lib.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/lib.rs
new file mode 100644
index 0000000..39d0017
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/lib.rs
@@ -0,0 +1,7 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub mod error;
+pub mod helpers;
+#[allow(clippy::all)]
+pub mod perun_types;
+pub mod sig;
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/perun_types.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/perun_types.rs
new file mode 100644
index 0000000..61fe220
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/perun_types.rs
@@ -0,0 +1,6561 @@
+// Generated by Molecule 0.7.3
+#![allow(unused_imports)]
+
+#[cfg(feature = "std")]
+use {ckb_types::packed::*, ckb_types::prelude::*};
+
+#[cfg(not(feature = "std"))]
+use {ckb_standalone_types::packed::*, ckb_standalone_types::prelude::*};
+
+use molecule::prelude::*;
+
+#[derive(Clone)]
+pub struct SEC1EncodedPubKey(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for SEC1EncodedPubKey {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for SEC1EncodedPubKey {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for SEC1EncodedPubKey {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl ::core::default::Default for SEC1EncodedPubKey {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ ];
+ SEC1EncodedPubKey::new_unchecked(v.into())
+ }
+}
+impl SEC1EncodedPubKey {
+ pub const TOTAL_SIZE: usize = 33;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 33;
+ pub fn nth0(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(0..1))
+ }
+ pub fn nth1(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(1..2))
+ }
+ pub fn nth2(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(2..3))
+ }
+ pub fn nth3(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(3..4))
+ }
+ pub fn nth4(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(4..5))
+ }
+ pub fn nth5(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(5..6))
+ }
+ pub fn nth6(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(6..7))
+ }
+ pub fn nth7(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(7..8))
+ }
+ pub fn nth8(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(8..9))
+ }
+ pub fn nth9(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(9..10))
+ }
+ pub fn nth10(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(10..11))
+ }
+ pub fn nth11(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(11..12))
+ }
+ pub fn nth12(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(12..13))
+ }
+ pub fn nth13(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(13..14))
+ }
+ pub fn nth14(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(14..15))
+ }
+ pub fn nth15(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(15..16))
+ }
+ pub fn nth16(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(16..17))
+ }
+ pub fn nth17(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(17..18))
+ }
+ pub fn nth18(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(18..19))
+ }
+ pub fn nth19(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(19..20))
+ }
+ pub fn nth20(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(20..21))
+ }
+ pub fn nth21(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(21..22))
+ }
+ pub fn nth22(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(22..23))
+ }
+ pub fn nth23(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(23..24))
+ }
+ pub fn nth24(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(24..25))
+ }
+ pub fn nth25(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(25..26))
+ }
+ pub fn nth26(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(26..27))
+ }
+ pub fn nth27(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(27..28))
+ }
+ pub fn nth28(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(28..29))
+ }
+ pub fn nth29(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(29..30))
+ }
+ pub fn nth30(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(30..31))
+ }
+ pub fn nth31(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(31..32))
+ }
+ pub fn nth32(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(32..33))
+ }
+ pub fn raw_data(&self) -> molecule::bytes::Bytes {
+ self.as_bytes()
+ }
+ pub fn as_reader<'r>(&'r self) -> SEC1EncodedPubKeyReader<'r> {
+ SEC1EncodedPubKeyReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for SEC1EncodedPubKey {
+ type Builder = SEC1EncodedPubKeyBuilder;
+ const NAME: &'static str = "SEC1EncodedPubKey";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ SEC1EncodedPubKey(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SEC1EncodedPubKeyReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SEC1EncodedPubKeyReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([
+ self.nth0(),
+ self.nth1(),
+ self.nth2(),
+ self.nth3(),
+ self.nth4(),
+ self.nth5(),
+ self.nth6(),
+ self.nth7(),
+ self.nth8(),
+ self.nth9(),
+ self.nth10(),
+ self.nth11(),
+ self.nth12(),
+ self.nth13(),
+ self.nth14(),
+ self.nth15(),
+ self.nth16(),
+ self.nth17(),
+ self.nth18(),
+ self.nth19(),
+ self.nth20(),
+ self.nth21(),
+ self.nth22(),
+ self.nth23(),
+ self.nth24(),
+ self.nth25(),
+ self.nth26(),
+ self.nth27(),
+ self.nth28(),
+ self.nth29(),
+ self.nth30(),
+ self.nth31(),
+ self.nth32(),
+ ])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct SEC1EncodedPubKeyReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for SEC1EncodedPubKeyReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for SEC1EncodedPubKeyReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for SEC1EncodedPubKeyReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl<'r> SEC1EncodedPubKeyReader<'r> {
+ pub const TOTAL_SIZE: usize = 33;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 33;
+ pub fn nth0(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[0..1])
+ }
+ pub fn nth1(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[1..2])
+ }
+ pub fn nth2(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[2..3])
+ }
+ pub fn nth3(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[3..4])
+ }
+ pub fn nth4(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[4..5])
+ }
+ pub fn nth5(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[5..6])
+ }
+ pub fn nth6(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[6..7])
+ }
+ pub fn nth7(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[7..8])
+ }
+ pub fn nth8(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[8..9])
+ }
+ pub fn nth9(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[9..10])
+ }
+ pub fn nth10(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[10..11])
+ }
+ pub fn nth11(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[11..12])
+ }
+ pub fn nth12(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[12..13])
+ }
+ pub fn nth13(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[13..14])
+ }
+ pub fn nth14(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[14..15])
+ }
+ pub fn nth15(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[15..16])
+ }
+ pub fn nth16(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[16..17])
+ }
+ pub fn nth17(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[17..18])
+ }
+ pub fn nth18(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[18..19])
+ }
+ pub fn nth19(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[19..20])
+ }
+ pub fn nth20(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[20..21])
+ }
+ pub fn nth21(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[21..22])
+ }
+ pub fn nth22(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[22..23])
+ }
+ pub fn nth23(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[23..24])
+ }
+ pub fn nth24(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[24..25])
+ }
+ pub fn nth25(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[25..26])
+ }
+ pub fn nth26(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[26..27])
+ }
+ pub fn nth27(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[27..28])
+ }
+ pub fn nth28(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[28..29])
+ }
+ pub fn nth29(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[29..30])
+ }
+ pub fn nth30(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[30..31])
+ }
+ pub fn nth31(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[31..32])
+ }
+ pub fn nth32(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[32..33])
+ }
+ pub fn raw_data(&self) -> &'r [u8] {
+ self.as_slice()
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for SEC1EncodedPubKeyReader<'r> {
+ type Entity = SEC1EncodedPubKey;
+ const NAME: &'static str = "SEC1EncodedPubKeyReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ SEC1EncodedPubKeyReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct SEC1EncodedPubKeyBuilder(pub(crate) [Byte; 33]);
+impl ::core::fmt::Debug for SEC1EncodedPubKeyBuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for SEC1EncodedPubKeyBuilder {
+ fn default() -> Self {
+ SEC1EncodedPubKeyBuilder([
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ Byte::default(),
+ ])
+ }
+}
+impl SEC1EncodedPubKeyBuilder {
+ pub const TOTAL_SIZE: usize = 33;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 33;
+ pub fn set(mut self, v: [Byte; 33]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Byte) -> Self {
+ self.0[0] = v;
+ self
+ }
+ pub fn nth1(mut self, v: Byte) -> Self {
+ self.0[1] = v;
+ self
+ }
+ pub fn nth2(mut self, v: Byte) -> Self {
+ self.0[2] = v;
+ self
+ }
+ pub fn nth3(mut self, v: Byte) -> Self {
+ self.0[3] = v;
+ self
+ }
+ pub fn nth4(mut self, v: Byte) -> Self {
+ self.0[4] = v;
+ self
+ }
+ pub fn nth5(mut self, v: Byte) -> Self {
+ self.0[5] = v;
+ self
+ }
+ pub fn nth6(mut self, v: Byte) -> Self {
+ self.0[6] = v;
+ self
+ }
+ pub fn nth7(mut self, v: Byte) -> Self {
+ self.0[7] = v;
+ self
+ }
+ pub fn nth8(mut self, v: Byte) -> Self {
+ self.0[8] = v;
+ self
+ }
+ pub fn nth9(mut self, v: Byte) -> Self {
+ self.0[9] = v;
+ self
+ }
+ pub fn nth10(mut self, v: Byte) -> Self {
+ self.0[10] = v;
+ self
+ }
+ pub fn nth11(mut self, v: Byte) -> Self {
+ self.0[11] = v;
+ self
+ }
+ pub fn nth12(mut self, v: Byte) -> Self {
+ self.0[12] = v;
+ self
+ }
+ pub fn nth13(mut self, v: Byte) -> Self {
+ self.0[13] = v;
+ self
+ }
+ pub fn nth14(mut self, v: Byte) -> Self {
+ self.0[14] = v;
+ self
+ }
+ pub fn nth15(mut self, v: Byte) -> Self {
+ self.0[15] = v;
+ self
+ }
+ pub fn nth16(mut self, v: Byte) -> Self {
+ self.0[16] = v;
+ self
+ }
+ pub fn nth17(mut self, v: Byte) -> Self {
+ self.0[17] = v;
+ self
+ }
+ pub fn nth18(mut self, v: Byte) -> Self {
+ self.0[18] = v;
+ self
+ }
+ pub fn nth19(mut self, v: Byte) -> Self {
+ self.0[19] = v;
+ self
+ }
+ pub fn nth20(mut self, v: Byte) -> Self {
+ self.0[20] = v;
+ self
+ }
+ pub fn nth21(mut self, v: Byte) -> Self {
+ self.0[21] = v;
+ self
+ }
+ pub fn nth22(mut self, v: Byte) -> Self {
+ self.0[22] = v;
+ self
+ }
+ pub fn nth23(mut self, v: Byte) -> Self {
+ self.0[23] = v;
+ self
+ }
+ pub fn nth24(mut self, v: Byte) -> Self {
+ self.0[24] = v;
+ self
+ }
+ pub fn nth25(mut self, v: Byte) -> Self {
+ self.0[25] = v;
+ self
+ }
+ pub fn nth26(mut self, v: Byte) -> Self {
+ self.0[26] = v;
+ self
+ }
+ pub fn nth27(mut self, v: Byte) -> Self {
+ self.0[27] = v;
+ self
+ }
+ pub fn nth28(mut self, v: Byte) -> Self {
+ self.0[28] = v;
+ self
+ }
+ pub fn nth29(mut self, v: Byte) -> Self {
+ self.0[29] = v;
+ self
+ }
+ pub fn nth30(mut self, v: Byte) -> Self {
+ self.0[30] = v;
+ self
+ }
+ pub fn nth31(mut self, v: Byte) -> Self {
+ self.0[31] = v;
+ self
+ }
+ pub fn nth32(mut self, v: Byte) -> Self {
+ self.0[32] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for SEC1EncodedPubKeyBuilder {
+ type Entity = SEC1EncodedPubKey;
+ const NAME: &'static str = "SEC1EncodedPubKeyBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ writer.write_all(self.0[1].as_slice())?;
+ writer.write_all(self.0[2].as_slice())?;
+ writer.write_all(self.0[3].as_slice())?;
+ writer.write_all(self.0[4].as_slice())?;
+ writer.write_all(self.0[5].as_slice())?;
+ writer.write_all(self.0[6].as_slice())?;
+ writer.write_all(self.0[7].as_slice())?;
+ writer.write_all(self.0[8].as_slice())?;
+ writer.write_all(self.0[9].as_slice())?;
+ writer.write_all(self.0[10].as_slice())?;
+ writer.write_all(self.0[11].as_slice())?;
+ writer.write_all(self.0[12].as_slice())?;
+ writer.write_all(self.0[13].as_slice())?;
+ writer.write_all(self.0[14].as_slice())?;
+ writer.write_all(self.0[15].as_slice())?;
+ writer.write_all(self.0[16].as_slice())?;
+ writer.write_all(self.0[17].as_slice())?;
+ writer.write_all(self.0[18].as_slice())?;
+ writer.write_all(self.0[19].as_slice())?;
+ writer.write_all(self.0[20].as_slice())?;
+ writer.write_all(self.0[21].as_slice())?;
+ writer.write_all(self.0[22].as_slice())?;
+ writer.write_all(self.0[23].as_slice())?;
+ writer.write_all(self.0[24].as_slice())?;
+ writer.write_all(self.0[25].as_slice())?;
+ writer.write_all(self.0[26].as_slice())?;
+ writer.write_all(self.0[27].as_slice())?;
+ writer.write_all(self.0[28].as_slice())?;
+ writer.write_all(self.0[29].as_slice())?;
+ writer.write_all(self.0[30].as_slice())?;
+ writer.write_all(self.0[31].as_slice())?;
+ writer.write_all(self.0[32].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ SEC1EncodedPubKey::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct CKByteDistribution(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for CKByteDistribution {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for CKByteDistribution {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for CKByteDistribution {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} [", Self::NAME)?;
+ write!(f, "{}", self.nth0())?;
+ write!(f, ", {}", self.nth1())?;
+ write!(f, "]")
+ }
+}
+impl ::core::default::Default for CKByteDistribution {
+ fn default() -> Self {
+ let v: Vec = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+ CKByteDistribution::new_unchecked(v.into())
+ }
+}
+impl CKByteDistribution {
+ pub const TOTAL_SIZE: usize = 16;
+ pub const ITEM_SIZE: usize = 8;
+ pub const ITEM_COUNT: usize = 2;
+ pub fn nth0(&self) -> Uint64 {
+ Uint64::new_unchecked(self.0.slice(0..8))
+ }
+ pub fn nth1(&self) -> Uint64 {
+ Uint64::new_unchecked(self.0.slice(8..16))
+ }
+ pub fn as_reader<'r>(&'r self) -> CKByteDistributionReader<'r> {
+ CKByteDistributionReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for CKByteDistribution {
+ type Builder = CKByteDistributionBuilder;
+ const NAME: &'static str = "CKByteDistribution";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ CKByteDistribution(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ CKByteDistributionReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ CKByteDistributionReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([self.nth0(), self.nth1()])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct CKByteDistributionReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for CKByteDistributionReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for CKByteDistributionReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for CKByteDistributionReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} [", Self::NAME)?;
+ write!(f, "{}", self.nth0())?;
+ write!(f, ", {}", self.nth1())?;
+ write!(f, "]")
+ }
+}
+impl<'r> CKByteDistributionReader<'r> {
+ pub const TOTAL_SIZE: usize = 16;
+ pub const ITEM_SIZE: usize = 8;
+ pub const ITEM_COUNT: usize = 2;
+ pub fn nth0(&self) -> Uint64Reader<'r> {
+ Uint64Reader::new_unchecked(&self.as_slice()[0..8])
+ }
+ pub fn nth1(&self) -> Uint64Reader<'r> {
+ Uint64Reader::new_unchecked(&self.as_slice()[8..16])
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for CKByteDistributionReader<'r> {
+ type Entity = CKByteDistribution;
+ const NAME: &'static str = "CKByteDistributionReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ CKByteDistributionReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct CKByteDistributionBuilder(pub(crate) [Uint64; 2]);
+impl ::core::fmt::Debug for CKByteDistributionBuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for CKByteDistributionBuilder {
+ fn default() -> Self {
+ CKByteDistributionBuilder([Uint64::default(), Uint64::default()])
+ }
+}
+impl CKByteDistributionBuilder {
+ pub const TOTAL_SIZE: usize = 16;
+ pub const ITEM_SIZE: usize = 8;
+ pub const ITEM_COUNT: usize = 2;
+ pub fn set(mut self, v: [Uint64; 2]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Uint64) -> Self {
+ self.0[0] = v;
+ self
+ }
+ pub fn nth1(mut self, v: Uint64) -> Self {
+ self.0[1] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for CKByteDistributionBuilder {
+ type Entity = CKByteDistribution;
+ const NAME: &'static str = "CKByteDistributionBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ writer.write_all(self.0[1].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ CKByteDistribution::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct SUDTDistribution(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for SUDTDistribution {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for SUDTDistribution {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for SUDTDistribution {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} [", Self::NAME)?;
+ write!(f, "{}", self.nth0())?;
+ write!(f, ", {}", self.nth1())?;
+ write!(f, "]")
+ }
+}
+impl ::core::default::Default for SUDTDistribution {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0,
+ ];
+ SUDTDistribution::new_unchecked(v.into())
+ }
+}
+impl SUDTDistribution {
+ pub const TOTAL_SIZE: usize = 32;
+ pub const ITEM_SIZE: usize = 16;
+ pub const ITEM_COUNT: usize = 2;
+ pub fn nth0(&self) -> Uint128 {
+ Uint128::new_unchecked(self.0.slice(0..16))
+ }
+ pub fn nth1(&self) -> Uint128 {
+ Uint128::new_unchecked(self.0.slice(16..32))
+ }
+ pub fn as_reader<'r>(&'r self) -> SUDTDistributionReader<'r> {
+ SUDTDistributionReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for SUDTDistribution {
+ type Builder = SUDTDistributionBuilder;
+ const NAME: &'static str = "SUDTDistribution";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ SUDTDistribution(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SUDTDistributionReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SUDTDistributionReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([self.nth0(), self.nth1()])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct SUDTDistributionReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for SUDTDistributionReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for SUDTDistributionReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for SUDTDistributionReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} [", Self::NAME)?;
+ write!(f, "{}", self.nth0())?;
+ write!(f, ", {}", self.nth1())?;
+ write!(f, "]")
+ }
+}
+impl<'r> SUDTDistributionReader<'r> {
+ pub const TOTAL_SIZE: usize = 32;
+ pub const ITEM_SIZE: usize = 16;
+ pub const ITEM_COUNT: usize = 2;
+ pub fn nth0(&self) -> Uint128Reader<'r> {
+ Uint128Reader::new_unchecked(&self.as_slice()[0..16])
+ }
+ pub fn nth1(&self) -> Uint128Reader<'r> {
+ Uint128Reader::new_unchecked(&self.as_slice()[16..32])
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for SUDTDistributionReader<'r> {
+ type Entity = SUDTDistribution;
+ const NAME: &'static str = "SUDTDistributionReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ SUDTDistributionReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct SUDTDistributionBuilder(pub(crate) [Uint128; 2]);
+impl ::core::fmt::Debug for SUDTDistributionBuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for SUDTDistributionBuilder {
+ fn default() -> Self {
+ SUDTDistributionBuilder([Uint128::default(), Uint128::default()])
+ }
+}
+impl SUDTDistributionBuilder {
+ pub const TOTAL_SIZE: usize = 32;
+ pub const ITEM_SIZE: usize = 16;
+ pub const ITEM_COUNT: usize = 2;
+ pub fn set(mut self, v: [Uint128; 2]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Uint128) -> Self {
+ self.0[0] = v;
+ self
+ }
+ pub fn nth1(mut self, v: Uint128) -> Self {
+ self.0[1] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for SUDTDistributionBuilder {
+ type Entity = SUDTDistribution;
+ const NAME: &'static str = "SUDTDistributionBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ writer.write_all(self.0[1].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ SUDTDistribution::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct SUDTAllocation(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for SUDTAllocation {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for SUDTAllocation {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for SUDTAllocation {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} [", Self::NAME)?;
+ for i in 0..self.len() {
+ if i == 0 {
+ write!(f, "{}", self.get_unchecked(i))?;
+ } else {
+ write!(f, ", {}", self.get_unchecked(i))?;
+ }
+ }
+ write!(f, "]")
+ }
+}
+impl ::core::default::Default for SUDTAllocation {
+ fn default() -> Self {
+ let v: Vec = vec![4, 0, 0, 0];
+ SUDTAllocation::new_unchecked(v.into())
+ }
+}
+impl SUDTAllocation {
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn item_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn len(&self) -> usize {
+ self.item_count()
+ }
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+ pub fn get(&self, idx: usize) -> Option {
+ if idx >= self.len() {
+ None
+ } else {
+ Some(self.get_unchecked(idx))
+ }
+ }
+ pub fn get_unchecked(&self, idx: usize) -> SUDTBalances {
+ let slice = self.as_slice();
+ let start_idx = molecule::NUMBER_SIZE * (1 + idx);
+ let start = molecule::unpack_number(&slice[start_idx..]) as usize;
+ if idx == self.len() - 1 {
+ SUDTBalances::new_unchecked(self.0.slice(start..))
+ } else {
+ let end_idx = start_idx + molecule::NUMBER_SIZE;
+ let end = molecule::unpack_number(&slice[end_idx..]) as usize;
+ SUDTBalances::new_unchecked(self.0.slice(start..end))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> SUDTAllocationReader<'r> {
+ SUDTAllocationReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for SUDTAllocation {
+ type Builder = SUDTAllocationBuilder;
+ const NAME: &'static str = "SUDTAllocation";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ SUDTAllocation(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SUDTAllocationReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SUDTAllocationReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().extend(self.into_iter())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct SUDTAllocationReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for SUDTAllocationReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for SUDTAllocationReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for SUDTAllocationReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} [", Self::NAME)?;
+ for i in 0..self.len() {
+ if i == 0 {
+ write!(f, "{}", self.get_unchecked(i))?;
+ } else {
+ write!(f, ", {}", self.get_unchecked(i))?;
+ }
+ }
+ write!(f, "]")
+ }
+}
+impl<'r> SUDTAllocationReader<'r> {
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn item_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn len(&self) -> usize {
+ self.item_count()
+ }
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+ pub fn get(&self, idx: usize) -> Option> {
+ if idx >= self.len() {
+ None
+ } else {
+ Some(self.get_unchecked(idx))
+ }
+ }
+ pub fn get_unchecked(&self, idx: usize) -> SUDTBalancesReader<'r> {
+ let slice = self.as_slice();
+ let start_idx = molecule::NUMBER_SIZE * (1 + idx);
+ let start = molecule::unpack_number(&slice[start_idx..]) as usize;
+ if idx == self.len() - 1 {
+ SUDTBalancesReader::new_unchecked(&self.as_slice()[start..])
+ } else {
+ let end_idx = start_idx + molecule::NUMBER_SIZE;
+ let end = molecule::unpack_number(&slice[end_idx..]) as usize;
+ SUDTBalancesReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for SUDTAllocationReader<'r> {
+ type Entity = SUDTAllocation;
+ const NAME: &'static str = "SUDTAllocationReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ SUDTAllocationReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(
+ Self,
+ TotalSizeNotMatch,
+ molecule::NUMBER_SIZE * 2,
+ slice_len
+ );
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ for pair in offsets.windows(2) {
+ let start = pair[0];
+ let end = pair[1];
+ SUDTBalancesReader::verify(&slice[start..end], compatible)?;
+ }
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct SUDTAllocationBuilder(pub(crate) Vec);
+impl SUDTAllocationBuilder {
+ pub fn set(mut self, v: Vec) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn push(mut self, v: SUDTBalances) -> Self {
+ self.0.push(v);
+ self
+ }
+ pub fn extend>(mut self, iter: T) -> Self {
+ for elem in iter {
+ self.0.push(elem);
+ }
+ self
+ }
+ pub fn replace(&mut self, index: usize, v: SUDTBalances) -> Option {
+ self.0
+ .get_mut(index)
+ .map(|item| ::core::mem::replace(item, v))
+ }
+}
+impl molecule::prelude::Builder for SUDTAllocationBuilder {
+ type Entity = SUDTAllocation;
+ const NAME: &'static str = "SUDTAllocationBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (self.0.len() + 1)
+ + self
+ .0
+ .iter()
+ .map(|inner| inner.as_slice().len())
+ .sum::()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let item_count = self.0.len();
+ if item_count == 0 {
+ writer.write_all(&molecule::pack_number(
+ molecule::NUMBER_SIZE as molecule::Number,
+ ))?;
+ } else {
+ let (total_size, offsets) = self.0.iter().fold(
+ (
+ molecule::NUMBER_SIZE * (item_count + 1),
+ Vec::with_capacity(item_count),
+ ),
+ |(start, mut offsets), inner| {
+ offsets.push(start);
+ (start + inner.as_slice().len(), offsets)
+ },
+ );
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ for inner in self.0.iter() {
+ writer.write_all(inner.as_slice())?;
+ }
+ }
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ SUDTAllocation::new_unchecked(inner.into())
+ }
+}
+pub struct SUDTAllocationIterator(SUDTAllocation, usize, usize);
+impl ::core::iter::Iterator for SUDTAllocationIterator {
+ type Item = SUDTBalances;
+ fn next(&mut self) -> Option {
+ if self.1 >= self.2 {
+ None
+ } else {
+ let ret = self.0.get_unchecked(self.1);
+ self.1 += 1;
+ Some(ret)
+ }
+ }
+}
+impl ::core::iter::ExactSizeIterator for SUDTAllocationIterator {
+ fn len(&self) -> usize {
+ self.2 - self.1
+ }
+}
+impl ::core::iter::IntoIterator for SUDTAllocation {
+ type Item = SUDTBalances;
+ type IntoIter = SUDTAllocationIterator;
+ fn into_iter(self) -> Self::IntoIter {
+ let len = self.len();
+ SUDTAllocationIterator(self, 0, len)
+ }
+}
+impl<'r> SUDTAllocationReader<'r> {
+ pub fn iter<'t>(&'t self) -> SUDTAllocationReaderIterator<'t, 'r> {
+ SUDTAllocationReaderIterator(&self, 0, self.len())
+ }
+}
+pub struct SUDTAllocationReaderIterator<'t, 'r>(&'t SUDTAllocationReader<'r>, usize, usize);
+impl<'t: 'r, 'r> ::core::iter::Iterator for SUDTAllocationReaderIterator<'t, 'r> {
+ type Item = SUDTBalancesReader<'t>;
+ fn next(&mut self) -> Option {
+ if self.1 >= self.2 {
+ None
+ } else {
+ let ret = self.0.get_unchecked(self.1);
+ self.1 += 1;
+ Some(ret)
+ }
+ }
+}
+impl<'t: 'r, 'r> ::core::iter::ExactSizeIterator for SUDTAllocationReaderIterator<'t, 'r> {
+ fn len(&self) -> usize {
+ self.2 - self.1
+ }
+}
+#[derive(Clone)]
+pub struct SUDTAsset(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for SUDTAsset {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for SUDTAsset {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for SUDTAsset {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "type_script", self.type_script())?;
+ write!(f, ", {}: {}", "max_capacity", self.max_capacity())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for SUDTAsset {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 73, 0, 0, 0, 12, 0, 0, 0, 65, 0, 0, 0, 53, 0, 0, 0, 16, 0, 0, 0, 48, 0, 0, 0, 49, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ];
+ SUDTAsset::new_unchecked(v.into())
+ }
+}
+impl SUDTAsset {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn type_script(&self) -> Script {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ Script::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn max_capacity(&self) -> Uint64 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Uint64::new_unchecked(self.0.slice(start..end))
+ } else {
+ Uint64::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> SUDTAssetReader<'r> {
+ SUDTAssetReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for SUDTAsset {
+ type Builder = SUDTAssetBuilder;
+ const NAME: &'static str = "SUDTAsset";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ SUDTAsset(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SUDTAssetReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SUDTAssetReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder()
+ .type_script(self.type_script())
+ .max_capacity(self.max_capacity())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct SUDTAssetReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for SUDTAssetReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for SUDTAssetReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for SUDTAssetReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "type_script", self.type_script())?;
+ write!(f, ", {}: {}", "max_capacity", self.max_capacity())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> SUDTAssetReader<'r> {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn type_script(&self) -> ScriptReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ ScriptReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn max_capacity(&self) -> Uint64Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Uint64Reader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ Uint64Reader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for SUDTAssetReader<'r> {
+ type Entity = SUDTAsset;
+ const NAME: &'static str = "SUDTAssetReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ SUDTAssetReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ ScriptReader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ Uint64Reader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct SUDTAssetBuilder {
+ pub(crate) type_script: Script,
+ pub(crate) max_capacity: Uint64,
+}
+impl SUDTAssetBuilder {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn type_script(mut self, v: Script) -> Self {
+ self.type_script = v;
+ self
+ }
+ pub fn max_capacity(mut self, v: Uint64) -> Self {
+ self.max_capacity = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for SUDTAssetBuilder {
+ type Entity = SUDTAsset;
+ const NAME: &'static str = "SUDTAssetBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.type_script.as_slice().len()
+ + self.max_capacity.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.type_script.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.max_capacity.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.type_script.as_slice())?;
+ writer.write_all(self.max_capacity.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ SUDTAsset::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct SUDTBalances(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for SUDTBalances {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for SUDTBalances {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for SUDTBalances {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "asset", self.asset())?;
+ write!(f, ", {}: {}", "distribution", self.distribution())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for SUDTBalances {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 117, 0, 0, 0, 12, 0, 0, 0, 85, 0, 0, 0, 73, 0, 0, 0, 12, 0, 0, 0, 65, 0, 0, 0, 53, 0,
+ 0, 0, 16, 0, 0, 0, 48, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ ];
+ SUDTBalances::new_unchecked(v.into())
+ }
+}
+impl SUDTBalances {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn asset(&self) -> SUDTAsset {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ SUDTAsset::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn distribution(&self) -> SUDTDistribution {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ SUDTDistribution::new_unchecked(self.0.slice(start..end))
+ } else {
+ SUDTDistribution::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> SUDTBalancesReader<'r> {
+ SUDTBalancesReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for SUDTBalances {
+ type Builder = SUDTBalancesBuilder;
+ const NAME: &'static str = "SUDTBalances";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ SUDTBalances(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SUDTBalancesReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ SUDTBalancesReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder()
+ .asset(self.asset())
+ .distribution(self.distribution())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct SUDTBalancesReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for SUDTBalancesReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for SUDTBalancesReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for SUDTBalancesReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "asset", self.asset())?;
+ write!(f, ", {}: {}", "distribution", self.distribution())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> SUDTBalancesReader<'r> {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn asset(&self) -> SUDTAssetReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ SUDTAssetReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn distribution(&self) -> SUDTDistributionReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ SUDTDistributionReader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ SUDTDistributionReader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for SUDTBalancesReader<'r> {
+ type Entity = SUDTBalances;
+ const NAME: &'static str = "SUDTBalancesReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ SUDTBalancesReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ SUDTAssetReader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ SUDTDistributionReader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct SUDTBalancesBuilder {
+ pub(crate) asset: SUDTAsset,
+ pub(crate) distribution: SUDTDistribution,
+}
+impl SUDTBalancesBuilder {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn asset(mut self, v: SUDTAsset) -> Self {
+ self.asset = v;
+ self
+ }
+ pub fn distribution(mut self, v: SUDTDistribution) -> Self {
+ self.distribution = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for SUDTBalancesBuilder {
+ type Entity = SUDTBalances;
+ const NAME: &'static str = "SUDTBalancesBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.asset.as_slice().len()
+ + self.distribution.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.asset.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.distribution.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.asset.as_slice())?;
+ writer.write_all(self.distribution.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ SUDTBalances::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct Balances(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for Balances {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for Balances {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for Balances {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "ckbytes", self.ckbytes())?;
+ write!(f, ", {}: {}", "sudts", self.sudts())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for Balances {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 32, 0, 0, 0, 12, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 4, 0, 0, 0,
+ ];
+ Balances::new_unchecked(v.into())
+ }
+}
+impl Balances {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn ckbytes(&self) -> CKByteDistribution {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ CKByteDistribution::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn sudts(&self) -> SUDTAllocation {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ SUDTAllocation::new_unchecked(self.0.slice(start..end))
+ } else {
+ SUDTAllocation::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> BalancesReader<'r> {
+ BalancesReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for Balances {
+ type Builder = BalancesBuilder;
+ const NAME: &'static str = "Balances";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ Balances(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ BalancesReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ BalancesReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder()
+ .ckbytes(self.ckbytes())
+ .sudts(self.sudts())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct BalancesReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for BalancesReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for BalancesReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for BalancesReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "ckbytes", self.ckbytes())?;
+ write!(f, ", {}: {}", "sudts", self.sudts())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> BalancesReader<'r> {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn ckbytes(&self) -> CKByteDistributionReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ CKByteDistributionReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn sudts(&self) -> SUDTAllocationReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ SUDTAllocationReader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ SUDTAllocationReader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for BalancesReader<'r> {
+ type Entity = Balances;
+ const NAME: &'static str = "BalancesReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ BalancesReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ CKByteDistributionReader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ SUDTAllocationReader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct BalancesBuilder {
+ pub(crate) ckbytes: CKByteDistribution,
+ pub(crate) sudts: SUDTAllocation,
+}
+impl BalancesBuilder {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn ckbytes(mut self, v: CKByteDistribution) -> Self {
+ self.ckbytes = v;
+ self
+ }
+ pub fn sudts(mut self, v: SUDTAllocation) -> Self {
+ self.sudts = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for BalancesBuilder {
+ type Entity = Balances;
+ const NAME: &'static str = "BalancesBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.ckbytes.as_slice().len()
+ + self.sudts.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.ckbytes.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.sudts.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.ckbytes.as_slice())?;
+ writer.write_all(self.sudts.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ Balances::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct True(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for True {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for True {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for True {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl ::core::default::Default for True {
+ fn default() -> Self {
+ let v: Vec = vec![0];
+ True::new_unchecked(v.into())
+ }
+}
+impl True {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(0..1))
+ }
+ pub fn raw_data(&self) -> molecule::bytes::Bytes {
+ self.as_bytes()
+ }
+ pub fn as_reader<'r>(&'r self) -> TrueReader<'r> {
+ TrueReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for True {
+ type Builder = TrueBuilder;
+ const NAME: &'static str = "True";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ True(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ TrueReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ TrueReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([self.nth0()])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct TrueReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for TrueReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for TrueReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for TrueReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl<'r> TrueReader<'r> {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[0..1])
+ }
+ pub fn raw_data(&self) -> &'r [u8] {
+ self.as_slice()
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for TrueReader<'r> {
+ type Entity = True;
+ const NAME: &'static str = "TrueReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ TrueReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct TrueBuilder(pub(crate) [Byte; 1]);
+impl ::core::fmt::Debug for TrueBuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for TrueBuilder {
+ fn default() -> Self {
+ TrueBuilder([Byte::default()])
+ }
+}
+impl TrueBuilder {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn set(mut self, v: [Byte; 1]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Byte) -> Self {
+ self.0[0] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for TrueBuilder {
+ type Entity = True;
+ const NAME: &'static str = "TrueBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ True::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct False(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for False {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for False {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for False {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl ::core::default::Default for False {
+ fn default() -> Self {
+ let v: Vec = vec![0];
+ False::new_unchecked(v.into())
+ }
+}
+impl False {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(0..1))
+ }
+ pub fn raw_data(&self) -> molecule::bytes::Bytes {
+ self.as_bytes()
+ }
+ pub fn as_reader<'r>(&'r self) -> FalseReader<'r> {
+ FalseReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for False {
+ type Builder = FalseBuilder;
+ const NAME: &'static str = "False";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ False(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ FalseReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ FalseReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([self.nth0()])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct FalseReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for FalseReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for FalseReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for FalseReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl<'r> FalseReader<'r> {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[0..1])
+ }
+ pub fn raw_data(&self) -> &'r [u8] {
+ self.as_slice()
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for FalseReader<'r> {
+ type Entity = False;
+ const NAME: &'static str = "FalseReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ FalseReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct FalseBuilder(pub(crate) [Byte; 1]);
+impl ::core::fmt::Debug for FalseBuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for FalseBuilder {
+ fn default() -> Self {
+ FalseBuilder([Byte::default()])
+ }
+}
+impl FalseBuilder {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn set(mut self, v: [Byte; 1]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Byte) -> Self {
+ self.0[0] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for FalseBuilder {
+ type Entity = False;
+ const NAME: &'static str = "FalseBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ False::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct Bool(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for Bool {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for Bool {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for Bool {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}(", Self::NAME)?;
+ self.to_enum().display_inner(f)?;
+ write!(f, ")")
+ }
+}
+impl ::core::default::Default for Bool {
+ fn default() -> Self {
+ let v: Vec = vec![0, 0, 0, 0, 0];
+ Bool::new_unchecked(v.into())
+ }
+}
+impl Bool {
+ pub const ITEMS_COUNT: usize = 2;
+ pub fn item_id(&self) -> molecule::Number {
+ molecule::unpack_number(self.as_slice())
+ }
+ pub fn to_enum(&self) -> BoolUnion {
+ let inner = self.0.slice(molecule::NUMBER_SIZE..);
+ match self.item_id() {
+ 0 => True::new_unchecked(inner).into(),
+ 1 => False::new_unchecked(inner).into(),
+ _ => panic!("{}: invalid data", Self::NAME),
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> BoolReader<'r> {
+ BoolReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for Bool {
+ type Builder = BoolBuilder;
+ const NAME: &'static str = "Bool";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ Bool(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ BoolReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ BoolReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set(self.to_enum())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct BoolReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for BoolReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for BoolReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for BoolReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}(", Self::NAME)?;
+ self.to_enum().display_inner(f)?;
+ write!(f, ")")
+ }
+}
+impl<'r> BoolReader<'r> {
+ pub const ITEMS_COUNT: usize = 2;
+ pub fn item_id(&self) -> molecule::Number {
+ molecule::unpack_number(self.as_slice())
+ }
+ pub fn to_enum(&self) -> BoolUnionReader<'r> {
+ let inner = &self.as_slice()[molecule::NUMBER_SIZE..];
+ match self.item_id() {
+ 0 => TrueReader::new_unchecked(inner).into(),
+ 1 => FalseReader::new_unchecked(inner).into(),
+ _ => panic!("{}: invalid data", Self::NAME),
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for BoolReader<'r> {
+ type Entity = Bool;
+ const NAME: &'static str = "BoolReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ BoolReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let item_id = molecule::unpack_number(slice);
+ let inner_slice = &slice[molecule::NUMBER_SIZE..];
+ match item_id {
+ 0 => TrueReader::verify(inner_slice, compatible),
+ 1 => FalseReader::verify(inner_slice, compatible),
+ _ => ve!(Self, UnknownItem, Self::ITEMS_COUNT, item_id),
+ }?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct BoolBuilder(pub(crate) BoolUnion);
+impl BoolBuilder {
+ pub const ITEMS_COUNT: usize = 2;
+ pub fn set(mut self, v: I) -> Self
+ where
+ I: ::core::convert::Into,
+ {
+ self.0 = v.into();
+ self
+ }
+}
+impl molecule::prelude::Builder for BoolBuilder {
+ type Entity = Bool;
+ const NAME: &'static str = "BoolBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE + self.0.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(&molecule::pack_number(self.0.item_id()))?;
+ writer.write_all(self.0.as_slice())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ Bool::new_unchecked(inner.into())
+ }
+}
+#[derive(Debug, Clone)]
+pub enum BoolUnion {
+ True(True),
+ False(False),
+}
+#[derive(Debug, Clone, Copy)]
+pub enum BoolUnionReader<'r> {
+ True(TrueReader<'r>),
+ False(FalseReader<'r>),
+}
+impl ::core::default::Default for BoolUnion {
+ fn default() -> Self {
+ BoolUnion::True(::core::default::Default::default())
+ }
+}
+impl ::core::fmt::Display for BoolUnion {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ match self {
+ BoolUnion::True(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, True::NAME, item)
+ }
+ BoolUnion::False(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, False::NAME, item)
+ }
+ }
+ }
+}
+impl<'r> ::core::fmt::Display for BoolUnionReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ match self {
+ BoolUnionReader::True(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, True::NAME, item)
+ }
+ BoolUnionReader::False(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, False::NAME, item)
+ }
+ }
+ }
+}
+impl BoolUnion {
+ pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ match self {
+ BoolUnion::True(ref item) => write!(f, "{}", item),
+ BoolUnion::False(ref item) => write!(f, "{}", item),
+ }
+ }
+}
+impl<'r> BoolUnionReader<'r> {
+ pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ match self {
+ BoolUnionReader::True(ref item) => write!(f, "{}", item),
+ BoolUnionReader::False(ref item) => write!(f, "{}", item),
+ }
+ }
+}
+impl ::core::convert::From for BoolUnion {
+ fn from(item: True) -> Self {
+ BoolUnion::True(item)
+ }
+}
+impl ::core::convert::From for BoolUnion {
+ fn from(item: False) -> Self {
+ BoolUnion::False(item)
+ }
+}
+impl<'r> ::core::convert::From> for BoolUnionReader<'r> {
+ fn from(item: TrueReader<'r>) -> Self {
+ BoolUnionReader::True(item)
+ }
+}
+impl<'r> ::core::convert::From> for BoolUnionReader<'r> {
+ fn from(item: FalseReader<'r>) -> Self {
+ BoolUnionReader::False(item)
+ }
+}
+impl BoolUnion {
+ pub const NAME: &'static str = "BoolUnion";
+ pub fn as_bytes(&self) -> molecule::bytes::Bytes {
+ match self {
+ BoolUnion::True(item) => item.as_bytes(),
+ BoolUnion::False(item) => item.as_bytes(),
+ }
+ }
+ pub fn as_slice(&self) -> &[u8] {
+ match self {
+ BoolUnion::True(item) => item.as_slice(),
+ BoolUnion::False(item) => item.as_slice(),
+ }
+ }
+ pub fn item_id(&self) -> molecule::Number {
+ match self {
+ BoolUnion::True(_) => 0,
+ BoolUnion::False(_) => 1,
+ }
+ }
+ pub fn item_name(&self) -> &str {
+ match self {
+ BoolUnion::True(_) => "True",
+ BoolUnion::False(_) => "False",
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> BoolUnionReader<'r> {
+ match self {
+ BoolUnion::True(item) => item.as_reader().into(),
+ BoolUnion::False(item) => item.as_reader().into(),
+ }
+ }
+}
+impl<'r> BoolUnionReader<'r> {
+ pub const NAME: &'r str = "BoolUnionReader";
+ pub fn as_slice(&self) -> &'r [u8] {
+ match self {
+ BoolUnionReader::True(item) => item.as_slice(),
+ BoolUnionReader::False(item) => item.as_slice(),
+ }
+ }
+ pub fn item_id(&self) -> molecule::Number {
+ match self {
+ BoolUnionReader::True(_) => 0,
+ BoolUnionReader::False(_) => 1,
+ }
+ }
+ pub fn item_name(&self) -> &str {
+ match self {
+ BoolUnionReader::True(_) => "True",
+ BoolUnionReader::False(_) => "False",
+ }
+ }
+}
+#[derive(Clone)]
+pub struct A(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for A {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for A {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for A {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl ::core::default::Default for A {
+ fn default() -> Self {
+ let v: Vec = vec![0];
+ A::new_unchecked(v.into())
+ }
+}
+impl A {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(0..1))
+ }
+ pub fn raw_data(&self) -> molecule::bytes::Bytes {
+ self.as_bytes()
+ }
+ pub fn as_reader<'r>(&'r self) -> AReader<'r> {
+ AReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for A {
+ type Builder = ABuilder;
+ const NAME: &'static str = "A";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ A(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ AReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ AReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([self.nth0()])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct AReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for AReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for AReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for AReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl<'r> AReader<'r> {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[0..1])
+ }
+ pub fn raw_data(&self) -> &'r [u8] {
+ self.as_slice()
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for AReader<'r> {
+ type Entity = A;
+ const NAME: &'static str = "AReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ AReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct ABuilder(pub(crate) [Byte; 1]);
+impl ::core::fmt::Debug for ABuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for ABuilder {
+ fn default() -> Self {
+ ABuilder([Byte::default()])
+ }
+}
+impl ABuilder {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn set(mut self, v: [Byte; 1]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Byte) -> Self {
+ self.0[0] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for ABuilder {
+ type Entity = A;
+ const NAME: &'static str = "ABuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ A::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct B(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for B {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for B {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for B {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl ::core::default::Default for B {
+ fn default() -> Self {
+ let v: Vec = vec![0];
+ B::new_unchecked(v.into())
+ }
+}
+impl B {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(0..1))
+ }
+ pub fn raw_data(&self) -> molecule::bytes::Bytes {
+ self.as_bytes()
+ }
+ pub fn as_reader<'r>(&'r self) -> BReader<'r> {
+ BReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for B {
+ type Builder = BBuilder;
+ const NAME: &'static str = "B";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ B(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ BReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ BReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([self.nth0()])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct BReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for BReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for BReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for BReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl<'r> BReader<'r> {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[0..1])
+ }
+ pub fn raw_data(&self) -> &'r [u8] {
+ self.as_slice()
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for BReader<'r> {
+ type Entity = B;
+ const NAME: &'static str = "BReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ BReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct BBuilder(pub(crate) [Byte; 1]);
+impl ::core::fmt::Debug for BBuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for BBuilder {
+ fn default() -> Self {
+ BBuilder([Byte::default()])
+ }
+}
+impl BBuilder {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn set(mut self, v: [Byte; 1]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Byte) -> Self {
+ self.0[0] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for BBuilder {
+ type Entity = B;
+ const NAME: &'static str = "BBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ B::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct App(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for App {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for App {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for App {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ if let Some(v) = self.to_opt() {
+ write!(f, "{}(Some({}))", Self::NAME, v)
+ } else {
+ write!(f, "{}(None)", Self::NAME)
+ }
+ }
+}
+impl ::core::default::Default for App {
+ fn default() -> Self {
+ let v: Vec = vec![];
+ App::new_unchecked(v.into())
+ }
+}
+impl App {
+ pub fn is_none(&self) -> bool {
+ self.0.is_empty()
+ }
+ pub fn is_some(&self) -> bool {
+ !self.0.is_empty()
+ }
+ pub fn to_opt(&self) -> Option {
+ if self.is_none() {
+ None
+ } else {
+ Some(Bytes::new_unchecked(self.0.clone()))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> AppReader<'r> {
+ AppReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for App {
+ type Builder = AppBuilder;
+ const NAME: &'static str = "App";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ App(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ AppReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ AppReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set(self.to_opt())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct AppReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for AppReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for AppReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for AppReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ if let Some(v) = self.to_opt() {
+ write!(f, "{}(Some({}))", Self::NAME, v)
+ } else {
+ write!(f, "{}(None)", Self::NAME)
+ }
+ }
+}
+impl<'r> AppReader<'r> {
+ pub fn is_none(&self) -> bool {
+ self.0.is_empty()
+ }
+ pub fn is_some(&self) -> bool {
+ !self.0.is_empty()
+ }
+ pub fn to_opt(&self) -> Option> {
+ if self.is_none() {
+ None
+ } else {
+ Some(BytesReader::new_unchecked(self.as_slice()))
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for AppReader<'r> {
+ type Entity = App;
+ const NAME: &'static str = "AppReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ AppReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ if !slice.is_empty() {
+ BytesReader::verify(&slice[..], compatible)?;
+ }
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct AppBuilder(pub(crate) Option);
+impl AppBuilder {
+ pub fn set(mut self, v: Option) -> Self {
+ self.0 = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for AppBuilder {
+ type Entity = App;
+ const NAME: &'static str = "AppBuilder";
+ fn expected_length(&self) -> usize {
+ self.0
+ .as_ref()
+ .map(|ref inner| inner.as_slice().len())
+ .unwrap_or(0)
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ self.0
+ .as_ref()
+ .map(|ref inner| writer.write_all(inner.as_slice()))
+ .unwrap_or(Ok(()))
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ App::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct Participant(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for Participant {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for Participant {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for Participant {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(
+ f,
+ "{}: {}",
+ "payment_script_hash",
+ self.payment_script_hash()
+ )?;
+ write!(
+ f,
+ ", {}: {}",
+ "payment_min_capacity",
+ self.payment_min_capacity()
+ )?;
+ write!(
+ f,
+ ", {}: {}",
+ "unlock_script_hash",
+ self.unlock_script_hash()
+ )?;
+ write!(f, ", {}: {}", "pub_key", self.pub_key())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for Participant {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 125, 0, 0, 0, 20, 0, 0, 0, 52, 0, 0, 0, 60, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ];
+ Participant::new_unchecked(v.into())
+ }
+}
+impl Participant {
+ pub const FIELD_COUNT: usize = 4;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn payment_script_hash(&self) -> Byte32 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ Byte32::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn payment_min_capacity(&self) -> Uint64 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Uint64::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn unlock_script_hash(&self) -> Byte32 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ Byte32::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn pub_key(&self) -> SEC1EncodedPubKey {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[16..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[20..]) as usize;
+ SEC1EncodedPubKey::new_unchecked(self.0.slice(start..end))
+ } else {
+ SEC1EncodedPubKey::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> ParticipantReader<'r> {
+ ParticipantReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for Participant {
+ type Builder = ParticipantBuilder;
+ const NAME: &'static str = "Participant";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ Participant(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ParticipantReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ParticipantReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder()
+ .payment_script_hash(self.payment_script_hash())
+ .payment_min_capacity(self.payment_min_capacity())
+ .unlock_script_hash(self.unlock_script_hash())
+ .pub_key(self.pub_key())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct ParticipantReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for ParticipantReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for ParticipantReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for ParticipantReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(
+ f,
+ "{}: {}",
+ "payment_script_hash",
+ self.payment_script_hash()
+ )?;
+ write!(
+ f,
+ ", {}: {}",
+ "payment_min_capacity",
+ self.payment_min_capacity()
+ )?;
+ write!(
+ f,
+ ", {}: {}",
+ "unlock_script_hash",
+ self.unlock_script_hash()
+ )?;
+ write!(f, ", {}: {}", "pub_key", self.pub_key())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> ParticipantReader<'r> {
+ pub const FIELD_COUNT: usize = 4;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn payment_script_hash(&self) -> Byte32Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ Byte32Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn payment_min_capacity(&self) -> Uint64Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Uint64Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn unlock_script_hash(&self) -> Byte32Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ Byte32Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn pub_key(&self) -> SEC1EncodedPubKeyReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[16..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[20..]) as usize;
+ SEC1EncodedPubKeyReader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ SEC1EncodedPubKeyReader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for ParticipantReader<'r> {
+ type Entity = Participant;
+ const NAME: &'static str = "ParticipantReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ ParticipantReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ Byte32Reader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ Uint64Reader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ Byte32Reader::verify(&slice[offsets[2]..offsets[3]], compatible)?;
+ SEC1EncodedPubKeyReader::verify(&slice[offsets[3]..offsets[4]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct ParticipantBuilder {
+ pub(crate) payment_script_hash: Byte32,
+ pub(crate) payment_min_capacity: Uint64,
+ pub(crate) unlock_script_hash: Byte32,
+ pub(crate) pub_key: SEC1EncodedPubKey,
+}
+impl ParticipantBuilder {
+ pub const FIELD_COUNT: usize = 4;
+ pub fn payment_script_hash(mut self, v: Byte32) -> Self {
+ self.payment_script_hash = v;
+ self
+ }
+ pub fn payment_min_capacity(mut self, v: Uint64) -> Self {
+ self.payment_min_capacity = v;
+ self
+ }
+ pub fn unlock_script_hash(mut self, v: Byte32) -> Self {
+ self.unlock_script_hash = v;
+ self
+ }
+ pub fn pub_key(mut self, v: SEC1EncodedPubKey) -> Self {
+ self.pub_key = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for ParticipantBuilder {
+ type Entity = Participant;
+ const NAME: &'static str = "ParticipantBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.payment_script_hash.as_slice().len()
+ + self.payment_min_capacity.as_slice().len()
+ + self.unlock_script_hash.as_slice().len()
+ + self.pub_key.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.payment_script_hash.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.payment_min_capacity.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.unlock_script_hash.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.pub_key.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.payment_script_hash.as_slice())?;
+ writer.write_all(self.payment_min_capacity.as_slice())?;
+ writer.write_all(self.unlock_script_hash.as_slice())?;
+ writer.write_all(self.pub_key.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ Participant::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct ChannelParameters(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for ChannelParameters {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for ChannelParameters {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for ChannelParameters {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "party_a", self.party_a())?;
+ write!(f, ", {}: {}", "party_b", self.party_b())?;
+ write!(f, ", {}: {}", "nonce", self.nonce())?;
+ write!(
+ f,
+ ", {}: {}",
+ "challenge_duration",
+ self.challenge_duration()
+ )?;
+ write!(f, ", {}: {}", "app", self.app())?;
+ write!(f, ", {}: {}", "is_ledger_channel", self.is_ledger_channel())?;
+ write!(
+ f,
+ ", {}: {}",
+ "is_virtual_channel",
+ self.is_virtual_channel()
+ )?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for ChannelParameters {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 76, 1, 0, 0, 32, 0, 0, 0, 157, 0, 0, 0, 26, 1, 0, 0, 58, 1, 0, 0, 66, 1, 0, 0, 66, 1,
+ 0, 0, 71, 1, 0, 0, 125, 0, 0, 0, 20, 0, 0, 0, 52, 0, 0, 0, 60, 0, 0, 0, 92, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 20, 0, 0, 0, 52, 0, 0,
+ 0, 60, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ];
+ ChannelParameters::new_unchecked(v.into())
+ }
+}
+impl ChannelParameters {
+ pub const FIELD_COUNT: usize = 7;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn party_a(&self) -> Participant {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ Participant::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn party_b(&self) -> Participant {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Participant::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn nonce(&self) -> Byte32 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ Byte32::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn challenge_duration(&self) -> Uint64 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[16..]) as usize;
+ let end = molecule::unpack_number(&slice[20..]) as usize;
+ Uint64::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn app(&self) -> App {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[20..]) as usize;
+ let end = molecule::unpack_number(&slice[24..]) as usize;
+ App::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn is_ledger_channel(&self) -> Bool {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[24..]) as usize;
+ let end = molecule::unpack_number(&slice[28..]) as usize;
+ Bool::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn is_virtual_channel(&self) -> Bool {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[28..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[32..]) as usize;
+ Bool::new_unchecked(self.0.slice(start..end))
+ } else {
+ Bool::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> ChannelParametersReader<'r> {
+ ChannelParametersReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for ChannelParameters {
+ type Builder = ChannelParametersBuilder;
+ const NAME: &'static str = "ChannelParameters";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ ChannelParameters(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelParametersReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelParametersReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder()
+ .party_a(self.party_a())
+ .party_b(self.party_b())
+ .nonce(self.nonce())
+ .challenge_duration(self.challenge_duration())
+ .app(self.app())
+ .is_ledger_channel(self.is_ledger_channel())
+ .is_virtual_channel(self.is_virtual_channel())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct ChannelParametersReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for ChannelParametersReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for ChannelParametersReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for ChannelParametersReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "party_a", self.party_a())?;
+ write!(f, ", {}: {}", "party_b", self.party_b())?;
+ write!(f, ", {}: {}", "nonce", self.nonce())?;
+ write!(
+ f,
+ ", {}: {}",
+ "challenge_duration",
+ self.challenge_duration()
+ )?;
+ write!(f, ", {}: {}", "app", self.app())?;
+ write!(f, ", {}: {}", "is_ledger_channel", self.is_ledger_channel())?;
+ write!(
+ f,
+ ", {}: {}",
+ "is_virtual_channel",
+ self.is_virtual_channel()
+ )?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> ChannelParametersReader<'r> {
+ pub const FIELD_COUNT: usize = 7;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn party_a(&self) -> ParticipantReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ ParticipantReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn party_b(&self) -> ParticipantReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ ParticipantReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn nonce(&self) -> Byte32Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ Byte32Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn challenge_duration(&self) -> Uint64Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[16..]) as usize;
+ let end = molecule::unpack_number(&slice[20..]) as usize;
+ Uint64Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn app(&self) -> AppReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[20..]) as usize;
+ let end = molecule::unpack_number(&slice[24..]) as usize;
+ AppReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn is_ledger_channel(&self) -> BoolReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[24..]) as usize;
+ let end = molecule::unpack_number(&slice[28..]) as usize;
+ BoolReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn is_virtual_channel(&self) -> BoolReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[28..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[32..]) as usize;
+ BoolReader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ BoolReader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for ChannelParametersReader<'r> {
+ type Entity = ChannelParameters;
+ const NAME: &'static str = "ChannelParametersReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ ChannelParametersReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ ParticipantReader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ ParticipantReader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ Byte32Reader::verify(&slice[offsets[2]..offsets[3]], compatible)?;
+ Uint64Reader::verify(&slice[offsets[3]..offsets[4]], compatible)?;
+ AppReader::verify(&slice[offsets[4]..offsets[5]], compatible)?;
+ BoolReader::verify(&slice[offsets[5]..offsets[6]], compatible)?;
+ BoolReader::verify(&slice[offsets[6]..offsets[7]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct ChannelParametersBuilder {
+ pub(crate) party_a: Participant,
+ pub(crate) party_b: Participant,
+ pub(crate) nonce: Byte32,
+ pub(crate) challenge_duration: Uint64,
+ pub(crate) app: App,
+ pub(crate) is_ledger_channel: Bool,
+ pub(crate) is_virtual_channel: Bool,
+}
+impl ChannelParametersBuilder {
+ pub const FIELD_COUNT: usize = 7;
+ pub fn party_a(mut self, v: Participant) -> Self {
+ self.party_a = v;
+ self
+ }
+ pub fn party_b(mut self, v: Participant) -> Self {
+ self.party_b = v;
+ self
+ }
+ pub fn nonce(mut self, v: Byte32) -> Self {
+ self.nonce = v;
+ self
+ }
+ pub fn challenge_duration(mut self, v: Uint64) -> Self {
+ self.challenge_duration = v;
+ self
+ }
+ pub fn app(mut self, v: App) -> Self {
+ self.app = v;
+ self
+ }
+ pub fn is_ledger_channel(mut self, v: Bool) -> Self {
+ self.is_ledger_channel = v;
+ self
+ }
+ pub fn is_virtual_channel(mut self, v: Bool) -> Self {
+ self.is_virtual_channel = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for ChannelParametersBuilder {
+ type Entity = ChannelParameters;
+ const NAME: &'static str = "ChannelParametersBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.party_a.as_slice().len()
+ + self.party_b.as_slice().len()
+ + self.nonce.as_slice().len()
+ + self.challenge_duration.as_slice().len()
+ + self.app.as_slice().len()
+ + self.is_ledger_channel.as_slice().len()
+ + self.is_virtual_channel.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.party_a.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.party_b.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.nonce.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.challenge_duration.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.app.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.is_ledger_channel.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.is_virtual_channel.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.party_a.as_slice())?;
+ writer.write_all(self.party_b.as_slice())?;
+ writer.write_all(self.nonce.as_slice())?;
+ writer.write_all(self.challenge_duration.as_slice())?;
+ writer.write_all(self.app.as_slice())?;
+ writer.write_all(self.is_ledger_channel.as_slice())?;
+ writer.write_all(self.is_virtual_channel.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ ChannelParameters::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct ChannelConstants(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for ChannelConstants {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for ChannelConstants {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for ChannelConstants {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "params", self.params())?;
+ write!(f, ", {}: {}", "pfls_code_hash", self.pfls_code_hash())?;
+ write!(f, ", {}: {}", "pfls_hash_type", self.pfls_hash_type())?;
+ write!(f, ", {}: {}", "pfls_min_capacity", self.pfls_min_capacity())?;
+ write!(f, ", {}: {}", "pcls_code_hash", self.pcls_code_hash())?;
+ write!(f, ", {}: {}", "pcls_hash_type", self.pcls_hash_type())?;
+ write!(f, ", {}: {}", "thread_token", self.thread_token())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for ChannelConstants {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 218, 1, 0, 0, 32, 0, 0, 0, 108, 1, 0, 0, 140, 1, 0, 0, 141, 1, 0, 0, 149, 1, 0, 0, 181,
+ 1, 0, 0, 182, 1, 0, 0, 76, 1, 0, 0, 32, 0, 0, 0, 157, 0, 0, 0, 26, 1, 0, 0, 58, 1, 0,
+ 0, 66, 1, 0, 0, 66, 1, 0, 0, 71, 1, 0, 0, 125, 0, 0, 0, 20, 0, 0, 0, 52, 0, 0, 0, 60,
+ 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0,
+ 0, 20, 0, 0, 0, 52, 0, 0, 0, 60, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ];
+ ChannelConstants::new_unchecked(v.into())
+ }
+}
+impl ChannelConstants {
+ pub const FIELD_COUNT: usize = 7;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn params(&self) -> ChannelParameters {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ ChannelParameters::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn pfls_code_hash(&self) -> Byte32 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Byte32::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn pfls_hash_type(&self) -> Byte {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ Byte::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn pfls_min_capacity(&self) -> Uint64 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[16..]) as usize;
+ let end = molecule::unpack_number(&slice[20..]) as usize;
+ Uint64::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn pcls_code_hash(&self) -> Byte32 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[20..]) as usize;
+ let end = molecule::unpack_number(&slice[24..]) as usize;
+ Byte32::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn pcls_hash_type(&self) -> Byte {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[24..]) as usize;
+ let end = molecule::unpack_number(&slice[28..]) as usize;
+ Byte::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn thread_token(&self) -> ChannelToken {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[28..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[32..]) as usize;
+ ChannelToken::new_unchecked(self.0.slice(start..end))
+ } else {
+ ChannelToken::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> ChannelConstantsReader<'r> {
+ ChannelConstantsReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for ChannelConstants {
+ type Builder = ChannelConstantsBuilder;
+ const NAME: &'static str = "ChannelConstants";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ ChannelConstants(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelConstantsReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelConstantsReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder()
+ .params(self.params())
+ .pfls_code_hash(self.pfls_code_hash())
+ .pfls_hash_type(self.pfls_hash_type())
+ .pfls_min_capacity(self.pfls_min_capacity())
+ .pcls_code_hash(self.pcls_code_hash())
+ .pcls_hash_type(self.pcls_hash_type())
+ .thread_token(self.thread_token())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct ChannelConstantsReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for ChannelConstantsReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for ChannelConstantsReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for ChannelConstantsReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "params", self.params())?;
+ write!(f, ", {}: {}", "pfls_code_hash", self.pfls_code_hash())?;
+ write!(f, ", {}: {}", "pfls_hash_type", self.pfls_hash_type())?;
+ write!(f, ", {}: {}", "pfls_min_capacity", self.pfls_min_capacity())?;
+ write!(f, ", {}: {}", "pcls_code_hash", self.pcls_code_hash())?;
+ write!(f, ", {}: {}", "pcls_hash_type", self.pcls_hash_type())?;
+ write!(f, ", {}: {}", "thread_token", self.thread_token())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> ChannelConstantsReader<'r> {
+ pub const FIELD_COUNT: usize = 7;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn params(&self) -> ChannelParametersReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ ChannelParametersReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn pfls_code_hash(&self) -> Byte32Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Byte32Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn pfls_hash_type(&self) -> ByteReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ ByteReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn pfls_min_capacity(&self) -> Uint64Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[16..]) as usize;
+ let end = molecule::unpack_number(&slice[20..]) as usize;
+ Uint64Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn pcls_code_hash(&self) -> Byte32Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[20..]) as usize;
+ let end = molecule::unpack_number(&slice[24..]) as usize;
+ Byte32Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn pcls_hash_type(&self) -> ByteReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[24..]) as usize;
+ let end = molecule::unpack_number(&slice[28..]) as usize;
+ ByteReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn thread_token(&self) -> ChannelTokenReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[28..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[32..]) as usize;
+ ChannelTokenReader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ ChannelTokenReader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for ChannelConstantsReader<'r> {
+ type Entity = ChannelConstants;
+ const NAME: &'static str = "ChannelConstantsReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ ChannelConstantsReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ ChannelParametersReader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ Byte32Reader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ ByteReader::verify(&slice[offsets[2]..offsets[3]], compatible)?;
+ Uint64Reader::verify(&slice[offsets[3]..offsets[4]], compatible)?;
+ Byte32Reader::verify(&slice[offsets[4]..offsets[5]], compatible)?;
+ ByteReader::verify(&slice[offsets[5]..offsets[6]], compatible)?;
+ ChannelTokenReader::verify(&slice[offsets[6]..offsets[7]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct ChannelConstantsBuilder {
+ pub(crate) params: ChannelParameters,
+ pub(crate) pfls_code_hash: Byte32,
+ pub(crate) pfls_hash_type: Byte,
+ pub(crate) pfls_min_capacity: Uint64,
+ pub(crate) pcls_code_hash: Byte32,
+ pub(crate) pcls_hash_type: Byte,
+ pub(crate) thread_token: ChannelToken,
+}
+impl ChannelConstantsBuilder {
+ pub const FIELD_COUNT: usize = 7;
+ pub fn params(mut self, v: ChannelParameters) -> Self {
+ self.params = v;
+ self
+ }
+ pub fn pfls_code_hash(mut self, v: Byte32) -> Self {
+ self.pfls_code_hash = v;
+ self
+ }
+ pub fn pfls_hash_type(mut self, v: Byte) -> Self {
+ self.pfls_hash_type = v;
+ self
+ }
+ pub fn pfls_min_capacity(mut self, v: Uint64) -> Self {
+ self.pfls_min_capacity = v;
+ self
+ }
+ pub fn pcls_code_hash(mut self, v: Byte32) -> Self {
+ self.pcls_code_hash = v;
+ self
+ }
+ pub fn pcls_hash_type(mut self, v: Byte) -> Self {
+ self.pcls_hash_type = v;
+ self
+ }
+ pub fn thread_token(mut self, v: ChannelToken) -> Self {
+ self.thread_token = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for ChannelConstantsBuilder {
+ type Entity = ChannelConstants;
+ const NAME: &'static str = "ChannelConstantsBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.params.as_slice().len()
+ + self.pfls_code_hash.as_slice().len()
+ + self.pfls_hash_type.as_slice().len()
+ + self.pfls_min_capacity.as_slice().len()
+ + self.pcls_code_hash.as_slice().len()
+ + self.pcls_hash_type.as_slice().len()
+ + self.thread_token.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.params.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.pfls_code_hash.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.pfls_hash_type.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.pfls_min_capacity.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.pcls_code_hash.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.pcls_hash_type.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.thread_token.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.params.as_slice())?;
+ writer.write_all(self.pfls_code_hash.as_slice())?;
+ writer.write_all(self.pfls_hash_type.as_slice())?;
+ writer.write_all(self.pfls_min_capacity.as_slice())?;
+ writer.write_all(self.pcls_code_hash.as_slice())?;
+ writer.write_all(self.pcls_hash_type.as_slice())?;
+ writer.write_all(self.thread_token.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ ChannelConstants::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct Fund(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for Fund {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for Fund {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for Fund {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl ::core::default::Default for Fund {
+ fn default() -> Self {
+ let v: Vec = vec![0];
+ Fund::new_unchecked(v.into())
+ }
+}
+impl Fund {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(0..1))
+ }
+ pub fn raw_data(&self) -> molecule::bytes::Bytes {
+ self.as_bytes()
+ }
+ pub fn as_reader<'r>(&'r self) -> FundReader<'r> {
+ FundReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for Fund {
+ type Builder = FundBuilder;
+ const NAME: &'static str = "Fund";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ Fund(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ FundReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ FundReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([self.nth0()])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct FundReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for FundReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for FundReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for FundReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl<'r> FundReader<'r> {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[0..1])
+ }
+ pub fn raw_data(&self) -> &'r [u8] {
+ self.as_slice()
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for FundReader<'r> {
+ type Entity = Fund;
+ const NAME: &'static str = "FundReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ FundReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct FundBuilder(pub(crate) [Byte; 1]);
+impl ::core::fmt::Debug for FundBuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for FundBuilder {
+ fn default() -> Self {
+ FundBuilder([Byte::default()])
+ }
+}
+impl FundBuilder {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn set(mut self, v: [Byte; 1]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Byte) -> Self {
+ self.0[0] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for FundBuilder {
+ type Entity = Fund;
+ const NAME: &'static str = "FundBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ Fund::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct Abort(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for Abort {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for Abort {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for Abort {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl ::core::default::Default for Abort {
+ fn default() -> Self {
+ let v: Vec = vec![0];
+ Abort::new_unchecked(v.into())
+ }
+}
+impl Abort {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(0..1))
+ }
+ pub fn raw_data(&self) -> molecule::bytes::Bytes {
+ self.as_bytes()
+ }
+ pub fn as_reader<'r>(&'r self) -> AbortReader<'r> {
+ AbortReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for Abort {
+ type Builder = AbortBuilder;
+ const NAME: &'static str = "Abort";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ Abort(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ AbortReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ AbortReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([self.nth0()])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct AbortReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for AbortReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for AbortReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for AbortReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl<'r> AbortReader<'r> {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[0..1])
+ }
+ pub fn raw_data(&self) -> &'r [u8] {
+ self.as_slice()
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for AbortReader<'r> {
+ type Entity = Abort;
+ const NAME: &'static str = "AbortReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ AbortReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct AbortBuilder(pub(crate) [Byte; 1]);
+impl ::core::fmt::Debug for AbortBuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for AbortBuilder {
+ fn default() -> Self {
+ AbortBuilder([Byte::default()])
+ }
+}
+impl AbortBuilder {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn set(mut self, v: [Byte; 1]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Byte) -> Self {
+ self.0[0] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for AbortBuilder {
+ type Entity = Abort;
+ const NAME: &'static str = "AbortBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ Abort::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct Dispute(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for Dispute {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for Dispute {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for Dispute {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "sig_a", self.sig_a())?;
+ write!(f, ", {}: {}", "sig_b", self.sig_b())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for Dispute {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 20, 0, 0, 0, 12, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ];
+ Dispute::new_unchecked(v.into())
+ }
+}
+impl Dispute {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn sig_a(&self) -> Bytes {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ Bytes::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn sig_b(&self) -> Bytes {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Bytes::new_unchecked(self.0.slice(start..end))
+ } else {
+ Bytes::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> DisputeReader<'r> {
+ DisputeReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for Dispute {
+ type Builder = DisputeBuilder;
+ const NAME: &'static str = "Dispute";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ Dispute(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ DisputeReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ DisputeReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().sig_a(self.sig_a()).sig_b(self.sig_b())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct DisputeReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for DisputeReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for DisputeReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for DisputeReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "sig_a", self.sig_a())?;
+ write!(f, ", {}: {}", "sig_b", self.sig_b())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> DisputeReader<'r> {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn sig_a(&self) -> BytesReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ BytesReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn sig_b(&self) -> BytesReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ BytesReader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ BytesReader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for DisputeReader<'r> {
+ type Entity = Dispute;
+ const NAME: &'static str = "DisputeReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ DisputeReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ BytesReader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ BytesReader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct DisputeBuilder {
+ pub(crate) sig_a: Bytes,
+ pub(crate) sig_b: Bytes,
+}
+impl DisputeBuilder {
+ pub const FIELD_COUNT: usize = 2;
+ pub fn sig_a(mut self, v: Bytes) -> Self {
+ self.sig_a = v;
+ self
+ }
+ pub fn sig_b(mut self, v: Bytes) -> Self {
+ self.sig_b = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for DisputeBuilder {
+ type Entity = Dispute;
+ const NAME: &'static str = "DisputeBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.sig_a.as_slice().len()
+ + self.sig_b.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.sig_a.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.sig_b.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.sig_a.as_slice())?;
+ writer.write_all(self.sig_b.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ Dispute::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct Close(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for Close {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for Close {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for Close {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "state", self.state())?;
+ write!(f, ", {}: {}", "sig_a", self.sig_a())?;
+ write!(f, ", {}: {}", "sig_b", self.sig_b())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for Close {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 121, 0, 0, 0, 16, 0, 0, 0, 113, 0, 0, 0, 117, 0, 0, 0, 97, 0, 0, 0, 20, 0, 0, 0, 52, 0,
+ 0, 0, 84, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 12, 0, 0, 0, 28, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ];
+ Close::new_unchecked(v.into())
+ }
+}
+impl Close {
+ pub const FIELD_COUNT: usize = 3;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn state(&self) -> ChannelState {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ ChannelState::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn sig_a(&self) -> Bytes {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Bytes::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn sig_b(&self) -> Bytes {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ Bytes::new_unchecked(self.0.slice(start..end))
+ } else {
+ Bytes::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> CloseReader<'r> {
+ CloseReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for Close {
+ type Builder = CloseBuilder;
+ const NAME: &'static str = "Close";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ Close(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ CloseReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ CloseReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder()
+ .state(self.state())
+ .sig_a(self.sig_a())
+ .sig_b(self.sig_b())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct CloseReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for CloseReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for CloseReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for CloseReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "state", self.state())?;
+ write!(f, ", {}: {}", "sig_a", self.sig_a())?;
+ write!(f, ", {}: {}", "sig_b", self.sig_b())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> CloseReader<'r> {
+ pub const FIELD_COUNT: usize = 3;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn state(&self) -> ChannelStateReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ ChannelStateReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn sig_a(&self) -> BytesReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ BytesReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn sig_b(&self) -> BytesReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ BytesReader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ BytesReader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for CloseReader<'r> {
+ type Entity = Close;
+ const NAME: &'static str = "CloseReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ CloseReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ ChannelStateReader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ BytesReader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ BytesReader::verify(&slice[offsets[2]..offsets[3]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct CloseBuilder {
+ pub(crate) state: ChannelState,
+ pub(crate) sig_a: Bytes,
+ pub(crate) sig_b: Bytes,
+}
+impl CloseBuilder {
+ pub const FIELD_COUNT: usize = 3;
+ pub fn state(mut self, v: ChannelState) -> Self {
+ self.state = v;
+ self
+ }
+ pub fn sig_a(mut self, v: Bytes) -> Self {
+ self.sig_a = v;
+ self
+ }
+ pub fn sig_b(mut self, v: Bytes) -> Self {
+ self.sig_b = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for CloseBuilder {
+ type Entity = Close;
+ const NAME: &'static str = "CloseBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.state.as_slice().len()
+ + self.sig_a.as_slice().len()
+ + self.sig_b.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.state.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.sig_a.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.sig_b.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.state.as_slice())?;
+ writer.write_all(self.sig_a.as_slice())?;
+ writer.write_all(self.sig_b.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ Close::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct ForceClose(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for ForceClose {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for ForceClose {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for ForceClose {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl ::core::default::Default for ForceClose {
+ fn default() -> Self {
+ let v: Vec = vec![0];
+ ForceClose::new_unchecked(v.into())
+ }
+}
+impl ForceClose {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> Byte {
+ Byte::new_unchecked(self.0.slice(0..1))
+ }
+ pub fn raw_data(&self) -> molecule::bytes::Bytes {
+ self.as_bytes()
+ }
+ pub fn as_reader<'r>(&'r self) -> ForceCloseReader<'r> {
+ ForceCloseReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for ForceClose {
+ type Builder = ForceCloseBuilder;
+ const NAME: &'static str = "ForceClose";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ ForceClose(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ForceCloseReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ForceCloseReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set([self.nth0()])
+ }
+}
+#[derive(Clone, Copy)]
+pub struct ForceCloseReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for ForceCloseReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for ForceCloseReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for ForceCloseReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ let raw_data = hex_string(&self.raw_data());
+ write!(f, "{}(0x{})", Self::NAME, raw_data)
+ }
+}
+impl<'r> ForceCloseReader<'r> {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn nth0(&self) -> ByteReader<'r> {
+ ByteReader::new_unchecked(&self.as_slice()[0..1])
+ }
+ pub fn raw_data(&self) -> &'r [u8] {
+ self.as_slice()
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for ForceCloseReader<'r> {
+ type Entity = ForceClose;
+ const NAME: &'static str = "ForceCloseReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ ForceCloseReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+pub struct ForceCloseBuilder(pub(crate) [Byte; 1]);
+impl ::core::fmt::Debug for ForceCloseBuilder {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:?})", Self::NAME, &self.0[..])
+ }
+}
+impl ::core::default::Default for ForceCloseBuilder {
+ fn default() -> Self {
+ ForceCloseBuilder([Byte::default()])
+ }
+}
+impl ForceCloseBuilder {
+ pub const TOTAL_SIZE: usize = 1;
+ pub const ITEM_SIZE: usize = 1;
+ pub const ITEM_COUNT: usize = 1;
+ pub fn set(mut self, v: [Byte; 1]) -> Self {
+ self.0 = v;
+ self
+ }
+ pub fn nth0(mut self, v: Byte) -> Self {
+ self.0[0] = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for ForceCloseBuilder {
+ type Entity = ForceClose;
+ const NAME: &'static str = "ForceCloseBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.0[0].as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ ForceClose::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct ChannelWitness(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for ChannelWitness {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for ChannelWitness {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for ChannelWitness {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}(", Self::NAME)?;
+ self.to_enum().display_inner(f)?;
+ write!(f, ")")
+ }
+}
+impl ::core::default::Default for ChannelWitness {
+ fn default() -> Self {
+ let v: Vec = vec![0, 0, 0, 0, 0];
+ ChannelWitness::new_unchecked(v.into())
+ }
+}
+impl ChannelWitness {
+ pub const ITEMS_COUNT: usize = 5;
+ pub fn item_id(&self) -> molecule::Number {
+ molecule::unpack_number(self.as_slice())
+ }
+ pub fn to_enum(&self) -> ChannelWitnessUnion {
+ let inner = self.0.slice(molecule::NUMBER_SIZE..);
+ match self.item_id() {
+ 0 => Fund::new_unchecked(inner).into(),
+ 1 => Abort::new_unchecked(inner).into(),
+ 2 => Dispute::new_unchecked(inner).into(),
+ 3 => Close::new_unchecked(inner).into(),
+ 4 => ForceClose::new_unchecked(inner).into(),
+ _ => panic!("{}: invalid data", Self::NAME),
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> ChannelWitnessReader<'r> {
+ ChannelWitnessReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for ChannelWitness {
+ type Builder = ChannelWitnessBuilder;
+ const NAME: &'static str = "ChannelWitness";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ ChannelWitness(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelWitnessReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelWitnessReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().set(self.to_enum())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct ChannelWitnessReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for ChannelWitnessReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for ChannelWitnessReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for ChannelWitnessReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}(", Self::NAME)?;
+ self.to_enum().display_inner(f)?;
+ write!(f, ")")
+ }
+}
+impl<'r> ChannelWitnessReader<'r> {
+ pub const ITEMS_COUNT: usize = 5;
+ pub fn item_id(&self) -> molecule::Number {
+ molecule::unpack_number(self.as_slice())
+ }
+ pub fn to_enum(&self) -> ChannelWitnessUnionReader<'r> {
+ let inner = &self.as_slice()[molecule::NUMBER_SIZE..];
+ match self.item_id() {
+ 0 => FundReader::new_unchecked(inner).into(),
+ 1 => AbortReader::new_unchecked(inner).into(),
+ 2 => DisputeReader::new_unchecked(inner).into(),
+ 3 => CloseReader::new_unchecked(inner).into(),
+ 4 => ForceCloseReader::new_unchecked(inner).into(),
+ _ => panic!("{}: invalid data", Self::NAME),
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for ChannelWitnessReader<'r> {
+ type Entity = ChannelWitness;
+ const NAME: &'static str = "ChannelWitnessReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ ChannelWitnessReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let item_id = molecule::unpack_number(slice);
+ let inner_slice = &slice[molecule::NUMBER_SIZE..];
+ match item_id {
+ 0 => FundReader::verify(inner_slice, compatible),
+ 1 => AbortReader::verify(inner_slice, compatible),
+ 2 => DisputeReader::verify(inner_slice, compatible),
+ 3 => CloseReader::verify(inner_slice, compatible),
+ 4 => ForceCloseReader::verify(inner_slice, compatible),
+ _ => ve!(Self, UnknownItem, Self::ITEMS_COUNT, item_id),
+ }?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct ChannelWitnessBuilder(pub(crate) ChannelWitnessUnion);
+impl ChannelWitnessBuilder {
+ pub const ITEMS_COUNT: usize = 5;
+ pub fn set(mut self, v: I) -> Self
+ where
+ I: ::core::convert::Into,
+ {
+ self.0 = v.into();
+ self
+ }
+}
+impl molecule::prelude::Builder for ChannelWitnessBuilder {
+ type Entity = ChannelWitness;
+ const NAME: &'static str = "ChannelWitnessBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE + self.0.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(&molecule::pack_number(self.0.item_id()))?;
+ writer.write_all(self.0.as_slice())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ ChannelWitness::new_unchecked(inner.into())
+ }
+}
+#[derive(Debug, Clone)]
+pub enum ChannelWitnessUnion {
+ Fund(Fund),
+ Abort(Abort),
+ Dispute(Dispute),
+ Close(Close),
+ ForceClose(ForceClose),
+}
+#[derive(Debug, Clone, Copy)]
+pub enum ChannelWitnessUnionReader<'r> {
+ Fund(FundReader<'r>),
+ Abort(AbortReader<'r>),
+ Dispute(DisputeReader<'r>),
+ Close(CloseReader<'r>),
+ ForceClose(ForceCloseReader<'r>),
+}
+impl ::core::default::Default for ChannelWitnessUnion {
+ fn default() -> Self {
+ ChannelWitnessUnion::Fund(::core::default::Default::default())
+ }
+}
+impl ::core::fmt::Display for ChannelWitnessUnion {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ match self {
+ ChannelWitnessUnion::Fund(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, Fund::NAME, item)
+ }
+ ChannelWitnessUnion::Abort(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, Abort::NAME, item)
+ }
+ ChannelWitnessUnion::Dispute(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, Dispute::NAME, item)
+ }
+ ChannelWitnessUnion::Close(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, Close::NAME, item)
+ }
+ ChannelWitnessUnion::ForceClose(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, ForceClose::NAME, item)
+ }
+ }
+ }
+}
+impl<'r> ::core::fmt::Display for ChannelWitnessUnionReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ match self {
+ ChannelWitnessUnionReader::Fund(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, Fund::NAME, item)
+ }
+ ChannelWitnessUnionReader::Abort(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, Abort::NAME, item)
+ }
+ ChannelWitnessUnionReader::Dispute(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, Dispute::NAME, item)
+ }
+ ChannelWitnessUnionReader::Close(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, Close::NAME, item)
+ }
+ ChannelWitnessUnionReader::ForceClose(ref item) => {
+ write!(f, "{}::{}({})", Self::NAME, ForceClose::NAME, item)
+ }
+ }
+ }
+}
+impl ChannelWitnessUnion {
+ pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ match self {
+ ChannelWitnessUnion::Fund(ref item) => write!(f, "{}", item),
+ ChannelWitnessUnion::Abort(ref item) => write!(f, "{}", item),
+ ChannelWitnessUnion::Dispute(ref item) => write!(f, "{}", item),
+ ChannelWitnessUnion::Close(ref item) => write!(f, "{}", item),
+ ChannelWitnessUnion::ForceClose(ref item) => write!(f, "{}", item),
+ }
+ }
+}
+impl<'r> ChannelWitnessUnionReader<'r> {
+ pub(crate) fn display_inner(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ match self {
+ ChannelWitnessUnionReader::Fund(ref item) => write!(f, "{}", item),
+ ChannelWitnessUnionReader::Abort(ref item) => write!(f, "{}", item),
+ ChannelWitnessUnionReader::Dispute(ref item) => write!(f, "{}", item),
+ ChannelWitnessUnionReader::Close(ref item) => write!(f, "{}", item),
+ ChannelWitnessUnionReader::ForceClose(ref item) => write!(f, "{}", item),
+ }
+ }
+}
+impl ::core::convert::From for ChannelWitnessUnion {
+ fn from(item: Fund) -> Self {
+ ChannelWitnessUnion::Fund(item)
+ }
+}
+impl ::core::convert::From for ChannelWitnessUnion {
+ fn from(item: Abort) -> Self {
+ ChannelWitnessUnion::Abort(item)
+ }
+}
+impl ::core::convert::From for ChannelWitnessUnion {
+ fn from(item: Dispute) -> Self {
+ ChannelWitnessUnion::Dispute(item)
+ }
+}
+impl ::core::convert::From for ChannelWitnessUnion {
+ fn from(item: Close) -> Self {
+ ChannelWitnessUnion::Close(item)
+ }
+}
+impl ::core::convert::From for ChannelWitnessUnion {
+ fn from(item: ForceClose) -> Self {
+ ChannelWitnessUnion::ForceClose(item)
+ }
+}
+impl<'r> ::core::convert::From> for ChannelWitnessUnionReader<'r> {
+ fn from(item: FundReader<'r>) -> Self {
+ ChannelWitnessUnionReader::Fund(item)
+ }
+}
+impl<'r> ::core::convert::From> for ChannelWitnessUnionReader<'r> {
+ fn from(item: AbortReader<'r>) -> Self {
+ ChannelWitnessUnionReader::Abort(item)
+ }
+}
+impl<'r> ::core::convert::From> for ChannelWitnessUnionReader<'r> {
+ fn from(item: DisputeReader<'r>) -> Self {
+ ChannelWitnessUnionReader::Dispute(item)
+ }
+}
+impl<'r> ::core::convert::From> for ChannelWitnessUnionReader<'r> {
+ fn from(item: CloseReader<'r>) -> Self {
+ ChannelWitnessUnionReader::Close(item)
+ }
+}
+impl<'r> ::core::convert::From> for ChannelWitnessUnionReader<'r> {
+ fn from(item: ForceCloseReader<'r>) -> Self {
+ ChannelWitnessUnionReader::ForceClose(item)
+ }
+}
+impl ChannelWitnessUnion {
+ pub const NAME: &'static str = "ChannelWitnessUnion";
+ pub fn as_bytes(&self) -> molecule::bytes::Bytes {
+ match self {
+ ChannelWitnessUnion::Fund(item) => item.as_bytes(),
+ ChannelWitnessUnion::Abort(item) => item.as_bytes(),
+ ChannelWitnessUnion::Dispute(item) => item.as_bytes(),
+ ChannelWitnessUnion::Close(item) => item.as_bytes(),
+ ChannelWitnessUnion::ForceClose(item) => item.as_bytes(),
+ }
+ }
+ pub fn as_slice(&self) -> &[u8] {
+ match self {
+ ChannelWitnessUnion::Fund(item) => item.as_slice(),
+ ChannelWitnessUnion::Abort(item) => item.as_slice(),
+ ChannelWitnessUnion::Dispute(item) => item.as_slice(),
+ ChannelWitnessUnion::Close(item) => item.as_slice(),
+ ChannelWitnessUnion::ForceClose(item) => item.as_slice(),
+ }
+ }
+ pub fn item_id(&self) -> molecule::Number {
+ match self {
+ ChannelWitnessUnion::Fund(_) => 0,
+ ChannelWitnessUnion::Abort(_) => 1,
+ ChannelWitnessUnion::Dispute(_) => 2,
+ ChannelWitnessUnion::Close(_) => 3,
+ ChannelWitnessUnion::ForceClose(_) => 4,
+ }
+ }
+ pub fn item_name(&self) -> &str {
+ match self {
+ ChannelWitnessUnion::Fund(_) => "Fund",
+ ChannelWitnessUnion::Abort(_) => "Abort",
+ ChannelWitnessUnion::Dispute(_) => "Dispute",
+ ChannelWitnessUnion::Close(_) => "Close",
+ ChannelWitnessUnion::ForceClose(_) => "ForceClose",
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> ChannelWitnessUnionReader<'r> {
+ match self {
+ ChannelWitnessUnion::Fund(item) => item.as_reader().into(),
+ ChannelWitnessUnion::Abort(item) => item.as_reader().into(),
+ ChannelWitnessUnion::Dispute(item) => item.as_reader().into(),
+ ChannelWitnessUnion::Close(item) => item.as_reader().into(),
+ ChannelWitnessUnion::ForceClose(item) => item.as_reader().into(),
+ }
+ }
+}
+impl<'r> ChannelWitnessUnionReader<'r> {
+ pub const NAME: &'r str = "ChannelWitnessUnionReader";
+ pub fn as_slice(&self) -> &'r [u8] {
+ match self {
+ ChannelWitnessUnionReader::Fund(item) => item.as_slice(),
+ ChannelWitnessUnionReader::Abort(item) => item.as_slice(),
+ ChannelWitnessUnionReader::Dispute(item) => item.as_slice(),
+ ChannelWitnessUnionReader::Close(item) => item.as_slice(),
+ ChannelWitnessUnionReader::ForceClose(item) => item.as_slice(),
+ }
+ }
+ pub fn item_id(&self) -> molecule::Number {
+ match self {
+ ChannelWitnessUnionReader::Fund(_) => 0,
+ ChannelWitnessUnionReader::Abort(_) => 1,
+ ChannelWitnessUnionReader::Dispute(_) => 2,
+ ChannelWitnessUnionReader::Close(_) => 3,
+ ChannelWitnessUnionReader::ForceClose(_) => 4,
+ }
+ }
+ pub fn item_name(&self) -> &str {
+ match self {
+ ChannelWitnessUnionReader::Fund(_) => "Fund",
+ ChannelWitnessUnionReader::Abort(_) => "Abort",
+ ChannelWitnessUnionReader::Dispute(_) => "Dispute",
+ ChannelWitnessUnionReader::Close(_) => "Close",
+ ChannelWitnessUnionReader::ForceClose(_) => "ForceClose",
+ }
+ }
+}
+#[derive(Clone)]
+pub struct ChannelState(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for ChannelState {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for ChannelState {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for ChannelState {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "channel_id", self.channel_id())?;
+ write!(f, ", {}: {}", "balances", self.balances())?;
+ write!(f, ", {}: {}", "version", self.version())?;
+ write!(f, ", {}: {}", "is_final", self.is_final())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for ChannelState {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 97, 0, 0, 0, 20, 0, 0, 0, 52, 0, 0, 0, 84, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0,
+ 12, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ];
+ ChannelState::new_unchecked(v.into())
+ }
+}
+impl ChannelState {
+ pub const FIELD_COUNT: usize = 4;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn channel_id(&self) -> Byte32 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ Byte32::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn balances(&self) -> Balances {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Balances::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn version(&self) -> Uint64 {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ Uint64::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn is_final(&self) -> Bool {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[16..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[20..]) as usize;
+ Bool::new_unchecked(self.0.slice(start..end))
+ } else {
+ Bool::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> ChannelStateReader<'r> {
+ ChannelStateReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for ChannelState {
+ type Builder = ChannelStateBuilder;
+ const NAME: &'static str = "ChannelState";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ ChannelState(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelStateReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelStateReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder()
+ .channel_id(self.channel_id())
+ .balances(self.balances())
+ .version(self.version())
+ .is_final(self.is_final())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct ChannelStateReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for ChannelStateReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for ChannelStateReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for ChannelStateReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "channel_id", self.channel_id())?;
+ write!(f, ", {}: {}", "balances", self.balances())?;
+ write!(f, ", {}: {}", "version", self.version())?;
+ write!(f, ", {}: {}", "is_final", self.is_final())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> ChannelStateReader<'r> {
+ pub const FIELD_COUNT: usize = 4;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn channel_id(&self) -> Byte32Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ Byte32Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn balances(&self) -> BalancesReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ BalancesReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn version(&self) -> Uint64Reader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ Uint64Reader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn is_final(&self) -> BoolReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[16..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[20..]) as usize;
+ BoolReader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ BoolReader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for ChannelStateReader<'r> {
+ type Entity = ChannelState;
+ const NAME: &'static str = "ChannelStateReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ ChannelStateReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ Byte32Reader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ BalancesReader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ Uint64Reader::verify(&slice[offsets[2]..offsets[3]], compatible)?;
+ BoolReader::verify(&slice[offsets[3]..offsets[4]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct ChannelStateBuilder {
+ pub(crate) channel_id: Byte32,
+ pub(crate) balances: Balances,
+ pub(crate) version: Uint64,
+ pub(crate) is_final: Bool,
+}
+impl ChannelStateBuilder {
+ pub const FIELD_COUNT: usize = 4;
+ pub fn channel_id(mut self, v: Byte32) -> Self {
+ self.channel_id = v;
+ self
+ }
+ pub fn balances(mut self, v: Balances) -> Self {
+ self.balances = v;
+ self
+ }
+ pub fn version(mut self, v: Uint64) -> Self {
+ self.version = v;
+ self
+ }
+ pub fn is_final(mut self, v: Bool) -> Self {
+ self.is_final = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for ChannelStateBuilder {
+ type Entity = ChannelState;
+ const NAME: &'static str = "ChannelStateBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.channel_id.as_slice().len()
+ + self.balances.as_slice().len()
+ + self.version.as_slice().len()
+ + self.is_final.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.channel_id.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.balances.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.version.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.is_final.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.channel_id.as_slice())?;
+ writer.write_all(self.balances.as_slice())?;
+ writer.write_all(self.version.as_slice())?;
+ writer.write_all(self.is_final.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ ChannelState::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct ChannelStatus(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for ChannelStatus {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for ChannelStatus {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for ChannelStatus {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "state", self.state())?;
+ write!(f, ", {}: {}", "funded", self.funded())?;
+ write!(f, ", {}: {}", "disputed", self.disputed())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for ChannelStatus {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 123, 0, 0, 0, 16, 0, 0, 0, 113, 0, 0, 0, 118, 0, 0, 0, 97, 0, 0, 0, 20, 0, 0, 0, 52, 0,
+ 0, 0, 84, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 12, 0, 0, 0, 28, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ ];
+ ChannelStatus::new_unchecked(v.into())
+ }
+}
+impl ChannelStatus {
+ pub const FIELD_COUNT: usize = 3;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn state(&self) -> ChannelState {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ ChannelState::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn funded(&self) -> Bool {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ Bool::new_unchecked(self.0.slice(start..end))
+ }
+ pub fn disputed(&self) -> Bool {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ Bool::new_unchecked(self.0.slice(start..end))
+ } else {
+ Bool::new_unchecked(self.0.slice(start..))
+ }
+ }
+ pub fn as_reader<'r>(&'r self) -> ChannelStatusReader<'r> {
+ ChannelStatusReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for ChannelStatus {
+ type Builder = ChannelStatusBuilder;
+ const NAME: &'static str = "ChannelStatus";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ ChannelStatus(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelStatusReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelStatusReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder()
+ .state(self.state())
+ .funded(self.funded())
+ .disputed(self.disputed())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct ChannelStatusReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for ChannelStatusReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for ChannelStatusReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for ChannelStatusReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "state", self.state())?;
+ write!(f, ", {}: {}", "funded", self.funded())?;
+ write!(f, ", {}: {}", "disputed", self.disputed())?;
+ let extra_count = self.count_extra_fields();
+ if extra_count != 0 {
+ write!(f, ", .. ({} fields)", extra_count)?;
+ }
+ write!(f, " }}")
+ }
+}
+impl<'r> ChannelStatusReader<'r> {
+ pub const FIELD_COUNT: usize = 3;
+ pub fn total_size(&self) -> usize {
+ molecule::unpack_number(self.as_slice()) as usize
+ }
+ pub fn field_count(&self) -> usize {
+ if self.total_size() == molecule::NUMBER_SIZE {
+ 0
+ } else {
+ (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1
+ }
+ }
+ pub fn count_extra_fields(&self) -> usize {
+ self.field_count() - Self::FIELD_COUNT
+ }
+ pub fn has_extra_fields(&self) -> bool {
+ Self::FIELD_COUNT != self.field_count()
+ }
+ pub fn state(&self) -> ChannelStateReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[4..]) as usize;
+ let end = molecule::unpack_number(&slice[8..]) as usize;
+ ChannelStateReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn funded(&self) -> BoolReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[8..]) as usize;
+ let end = molecule::unpack_number(&slice[12..]) as usize;
+ BoolReader::new_unchecked(&self.as_slice()[start..end])
+ }
+ pub fn disputed(&self) -> BoolReader<'r> {
+ let slice = self.as_slice();
+ let start = molecule::unpack_number(&slice[12..]) as usize;
+ if self.has_extra_fields() {
+ let end = molecule::unpack_number(&slice[16..]) as usize;
+ BoolReader::new_unchecked(&self.as_slice()[start..end])
+ } else {
+ BoolReader::new_unchecked(&self.as_slice()[start..])
+ }
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for ChannelStatusReader<'r> {
+ type Entity = ChannelStatus;
+ const NAME: &'static str = "ChannelStatusReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ ChannelStatusReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len < molecule::NUMBER_SIZE {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len);
+ }
+ let total_size = molecule::unpack_number(slice) as usize;
+ if slice_len != total_size {
+ return ve!(Self, TotalSizeNotMatch, total_size, slice_len);
+ }
+ if slice_len == molecule::NUMBER_SIZE && Self::FIELD_COUNT == 0 {
+ return Ok(());
+ }
+ if slice_len < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len);
+ }
+ let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize;
+ if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ if slice_len < offset_first {
+ return ve!(Self, HeaderIsBroken, offset_first, slice_len);
+ }
+ let field_count = offset_first / molecule::NUMBER_SIZE - 1;
+ if field_count < Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ } else if !compatible && field_count > Self::FIELD_COUNT {
+ return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count);
+ };
+ let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first]
+ .chunks_exact(molecule::NUMBER_SIZE)
+ .map(|x| molecule::unpack_number(x) as usize)
+ .collect();
+ offsets.push(total_size);
+ if offsets.windows(2).any(|i| i[0] > i[1]) {
+ return ve!(Self, OffsetsNotMatch);
+ }
+ ChannelStateReader::verify(&slice[offsets[0]..offsets[1]], compatible)?;
+ BoolReader::verify(&slice[offsets[1]..offsets[2]], compatible)?;
+ BoolReader::verify(&slice[offsets[2]..offsets[3]], compatible)?;
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct ChannelStatusBuilder {
+ pub(crate) state: ChannelState,
+ pub(crate) funded: Bool,
+ pub(crate) disputed: Bool,
+}
+impl ChannelStatusBuilder {
+ pub const FIELD_COUNT: usize = 3;
+ pub fn state(mut self, v: ChannelState) -> Self {
+ self.state = v;
+ self
+ }
+ pub fn funded(mut self, v: Bool) -> Self {
+ self.funded = v;
+ self
+ }
+ pub fn disputed(mut self, v: Bool) -> Self {
+ self.disputed = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for ChannelStatusBuilder {
+ type Entity = ChannelStatus;
+ const NAME: &'static str = "ChannelStatusBuilder";
+ fn expected_length(&self) -> usize {
+ molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1)
+ + self.state.as_slice().len()
+ + self.funded.as_slice().len()
+ + self.disputed.as_slice().len()
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1);
+ let mut offsets = Vec::with_capacity(Self::FIELD_COUNT);
+ offsets.push(total_size);
+ total_size += self.state.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.funded.as_slice().len();
+ offsets.push(total_size);
+ total_size += self.disputed.as_slice().len();
+ writer.write_all(&molecule::pack_number(total_size as molecule::Number))?;
+ for offset in offsets.into_iter() {
+ writer.write_all(&molecule::pack_number(offset as molecule::Number))?;
+ }
+ writer.write_all(self.state.as_slice())?;
+ writer.write_all(self.funded.as_slice())?;
+ writer.write_all(self.disputed.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ ChannelStatus::new_unchecked(inner.into())
+ }
+}
+#[derive(Clone)]
+pub struct ChannelToken(molecule::bytes::Bytes);
+impl ::core::fmt::LowerHex for ChannelToken {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl ::core::fmt::Debug for ChannelToken {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl ::core::fmt::Display for ChannelToken {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "out_point", self.out_point())?;
+ write!(f, " }}")
+ }
+}
+impl ::core::default::Default for ChannelToken {
+ fn default() -> Self {
+ let v: Vec = vec![
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ ];
+ ChannelToken::new_unchecked(v.into())
+ }
+}
+impl ChannelToken {
+ pub const TOTAL_SIZE: usize = 36;
+ pub const FIELD_SIZES: [usize; 1] = [36];
+ pub const FIELD_COUNT: usize = 1;
+ pub fn out_point(&self) -> OutPoint {
+ OutPoint::new_unchecked(self.0.slice(0..36))
+ }
+ pub fn as_reader<'r>(&'r self) -> ChannelTokenReader<'r> {
+ ChannelTokenReader::new_unchecked(self.as_slice())
+ }
+}
+impl molecule::prelude::Entity for ChannelToken {
+ type Builder = ChannelTokenBuilder;
+ const NAME: &'static str = "ChannelToken";
+ fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
+ ChannelToken(data)
+ }
+ fn as_bytes(&self) -> molecule::bytes::Bytes {
+ self.0.clone()
+ }
+ fn as_slice(&self) -> &[u8] {
+ &self.0[..]
+ }
+ fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelTokenReader::from_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult {
+ ChannelTokenReader::from_compatible_slice(slice).map(|reader| reader.to_entity())
+ }
+ fn new_builder() -> Self::Builder {
+ ::core::default::Default::default()
+ }
+ fn as_builder(self) -> Self::Builder {
+ Self::new_builder().out_point(self.out_point())
+ }
+}
+#[derive(Clone, Copy)]
+pub struct ChannelTokenReader<'r>(&'r [u8]);
+impl<'r> ::core::fmt::LowerHex for ChannelTokenReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ use molecule::hex_string;
+ if f.alternate() {
+ write!(f, "0x")?;
+ }
+ write!(f, "{}", hex_string(self.as_slice()))
+ }
+}
+impl<'r> ::core::fmt::Debug for ChannelTokenReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{}({:#x})", Self::NAME, self)
+ }
+}
+impl<'r> ::core::fmt::Display for ChannelTokenReader<'r> {
+ fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
+ write!(f, "{} {{ ", Self::NAME)?;
+ write!(f, "{}: {}", "out_point", self.out_point())?;
+ write!(f, " }}")
+ }
+}
+impl<'r> ChannelTokenReader<'r> {
+ pub const TOTAL_SIZE: usize = 36;
+ pub const FIELD_SIZES: [usize; 1] = [36];
+ pub const FIELD_COUNT: usize = 1;
+ pub fn out_point(&self) -> OutPointReader<'r> {
+ OutPointReader::new_unchecked(&self.as_slice()[0..36])
+ }
+}
+impl<'r> molecule::prelude::Reader<'r> for ChannelTokenReader<'r> {
+ type Entity = ChannelToken;
+ const NAME: &'static str = "ChannelTokenReader";
+ fn to_entity(&self) -> Self::Entity {
+ Self::Entity::new_unchecked(self.as_slice().to_owned().into())
+ }
+ fn new_unchecked(slice: &'r [u8]) -> Self {
+ ChannelTokenReader(slice)
+ }
+ fn as_slice(&self) -> &'r [u8] {
+ self.0
+ }
+ fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> {
+ use molecule::verification_error as ve;
+ let slice_len = slice.len();
+ if slice_len != Self::TOTAL_SIZE {
+ return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len);
+ }
+ Ok(())
+ }
+}
+#[derive(Debug, Default)]
+pub struct ChannelTokenBuilder {
+ pub(crate) out_point: OutPoint,
+}
+impl ChannelTokenBuilder {
+ pub const TOTAL_SIZE: usize = 36;
+ pub const FIELD_SIZES: [usize; 1] = [36];
+ pub const FIELD_COUNT: usize = 1;
+ pub fn out_point(mut self, v: OutPoint) -> Self {
+ self.out_point = v;
+ self
+ }
+}
+impl molecule::prelude::Builder for ChannelTokenBuilder {
+ type Entity = ChannelToken;
+ const NAME: &'static str = "ChannelTokenBuilder";
+ fn expected_length(&self) -> usize {
+ Self::TOTAL_SIZE
+ }
+ fn write(&self, writer: &mut W) -> molecule::io::Result<()> {
+ writer.write_all(self.out_point.as_slice())?;
+ Ok(())
+ }
+ fn build(&self) -> Self::Entity {
+ let mut inner = Vec::with_capacity(self.expected_length());
+ self.write(&mut inner)
+ .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME));
+ ChannelToken::new_unchecked(inner.into())
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/sig.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/sig.rs
new file mode 100644
index 0000000..3a7e905
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-common/src/sig.rs
@@ -0,0 +1,11 @@
+use k256::{ecdsa::{VerifyingKey, Signature, signature::{hazmat::PrehashVerifier}}, elliptic_curve::sec1::EncodedPoint, Secp256k1};
+
+use crate::error::Error;
+
+pub fn verify_signature(msg_hash: &[u8; 32], sig: &[u8], key: &[u8]) -> Result<(), Error> {
+ let signature = Signature::from_der(sig)?;
+ let e = EncodedPoint::::from_bytes(key).expect("unable to decode public key");
+ let verifying_key = VerifyingKey::from_encoded_point(&e)?;
+ verifying_key.verify_prehash(msg_hash, &signature)?;
+ Ok(())
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-common/types.mol b/payment-channel-ckb/devnet/contracts/contracts/perun-common/types.mol
new file mode 100644
index 0000000..5fa3a5f
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-common/types.mol
@@ -0,0 +1,140 @@
+import blockchain;
+
+/* Perun Types */
+array SEC1EncodedPubKey [byte; 33];
+
+array CKByteDistribution [Uint64; 2];
+
+array SUDTDistribution [Uint128; 2];
+
+vector SUDTAllocation ;
+
+table SUDTAsset {
+ type_script: Script,
+ // The max_capacity of an SUDTAsset should always be at least the capacity needed for the SUDT type script + outputs_data
+ // + max(party_a.payment_min_capacity, party_b.payment_min_capacity)
+ // Make sure verify this in the Funding Agreement, as the contract can not verify this upon channel start!
+ max_capacity: Uint64,
+}
+
+table SUDTBalances {
+ asset: SUDTAsset,
+ distribution: SUDTDistribution,
+}
+
+table Balances {
+ ckbytes: CKByteDistribution,
+ sudts: SUDTAllocation,
+}
+
+array True [byte; 1];
+array False [byte; 1];
+
+union Bool {
+ True,
+ False,
+}
+
+array A [byte; 1];
+array B [byte; 1];
+
+
+option App (Bytes);
+
+// Terminology:
+// - script_hash: By script_hash we mean the results of the syscalls load_cell_lock_hash / load_cell_type_hash
+// and the sdk function calc_script_hash. This is the hash of the script struct (code_hash, hash_type and args).
+// - code_hash: By code_hash we mean the member of a script that hold the hash of the executed code (depending on the hash_type).
+// See: https://docs.nervos.org/docs/reference/script/
+
+
+
+table Participant {
+ // payment_script_hash specifies the script-hash used
+ // to lock payments to this participant (upon channel close)
+ payment_script_hash: Byte32,
+ // payment_min_capacity specifies the minimum capacity of the payment lock script.
+ payment_min_capacity: Uint64,
+
+ // unlock_script_hash specifies the script-hash that needs to be present in the inputs
+ // to a transaction to authorize the transaction to interact with the channel as
+ // this channel participant.
+ unlock_script_hash: Byte32,
+
+ pub_key: SEC1EncodedPubKey,
+}
+table ChannelParameters {
+ party_a: Participant,
+ party_b: Participant,
+ nonce: Byte32,
+ challenge_duration: Uint64,
+ // The default should be NoApp!
+ app: App,
+ // This should always be set to true for, as we currently only support ledger channels.
+ is_ledger_channel: Bool,
+ // This should always be set to false for, as we currently do not support virtual channels.
+ is_virtual_channel: Bool,
+}
+
+// Important: Upon channel creation, every participant must verify the integrity of the channel.
+// This includes verifying that the correct ChannelConstants are present.
+// If e.g. the payment_min_capacity (inside the participants of the channel parameters) were to be significantly larger than the minimum
+// capacity of the payment lock script, a party could steal funds from the channel participants with balances smaller than the
+// payment_min_capacity upon channel closing.
+table ChannelConstants {
+ params: ChannelParameters,
+ // pfls__code_hash specifies the code hash of the lock_script that guards funds for this channel.
+ // Specifically, this should be the perun-funds-lockscript.
+ pfls_code_hash: Byte32,
+ pfls_hash_type: byte,
+ pfls_min_capacity: Uint64,
+
+ // pcls_hash specifies the lock_script used for this channel.
+ // Specifically, this should be the perun-channel-lockscript.
+ pcls_code_hash: Byte32,
+ pcls_hash_type: byte,
+
+ thread_token: ChannelToken,
+}
+
+array Fund [byte; 1];
+
+array Abort [byte; 1];
+
+table Dispute {
+ sig_a: Bytes,
+ sig_b: Bytes,
+}
+table Close {
+ state: ChannelState,
+ sig_a: Bytes,
+ sig_b: Bytes,
+}
+array ForceClose [byte; 1];
+
+
+union ChannelWitness {
+ Fund,
+ Abort,
+ Dispute,
+ Close,
+ ForceClose,
+}
+
+table ChannelState {
+ //
+ channel_id: Byte32,
+ balances: Balances,
+ version: Uint64,
+ is_final: Bool,
+}
+
+table ChannelStatus {
+ state: ChannelState,
+ funded: Bool,
+ disputed: Bool,
+}
+
+struct ChannelToken {
+ out_point: OutPoint,
+}
\ No newline at end of file
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-funds-lockscript/Cargo.toml b/payment-channel-ckb/devnet/contracts/contracts/perun-funds-lockscript/Cargo.toml
new file mode 100644
index 0000000..6e66cd6
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-funds-lockscript/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "perun-funds-lockscript"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+ckb-std = "0.10.0"
+perun-common = { path = "../perun-common", default-features = false, features = ["contract"] }
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-funds-lockscript/src/entry.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-funds-lockscript/src/entry.rs
new file mode 100644
index 0000000..1d21352
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-funds-lockscript/src/entry.rs
@@ -0,0 +1,47 @@
+// Import from `core` instead of from `std` since we are in no-std mode
+use core::result::Result;
+
+// Import heap related library from `alloc`
+// https://doc.rust-lang.org/alloc/index.html
+
+use perun_common::error::Error;
+
+// Import CKB syscalls and structures
+// https://docs.rs/ckb-std/
+use ckb_std::{
+ ckb_constants::Source,
+ ckb_types::{bytes::Bytes, packed::Byte32, prelude::*},
+ high_level::{load_cell_type_hash, load_script, load_transaction},
+};
+
+// The Perun Funds Lock Script can be unlocked by including an input cell with the pcts script hash
+// that is specified in the args of the pfls.
+pub fn main() -> Result<(), Error> {
+ let script = load_script()?;
+ let args: Bytes = script.args().unpack();
+
+ if args.is_empty() {
+ return Err(Error::NoArgs);
+ }
+
+ let pcts_script_hash = Byte32::from_slice(&args)?;
+
+ return verify_pcts_in_inputs(&pcts_script_hash.unpack());
+}
+
+pub fn verify_pcts_in_inputs(pcts_script_hash: &[u8; 32]) -> Result<(), Error> {
+ let num_inputs = load_transaction()?.raw().inputs().len();
+ for i in 0..num_inputs {
+ match load_cell_type_hash(i, Source::Input)? {
+ Some(cell_type_script_hash) => {
+ if cell_type_script_hash[..] == pcts_script_hash[..] {
+ return Ok(());
+ } else {
+ continue;
+ }
+ }
+ None => continue,
+ };
+ }
+ Err(Error::PCTSNotFound)
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/perun-funds-lockscript/src/main.rs b/payment-channel-ckb/devnet/contracts/contracts/perun-funds-lockscript/src/main.rs
new file mode 100644
index 0000000..9306fc4
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/perun-funds-lockscript/src/main.rs
@@ -0,0 +1,32 @@
+//! Generated by capsule
+//!
+//! `main.rs` is used to define rust lang items and modules.
+//! See `entry.rs` for the `main` function.
+//! See `error.rs` for the `Error` type.
+
+#![no_std]
+#![no_main]
+#![feature(asm_sym)]
+#![feature(lang_items)]
+#![feature(alloc_error_handler)]
+#![feature(panic_info_message)]
+
+// define modules
+mod entry;
+
+use ckb_std::default_alloc;
+use core::arch::asm;
+
+ckb_std::entry!(program_entry);
+default_alloc!();
+
+/// program entry
+///
+/// Both `argc` and `argv` can be omitted.
+fn program_entry(_argc: u64, _argv: *const *const u8) -> i8 {
+ // Call main function and return error code
+ match entry::main() {
+ Ok(_) => 0,
+ Err(err) => err as i8,
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/contracts/sample-udt/Cargo.toml b/payment-channel-ckb/devnet/contracts/contracts/sample-udt/Cargo.toml
new file mode 100644
index 0000000..1d27e95
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/sample-udt/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "sample-udt"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+ckb-std = "0.10.0"
+perun-common = { path = "../perun-common", default-features = false, features = ["contract"] }
diff --git a/payment-channel-ckb/devnet/contracts/contracts/sample-udt/src/entry.rs b/payment-channel-ckb/devnet/contracts/contracts/sample-udt/src/entry.rs
new file mode 100644
index 0000000..4e0c757
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/sample-udt/src/entry.rs
@@ -0,0 +1,101 @@
+// Import from `core` instead of from `std` since we are in no-std mode
+use core::result::Result;
+
+// Import CKB syscalls and structures
+// https://docs.rs/ckb-std/
+use ckb_std::{
+ ckb_constants::Source,
+ ckb_types::{bytes::Bytes, prelude::*},
+ high_level::{load_cell_lock_hash, load_script, load_cell_data},
+ syscalls::SysError,
+};
+use perun_common::error::Error;
+
+
+pub fn main() -> Result<(), Error> {
+ let script = load_script()?;
+ let args: Bytes = script.args().unpack();
+
+ // return success if owner mode is true
+ if check_owner_mode(&args)? {
+ return Ok(());
+ }
+
+ let inputs_amount = collect_inputs_amount()?;
+ let outputs_amount = collect_outputs_amount()?;
+
+ if inputs_amount < outputs_amount {
+ return Err(Error::DecreasingAmount);
+ }
+
+ Ok(())
+}
+
+pub fn check_owner_mode(args: &Bytes) -> Result {
+ // With owner lock script extracted, we will look through each input in the
+ // current transaction to see if any unlocked cell uses owner lock.
+ for i in 0.. {
+ // check input's lock_hash with script args
+ let lock_hash = match load_cell_lock_hash(
+ i,
+ Source::Input,
+ ) {
+ Ok(lock_hash) => lock_hash,
+ Err(SysError::IndexOutOfBound) => return Ok(false),
+ Err(err) => return Err(err.into()),
+ };
+ // invalid length of loaded data
+ if args[..] == lock_hash[..] {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+}
+
+const UDT_LEN: usize = 16;
+
+pub fn collect_inputs_amount() -> Result {
+ // let's loop through all input cells containing current UDTs,
+ // and gather the sum of all input tokens.
+ let mut inputs_amount: u128 = 0;
+ let mut buf = [0u8; UDT_LEN];
+
+ // u128 is 16 bytes
+ for i in 0.. {
+ let data = match load_cell_data(i, Source::GroupInput) {
+ Ok(data) => data,
+ Err(SysError::IndexOutOfBound) => break,
+ Err(err) => return Err(err.into()),
+ };
+
+ if data.len() != UDT_LEN {
+ return Err(Error::Encoding);
+ }
+ buf.copy_from_slice(&data);
+ inputs_amount += u128::from_le_bytes(buf);
+ }
+ Ok(inputs_amount)
+}
+
+fn collect_outputs_amount() -> Result {
+ // With the sum of all input UDT tokens gathered, let's now iterate through
+ // output cells to grab the sum of all output UDT tokens.
+ let mut outputs_amount: u128 = 0;
+
+ // u128 is 16 bytes
+ let mut buf = [0u8; UDT_LEN];
+ for i in 0.. {
+ let data = match load_cell_data(i, Source::GroupOutput) {
+ Ok(data) => data,
+ Err(SysError::IndexOutOfBound) => break,
+ Err(err) => return Err(err.into()),
+ };
+
+ if data.len() != UDT_LEN {
+ return Err(Error::Encoding);
+ }
+ buf.copy_from_slice(&data);
+ outputs_amount += u128::from_le_bytes(buf);
+ }
+ Ok(outputs_amount)
+}
\ No newline at end of file
diff --git a/payment-channel-ckb/devnet/contracts/contracts/sample-udt/src/main.rs b/payment-channel-ckb/devnet/contracts/contracts/sample-udt/src/main.rs
new file mode 100644
index 0000000..9306fc4
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/contracts/sample-udt/src/main.rs
@@ -0,0 +1,32 @@
+//! Generated by capsule
+//!
+//! `main.rs` is used to define rust lang items and modules.
+//! See `entry.rs` for the `main` function.
+//! See `error.rs` for the `Error` type.
+
+#![no_std]
+#![no_main]
+#![feature(asm_sym)]
+#![feature(lang_items)]
+#![feature(alloc_error_handler)]
+#![feature(panic_info_message)]
+
+// define modules
+mod entry;
+
+use ckb_std::default_alloc;
+use core::arch::asm;
+
+ckb_std::entry!(program_entry);
+default_alloc!();
+
+/// program entry
+///
+/// Both `argc` and `argv` can be omitted.
+fn program_entry(_argc: u64, _argv: *const *const u8) -> i8 {
+ // Call main function and return error code
+ match entry::main() {
+ Ok(_) => 0,
+ Err(err) => err as i8,
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/deployment/dev/deployment.toml b/payment-channel-ckb/devnet/contracts/deployment/dev/deployment.toml
new file mode 100644
index 0000000..899072d
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/deployment/dev/deployment.toml
@@ -0,0 +1,41 @@
+[[cells]]
+name = "pcts"
+enable_type_id = false
+location = { file = "build/release/perun-channel-typescript" }
+
+[[cells]]
+name = "pcls"
+enable_type_id = false
+location = { file = "build/release/perun-channel-lockscript" }
+
+[[cells]]
+name = "pfls"
+enable_type_id = false
+location = { file = "build/release/perun-funds-lockscript" }
+
+[[cells]]
+name = "sudt"
+enable_type_id = false
+location = { file = "build/release/sample-udt" }
+
+#
+# # reference to on-chain cells
+# [[cells]]
+# name = "genesis_cell"
+# enable_type_id = false
+# location = { tx_hash = "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c", index = 0 }
+
+# # Dep group cells
+# [[dep_groups]]
+# name = "my_dep_group"
+# cells = [
+# "my_cell",
+# "genesis_cell"
+# ]
+
+# # Replace with your own lock if you want to unlock deployed cells.
+# # For example the secp256k1 lock
+[lock]
+code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"
+args = "0x4a3c88737f8d4d26031ad9a6d6448ba75f1533e2"
+hash_type = "type"
diff --git a/payment-channel-ckb/devnet/contracts/deployment/release/deployment.toml b/payment-channel-ckb/devnet/contracts/deployment/release/deployment.toml
new file mode 100644
index 0000000..65968f5
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/deployment/release/deployment.toml
@@ -0,0 +1,37 @@
+[[cells]]
+name = "pcts"
+enable_type_id = false
+location = { file = "build/release/perun-channel-typescript" }
+
+[[cells]]
+name = "pcls"
+enable_type_id = false
+location = { file = "build/release/perun-channel-lockscript" }
+
+[[cells]]
+name = "pfls"
+enable_type_id = false
+location = { file = "build/release/perun-funds-lockscript" }
+
+#
+# # reference to on-chain cells
+# [[cells]]
+# name = "genesis_cell"
+# enable_type_id = false
+# location = { tx_hash = "0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c", index = 0 }
+
+# # Dep group cells
+# [[dep_groups]]
+# name = "my_dep_group"
+# cells = [
+# "my_cell",
+# "genesis_cell"
+# ]
+
+# # Replace with your own lock if you want to unlock deployed cells.
+# # For example the secp256k1 lock
+# [lock]
+# code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8"
+# args = "0x1edcdab5ec7e0f748c60815fce513ee1fe4d63ee"
+# hash_type = "type"
+
diff --git a/payment-channel-ckb/devnet/contracts/migrations/.gitkeep b/payment-channel-ckb/devnet/contracts/migrations/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/payment-channel-ckb/devnet/contracts/tests/Cargo.lock b/payment-channel-ckb/devnet/contracts/tests/Cargo.lock
new file mode 100644
index 0000000..d7d027c
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/Cargo.lock
@@ -0,0 +1,1593 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom 0.2.9",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base16ct"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce"
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "blake2b-ref"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95916998c798756098a4eb1b3f2cd510659705a9817bf203d61abd30fbec3e7b"
+
+[[package]]
+name = "blake2b-rs"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89a8565807f21b913288968e391819e7f9b2f0f46c7b89549c051cccf3a2771"
+dependencies = [
+ "cc",
+ "cty",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "buddy-alloc"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3240a4cb09cf0da6a51641bd40ce90e96ea6065e3a1adc46434029254bcc2d09"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "ckb-always-success-script"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b3b72a38c9920a29990df12002c4d069a147c8782f0c211f8a01b2df8f42bfd"
+
+[[package]]
+name = "ckb-chain-spec"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78df45446aaa86b06a77b8b145cffa79950e7ede293cebcd114a62e74c29dbf"
+dependencies = [
+ "ckb-constant",
+ "ckb-crypto",
+ "ckb-dao-utils",
+ "ckb-error",
+ "ckb-hash",
+ "ckb-jsonrpc-types",
+ "ckb-pow",
+ "ckb-rational",
+ "ckb-resource",
+ "ckb-traits",
+ "ckb-types",
+ "ckb-util",
+ "serde",
+ "toml",
+]
+
+[[package]]
+name = "ckb-channel"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "920f26cc48cadcaf6f7bcc3960fde9f9f355633b6361da8ef31e1e1c00fc8858"
+dependencies = [
+ "crossbeam-channel",
+]
+
+[[package]]
+name = "ckb-constant"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "302566408e5b296663ac5e8245bf71824ca2c7c2ef19a57fcc15939dd66527e9"
+
+[[package]]
+name = "ckb-crypto"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aac31177b0a8bf3acd563c042775e40494e437b2bbbae96ac2473eec3a4da95d"
+dependencies = [
+ "ckb-fixed-hash",
+ "faster-hex",
+ "lazy_static",
+ "rand 0.7.3",
+ "secp256k1",
+ "thiserror",
+]
+
+[[package]]
+name = "ckb-dao"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b70944b9013ead64287b87ac19608a3ca5ab19a9f29b7a76f637ad7831510e88"
+dependencies = [
+ "byteorder",
+ "ckb-chain-spec",
+ "ckb-dao-utils",
+ "ckb-traits",
+ "ckb-types",
+]
+
+[[package]]
+name = "ckb-dao-utils"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1929c9627923fe1d22151361d74f5a5aa0dda77016d020307a54486eae11cb3c"
+dependencies = [
+ "byteorder",
+ "ckb-error",
+ "ckb-types",
+]
+
+[[package]]
+name = "ckb-error"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446a519d8a847d97f1c8ece739dc1748751a9a2179249c96c45cced0825a7aa5"
+dependencies = [
+ "anyhow",
+ "ckb-occupied-capacity",
+ "derive_more",
+ "thiserror",
+]
+
+[[package]]
+name = "ckb-fixed-hash"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00cbbc455b23748b32e06d16628a03e30d56ffa057f17093fdf5b42d4fb6c879"
+dependencies = [
+ "ckb-fixed-hash-core",
+ "ckb-fixed-hash-macros",
+]
+
+[[package]]
+name = "ckb-fixed-hash-core"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4e644a4e026625b4be5a04cdf6c02043080e79feaf77d9cdbb2f0e6553f751"
+dependencies = [
+ "faster-hex",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "ckb-fixed-hash-macros"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cfc980ef88c217825172eb46df269f47890f5e78a38214416f13b3bd17a4b4"
+dependencies = [
+ "ckb-fixed-hash-core",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ckb-hash"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d9b683e89ae4ffdd5aaf4172eab00b6bbe7ea24e2abf77d3eb850ba36e8983"
+dependencies = [
+ "blake2b-ref",
+ "blake2b-rs",
+]
+
+[[package]]
+name = "ckb-jsonrpc-types"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac087657eaf964e729f40b3c929d3dac74a2cd8bb38d5e588756e2495711f810"
+dependencies = [
+ "ckb-types",
+ "faster-hex",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "ckb-logger"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "911c4695ddf82f78da8f514b359092bbe231f58c2669c93b1cfc9a2030b125bb"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "ckb-merkle-mountain-range"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ccb671c5921be8a84686e6212ca184cb1d7c51cadcdbfcbd1cc3f042f5dfb8"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "ckb-occupied-capacity"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d2a1dd0d4ba5dafba1e30d437c1148b20f42edb76b6794323e05bda626754eb"
+dependencies = [
+ "ckb-occupied-capacity-core",
+ "ckb-occupied-capacity-macros",
+]
+
+[[package]]
+name = "ckb-occupied-capacity-core"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ebba3d564098a84c83f4740e1dce48a5e2da759becdb47e3c7965f0808e6e92"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "ckb-occupied-capacity-macros"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6321bba85cdf9724029d8c906851dd4a90906869b42f9100b16645a1261d4c"
+dependencies = [
+ "ckb-occupied-capacity-core",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ckb-pow"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9167b427f42874e68e20e6946d5211709979ff1d86c0061a71c2f6a6aa17659"
+dependencies = [
+ "byteorder",
+ "ckb-hash",
+ "ckb-types",
+ "eaglesong",
+ "log",
+ "serde",
+]
+
+[[package]]
+name = "ckb-rational"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2519249f8d47fa758d3fb3cf3049327c69ce0f2acd79d61427482c8661d3dbd"
+dependencies = [
+ "numext-fixed-uint",
+ "serde",
+]
+
+[[package]]
+name = "ckb-resource"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3abddc968d7f1e70584ab04180c347380a44acbe0b60e26cc96208ec8885279"
+dependencies = [
+ "ckb-system-scripts",
+ "ckb-types",
+ "includedir",
+ "includedir_codegen",
+ "phf",
+ "serde",
+ "walkdir",
+]
+
+[[package]]
+name = "ckb-script"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12b4754a2f0ccea5ea1934822bd18a3a66c46344d8c3872cb20ffdcf0851fab9"
+dependencies = [
+ "byteorder",
+ "ckb-chain-spec",
+ "ckb-error",
+ "ckb-hash",
+ "ckb-logger",
+ "ckb-traits",
+ "ckb-types",
+ "ckb-vm",
+ "faster-hex",
+ "serde",
+]
+
+[[package]]
+name = "ckb-standalone-types"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22d7cbbdab96e6b809a102cf88bfec28795a0a3c06bfdea4abe4de89777801cd"
+dependencies = [
+ "cfg-if",
+ "molecule",
+]
+
+[[package]]
+name = "ckb-std"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47a6ad40455c446ad6fbb303dae24827fc309f43558f59d1f1b863a9de3e9f81"
+dependencies = [
+ "buddy-alloc",
+ "cc",
+ "ckb-standalone-types",
+ "cstr_core",
+]
+
+[[package]]
+name = "ckb-system-scripts"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa5c59063142de7a68cfad4449c6b3863563856219a2925dfb8c5f019ec2aa47"
+dependencies = [
+ "blake2b-rs",
+ "faster-hex",
+ "includedir",
+ "includedir_codegen",
+ "phf",
+]
+
+[[package]]
+name = "ckb-systemtime"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "243197680f69d6bb6cb1caf16199ce4a8162a258c757d5af8f727af0d8aabe9e"
+
+[[package]]
+name = "ckb-testtool"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15fed7e8aeb21e981246bc39e0bd49067f7734851798bef996389a3b02bf9b4e"
+dependencies = [
+ "ckb-always-success-script",
+ "ckb-chain-spec",
+ "ckb-crypto",
+ "ckb-error",
+ "ckb-hash",
+ "ckb-jsonrpc-types",
+ "ckb-resource",
+ "ckb-script",
+ "ckb-traits",
+ "ckb-types",
+ "ckb-verification",
+ "lazy_static",
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "ckb-traits"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e9d5827f20a396dfb785398db484fe50de93d76c02e1e32287832604a9dda91"
+dependencies = [
+ "ckb-types",
+]
+
+[[package]]
+name = "ckb-types"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c22b3b1ca8f88a8f48e2f73321c0605281c9c6f1e1c4d651c6138265c22291e"
+dependencies = [
+ "bit-vec",
+ "bytes",
+ "ckb-channel",
+ "ckb-error",
+ "ckb-fixed-hash",
+ "ckb-hash",
+ "ckb-merkle-mountain-range",
+ "ckb-occupied-capacity",
+ "ckb-rational",
+ "derive_more",
+ "merkle-cbt",
+ "molecule",
+ "numext-fixed-uint",
+ "once_cell",
+]
+
+[[package]]
+name = "ckb-util"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03d165c6958601dfbfa4cd00c9263ecfb013b4ccb6d9e1d3187bfa62801abc7d"
+dependencies = [
+ "linked-hash-map",
+ "once_cell",
+ "parking_lot",
+ "regex",
+]
+
+[[package]]
+name = "ckb-verification"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbc1745cf02f6d628ac04cf58145b853a359ad4d74fdb418207e99773185ad11"
+dependencies = [
+ "ckb-chain-spec",
+ "ckb-dao",
+ "ckb-dao-utils",
+ "ckb-error",
+ "ckb-pow",
+ "ckb-script",
+ "ckb-systemtime",
+ "ckb-traits",
+ "ckb-types",
+ "ckb-verification-traits",
+ "derive_more",
+ "lru",
+]
+
+[[package]]
+name = "ckb-verification-traits"
+version = "0.108.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88de577410c2e72ccd18e00cb63fc0000d41be50604a895946a1566a02272730"
+dependencies = [
+ "bitflags",
+ "ckb-error",
+]
+
+[[package]]
+name = "ckb-vm"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1223acc8054ce96f91c5d99d4942898d0bdadd618c3b14f1acd3e67212991d8e"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "cc",
+ "ckb-vm-definitions",
+ "derive_more",
+ "goblin 0.2.3",
+ "goblin 0.4.0",
+ "rand 0.7.3",
+ "scroll",
+ "serde",
+]
+
+[[package]]
+name = "ckb-vm-definitions"
+version = "0.22.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4af800ae2b6c54b70efa398dab015a09a52eeac2dd1ac3ad32c9bbe224974225"
+
+[[package]]
+name = "const-oid"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
+
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-bigint"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cstr_core"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd98742e4fdca832d40cab219dc2e3048de17d873248f83f17df47c1bea70956"
+dependencies = [
+ "cty",
+ "memchr",
+]
+
+[[package]]
+name = "cty"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
+
+[[package]]
+name = "der"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
+dependencies = [
+ "const-oid",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "eaglesong"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d978bd5d343e8ab9b5c0fc8d93ff9c602fdc96616ffff9c05ac7a155419b824"
+
+[[package]]
+name = "ecdsa"
+version = "0.14.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
+dependencies = [
+ "der",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+]
+
+[[package]]
+name = "elliptic-curve"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "der",
+ "digest",
+ "ff",
+ "generic-array",
+ "group",
+ "rand_core 0.6.4",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "faster-hex"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51e2ce894d53b295cf97b05685aa077950ff3e8541af83217fc720a6437169f8"
+
+[[package]]
+name = "ff"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
+dependencies = [
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "goblin"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d20fd25aa456527ce4f544271ae4fea65d2eda4a6561ea56f39fb3ee4f7e3884"
+dependencies = [
+ "log",
+ "plain",
+ "scroll",
+]
+
+[[package]]
+name = "goblin"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "532a09cd3df2c6bbfc795fb0434bff8f22255d1d07328180e918a2e6ce122d4d"
+dependencies = [
+ "log",
+ "plain",
+ "scroll",
+]
+
+[[package]]
+name = "group"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7"
+dependencies = [
+ "ff",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "heapsize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "includedir"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afd126bd778c00c43a9dc76d1609a0894bf4222088088b2217ccc0ce9e816db7"
+dependencies = [
+ "flate2",
+ "phf",
+]
+
+[[package]]
+name = "includedir_codegen"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ac1500c9780957c9808c4ec3b94002f35aab01483833f5a8bce7dfb243e3148"
+dependencies = [
+ "flate2",
+ "phf_codegen",
+ "walkdir",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
+[[package]]
+name = "k256"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b"
+dependencies = [
+ "cfg-if",
+ "ecdsa",
+ "elliptic-curve",
+ "sha2",
+ "sha3",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768"
+dependencies = [
+ "cpufeatures",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.142"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "lru"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
+dependencies = [
+ "hashbrown",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "merkle-cbt"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "171d2f700835121c3b04ccf0880882987a050fd5c7ae88148abf537d33dd3a56"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "molecule"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edc8276c02a006bddad7d1c28c1a88f30421e1b5f0ba0ca96ceb8077c7d20c01"
+dependencies = [
+ "bytes",
+ "cfg-if",
+ "faster-hex",
+]
+
+[[package]]
+name = "numext-constructor"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "621fe0f044729f810c6815cdd77e8f5e0cd803ce4f6a38380ebfc1322af98661"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "numext-fixed-uint"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c68c76f96d589d1009a666c5072f37f3114d682696505f2cf445f27766c7d70"
+dependencies = [
+ "numext-fixed-uint-core",
+ "numext-fixed-uint-hack",
+]
+
+[[package]]
+name = "numext-fixed-uint-core"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6aab1d6457b97b49482f22a92f0f58a2f39bdd7f3b2f977eae67e8bc206aa980"
+dependencies = [
+ "heapsize",
+ "numext-constructor",
+ "rand 0.7.3",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "numext-fixed-uint-hack"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0200f8d55c36ec1b6a8cf810115be85d4814f045e0097dfd50033ba25adb4c9e"
+dependencies = [
+ "numext-fixed-uint-core",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys",
+]
+
+[[package]]
+name = "perun-common"
+version = "0.1.0"
+dependencies = [
+ "blake2b-rs",
+ "ckb-occupied-capacity",
+ "ckb-std",
+ "ckb-types",
+ "k256",
+ "molecule",
+]
+
+[[package]]
+name = "phf"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+dependencies = [
+ "phf_shared",
+ "rand 0.7.3",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "plain"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.9",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
+
+[[package]]
+name = "rfc6979"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
+dependencies = [
+ "crypto-bigint",
+ "hmac",
+ "zeroize",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "scroll"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec"
+dependencies = [
+ "scroll_derive",
+]
+
+[[package]]
+name = "scroll_derive"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "sec1"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "secp256k1"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62"
+dependencies = [
+ "secp256k1-sys",
+]
+
+[[package]]
+name = "secp256k1-sys"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
+
+[[package]]
+name = "serde"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha3"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c"
+dependencies = [
+ "digest",
+ "keccak",
+]
+
+[[package]]
+name = "signature"
+version = "1.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
+dependencies = [
+ "digest",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tests"
+version = "0.1.0"
+dependencies = [
+ "ckb-occupied-capacity",
+ "ckb-standalone-types",
+ "ckb-std",
+ "ckb-testtool",
+ "hex",
+ "k256",
+ "molecule",
+ "perun-common",
+ "rand 0.8.5",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "walkdir"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "zeroize"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
diff --git a/payment-channel-ckb/devnet/contracts/tests/Cargo.toml b/payment-channel-ckb/devnet/contracts/tests/Cargo.toml
new file mode 100644
index 0000000..7d3dbcb
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "tests"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+ckb-testtool = "0.9"
+hex = "0.4.3"
+rand = "0.8.5"
+perun-common = { path = "../contracts/perun-common", default-features = false, features = ["testing"] }
+molecule = "0.7.3"
+ckb-types = { package = "ckb-standalone-types", version = "0.1.2" }
+k256 = { version = "0.11.6", default-features = false, features = ["ecdsa", "arithmetic"]}
+rand_core = { version = "0.6", features = ["getrandom"] }
+ckb-std = "0.10.0"
+ckb-occupied-capacity = "0.108.0"
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/lib.rs b/payment-channel-ckb/devnet/contracts/tests/src/lib.rs
new file mode 100644
index 0000000..c58a205
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/lib.rs
@@ -0,0 +1,63 @@
+use ckb_testtool::ckb_types::bytes::Bytes;
+use std::env;
+use std::fs;
+use std::path::PathBuf;
+use std::str::FromStr;
+
+#[cfg(test)]
+mod perun;
+#[cfg(test)]
+mod tests;
+
+const TEST_ENV_VAR: &str = "CAPSULE_TEST_ENV";
+
+pub enum TestEnv {
+ Debug,
+ Release,
+}
+
+impl FromStr for TestEnv {
+ type Err = &'static str;
+
+ fn from_str(s: &str) -> Result {
+ match s.to_lowercase().as_str() {
+ "debug" => Ok(TestEnv::Debug),
+ "release" => Ok(TestEnv::Release),
+ _ => Err("no match"),
+ }
+ }
+}
+
+pub struct Loader(PathBuf);
+
+impl Default for Loader {
+ fn default() -> Self {
+ let test_env = match env::var(TEST_ENV_VAR) {
+ Ok(val) => val.parse().expect("test env"),
+ Err(_) => TestEnv::Debug,
+ };
+ Self::with_test_env(test_env)
+ }
+}
+
+impl Loader {
+ fn with_test_env(env: TestEnv) -> Self {
+ let load_prefix = match env {
+ TestEnv::Debug => "debug",
+ TestEnv::Release => "release",
+ };
+ let dir = env::current_dir().unwrap();
+ let mut base_path = PathBuf::new();
+ base_path.push(dir);
+ base_path.push("..");
+ base_path.push("build");
+ base_path.push(load_prefix);
+ Loader(base_path)
+ }
+
+ pub fn load_binary(&self, name: &str) -> Bytes {
+ let mut path = self.0.clone();
+ path.push(name);
+ fs::read(path).expect("binary").into()
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/account.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/account.rs
new file mode 100644
index 0000000..6f06ec9
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/account.rs
@@ -0,0 +1,41 @@
+use k256::{ecdsa::SigningKey, PublicKey};
+use rand_core::OsRng;
+use std::fmt::Debug;
+
+pub trait Account: Debug + Clone {
+ fn public_key(&self) -> PublicKey;
+ fn name(&self) -> String;
+}
+
+#[derive(Clone, Debug)]
+pub struct TestAccount {
+ pub sk: SigningKey,
+ pub name: String,
+}
+
+impl TestAccount {
+ pub fn new(sk: SigningKey, name: String) -> Self {
+ Self { sk, name }
+ }
+
+ pub fn new_with_random_key(name: String) -> Self {
+ Self {
+ sk: SigningKey::random(&mut OsRng),
+ name,
+ }
+ }
+
+ pub fn id(&self) -> &str {
+ &self.name
+ }
+}
+
+impl Account for TestAccount {
+ fn public_key(&self) -> PublicKey {
+ PublicKey::from(self.sk.verifying_key())
+ }
+
+ fn name(&self) -> String {
+ self.name.clone()
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/action.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/action.rs
new file mode 100644
index 0000000..71ea179
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/action.rs
@@ -0,0 +1,22 @@
+/// Action is a generic channel action, that occurred in the channel. It is
+/// parameterized by the type of the channel state.
+pub enum Action
+where
+ S: Applyable,
+{
+ Open(S),
+ Fund(S),
+ Abort(S),
+ Send(S),
+ Close(S),
+ ForceClose(S),
+}
+
+/// Applyable allows to apply an action containing the same state type to its
+/// current state.
+pub trait Applyable
+where
+ Self: Clone,
+{
+ fn apply(self, action: &Action) -> Self;
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/channel.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/channel.rs
new file mode 100644
index 0000000..266d0da
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/channel.rs
@@ -0,0 +1,374 @@
+use ckb_testtool::{
+ ckb_types::{
+ packed::{Header, OutPoint, RawHeader, Script},
+ prelude::{Builder, Entity, Pack, Unpack},
+ },
+ context::Context,
+};
+use k256::ecdsa::VerifyingKey;
+use perun_common::{
+ ctrue,
+ perun_types::{ChannelConstants, ChannelStatus, ChannelState},
+};
+
+use crate::perun::{
+ self,
+ test::{keys, Client},
+};
+use crate::perun::{harness, test};
+use std::cmp::PartialEq;
+use std::collections::HashMap;
+use std::fmt::Debug;
+
+use super::{test::cell::FundingCell, Account};
+
+enum ActionValidity {
+ Valid,
+ Invalid,
+}
+
+/// Channel is a Perun test channel. It handles the state of said channel
+/// together with the participants, the current time and surrounding chain
+/// context.
+pub struct Channel<'a, S>
+where
+ S: perun::Applyable + Debug + PartialEq,
+{
+ /// The active party. Actions called on the channel will be issued by this
+ /// party henceforth.
+ active_part: test::Client,
+ /// The id of the channel.
+ id: test::ChannelId,
+ /// The cell which represents this channel on-chain.
+ channel_cell: Option,
+ /// The current state of this channel.
+ channel_state: ChannelStatus,
+ /// The cells locking funds for this channel.
+ funding_cells: Vec,
+ /// The used Perun Channel Type Script.
+ pcts: Script,
+ /// All available parties.
+ parts: HashMap,
+ /// The surrounding chain context.
+ ctx: &'a mut Context,
+ /// The intial test harness environment supplying all Perun specific
+ /// contracts and functionality for deployment etc.
+ env: &'a harness::Env,
+ /// The current channel time.
+ current_time: u64,
+ /// The validity of the next action.
+ validity: ActionValidity,
+ /// The history of actions performed on this channel.
+ history: Vec>,
+ /// The currently tracked channel state as produced by the unit under test.
+ current_state: S,
+}
+
+/// call_action! is a macro that calls the given action on the currently active
+/// participant. It also sets the validity of the next action to `Valid`.
+macro_rules! call_action {
+ ($self:ident, $action:ident $(, $x:expr)*$(,)*) => (
+ {
+ println!("calling action {} on {}", stringify!($action), $self.active_part.name());
+ let res = match $self.validity {
+ ActionValidity::Valid => $self.active_part.$action($self.ctx, $self.env, $($x),*),
+ ActionValidity::Invalid => {
+ let res = $self.active_part.$action($self.ctx, $self.env, $($x),*);
+ match res {
+ Ok(_) => Err(perun::Error::new("action should have failed")),
+ Err(_) => Ok(Default::default()),
+ }
+ }
+ };
+ $self.validity = ActionValidity::Valid;
+ res
+ }
+)
+}
+
+impl<'a, S> Channel<'a, S>
+where
+ S: Default + perun::Applyable + Debug + PartialEq,
+{
+ pub fn new(
+ context: &'a mut Context,
+ env: &'a perun::harness::Env,
+ parts: &[perun::TestAccount],
+ ) -> Self {
+ let m_parts: HashMap<_, _> = parts
+ .iter()
+ .enumerate()
+ .map(|(i, p)| {
+ (
+ p.name().clone(),
+ perun::test::Client::new(i as u8, p.name(), p.sk.clone()),
+ )
+ })
+ .collect();
+ let active = m_parts.get(&parts[0].name()).expect("part not found");
+
+ Channel {
+ id: test::ChannelId::new(),
+ current_time: 0,
+ ctx: context,
+ env,
+ pcts: Script::default(),
+ channel_cell: None,
+ channel_state: ChannelStatus::default(),
+ funding_cells: Vec::new(),
+ active_part: active.clone(),
+ parts: m_parts.clone(),
+ validity: ActionValidity::Valid,
+ history: Vec::new(),
+ current_state: S::default(),
+ }
+ }
+
+ /// with sets the currently active participant to the given `part`.
+ pub fn with(&mut self, part: &str) -> &mut Self {
+ self.active_part = self.parts.get(part).expect("part not found").clone();
+ self
+ }
+
+ /// delay the environment by the given `duration`, this makes the next
+ /// transaction receive a block_header with a timestamp that is `duration`
+ /// in the future.
+ pub fn delay(&mut self, duration: u64) {
+ self.current_time += duration;
+ }
+
+ /// open a channel using the currently active participant set by `with(..)`
+ /// with the value given in `funding_agreement`.
+ pub fn open(&mut self, funding_agreement: &test::FundingAgreement) -> Result<(), perun::Error> {
+ let (id, or) = call_action!(self, open, funding_agreement)?;
+ self.id = id;
+ self.channel_cell = Some(or.channel_cell.clone());
+ // Make sure the channel cell is linked to a header with a timestamp.
+ self.push_header_with_cell(or.channel_cell);
+ let mut fs = self.funding_cells.clone();
+ fs.extend(or.funds_cells.iter().cloned());
+ self.funding_cells = fs.to_vec();
+ self.pcts = or.pcts;
+ self.channel_state = or.state;
+ Ok(())
+ }
+
+ fn push_header_with_cell(&mut self, cell: OutPoint) {
+ let header = Header::new_builder()
+ .raw(
+ RawHeader::new_builder()
+ .timestamp(self.current_time.pack())
+ .build(),
+ )
+ .build()
+ .into_view();
+ self.ctx.insert_header(header.clone());
+ // We will always use 0 as the `tx_index`.
+ self.ctx.link_cell_with_block(cell, header.hash(), 0);
+ }
+
+ /// fund a channel using the currently active participant set by `with(..)`
+ /// with the value given in `funding_agreement`.
+ pub fn fund(&mut self, funding_agreement: &test::FundingAgreement) -> Result<(), perun::Error> {
+ // TODO: Lift this check into the type-system to make this more readable and stick to DRY.
+ let res = match &self.channel_cell {
+ Some(channel_cell) => {
+ call_action!(
+ self,
+ fund,
+ self.id,
+ funding_agreement,
+ channel_cell.clone(),
+ self.channel_state.clone(),
+ self.pcts.clone()
+ )
+ }
+ None => panic!("no channel cell, invalid test setup"),
+ }?;
+ // TODO: DRY please.
+ self.channel_state = res.state;
+ self.channel_cell = Some(res.channel_cell.clone());
+ self.push_header_with_cell(res.channel_cell);
+ let mut fs = self.funding_cells.clone();
+ fs.extend(res.funds_cells.iter().cloned());
+ self.funding_cells = fs.to_vec();
+ Ok(())
+ }
+
+ /// send a payment using the currently active participant set by `with(..)`
+ /// to the given `to` participant.
+ pub fn send(&mut self, to: &P, amount: u64) -> Result<(), perun::Error> {
+ let to = self.parts.get(&to.name()).expect("part not found");
+ self.active_part.send(self.ctx, self.env)
+ }
+
+ /// dispute a channel using the currently active participant set by
+ /// `with(..)`.
+ pub fn dispute(&mut self) -> Result<(), perun::Error> {
+ self.channel_state = self.channel_state.clone().as_builder().disputed(ctrue!()).build();
+ let sigs = self.sigs_for_channel_state()?;
+ let res = match &self.channel_cell {
+ Some(channel_cell) => {
+ call_action!(
+ self,
+ dispute,
+ self.id,
+ channel_cell.clone(),
+ self.channel_state.clone(),
+ self.pcts.clone(),
+ sigs,
+ )
+ }
+ None => panic!("no channel cell, invalid test setup"),
+ }?;
+ self.channel_cell = Some(res.channel_cell.clone());
+ self.push_header_with_cell(res.channel_cell);
+ Ok(())
+ }
+
+ /// abort a channel using the currently active participant set by
+ /// `with(..)`.
+ pub fn abort(&mut self) -> Result<(), perun::Error> {
+ match &self.channel_cell {
+ Some(channel_cell) => {
+ call_action!(
+ self,
+ abort,
+ self.id,
+ self.channel_state.clone(),
+ channel_cell.clone(),
+ self.funding_cells.clone()
+ )
+ }
+ None => panic!("no channel cell, invalid test setup"),
+ }?;
+ Ok(())
+ }
+
+ /// finalize finalizes the channel state in use. It has to be called for
+ /// before successful close actions. It bumps the version of the channel state.
+ pub fn finalize(&mut self) -> &mut Self {
+ let status = self.channel_state.clone();
+ let old_version: u64 = status.state().version().unpack();
+ let state = status.state().as_builder().is_final(ctrue!()).version((old_version + 1).pack()).build();
+ self.channel_state = status.as_builder().state(state).build();
+ self
+ }
+
+ pub fn update(&mut self, update: impl Fn(&ChannelState) -> Result) -> &mut Self {
+ let new_state = update(&self.channel_state.state()).expect("update failed");
+ self.channel_state = self.channel_state
+ .clone()
+ .as_builder()
+ .state(new_state)
+ .build();
+ self
+ }
+
+ /// close a channel using the currently active participant set by
+ /// `with(..)`.
+ pub fn close(&mut self) -> Result<(), perun::Error> {
+ let sigs = self.sigs_for_channel_state()?;
+ match self.channel_cell.clone() {
+ Some(channel_cell) => call_action!(
+ self,
+ close,
+ self.id,
+ channel_cell,
+ self.funding_cells.clone(),
+ self.channel_state.clone(),
+ sigs
+ ),
+ None => panic!("no channel cell, invalid test setup"),
+ }?;
+ Ok(())
+ }
+
+ fn sigs_for_channel_state(&self) -> Result<[Vec; 2], perun::Error> {
+ // We have to unpack the ChannelConstants like this. Otherwise the molecule header is still
+ // part of the slice. On-chain we have no problem due to unpacking the arguments, but this
+ // does not seem possible in this scope.
+ let bytes = self.pcts.args().raw_data();
+ // We want to have the correct order of clients in an array to construct signatures. For
+ // consistency we use the ChannelConstants which are also used to construct the channel and
+ // look up the participants according to their public key identifier.
+ let s = ChannelConstants::from_slice(&bytes)?;
+ let resolve_client = |verifying_key_raw: Vec| -> Result {
+ let verifying_key = VerifyingKey::from_sec1_bytes(verifying_key_raw.as_slice())?;
+ let pubkey = keys::verifying_key_to_byte_array(&verifying_key);
+ self.parts
+ .values()
+ .cloned()
+ .find(|c| c.pubkey() == pubkey)
+ .ok_or("unknown participant in channel parameters".into())
+ };
+ let clients: Result, _> = s
+ .params()
+ .mk_party_pubkeys()
+ .iter()
+ .cloned()
+ .map(resolve_client)
+ .collect();
+ let sigs: Result, _> = clients?
+ .iter()
+ .map(|p| p.sign(self.channel_state.state()))
+ .collect();
+ let sig_arr: [Vec; 2] = sigs?.try_into()?;
+ Ok(sig_arr)
+ }
+
+ /// force_close a channel using the currently active participant set by
+ /// `with(..)`.
+ pub fn force_close(&mut self) -> Result<(), perun::Error> {
+ let h = Header::new_builder()
+ .raw(
+ RawHeader::new_builder()
+ .timestamp(self.current_time.pack())
+ .build(),
+ )
+ .build()
+ .into_view();
+ // Push a header with the current time which can be used in force_close
+ // as for time validation purposes.
+ self.ctx.insert_header(h.clone());
+ match self.channel_cell.clone() {
+ Some(channel_cell) => call_action!(
+ self,
+ force_close,
+ self.id,
+ channel_cell,
+ self.funding_cells.clone(),
+ self.channel_state.clone(),
+ ),
+ None => panic!("no channel cell, invalid test setup"),
+ }?;
+ Ok(())
+ }
+
+ /// valid sets the validity of the next action to valid. (default)
+ pub fn valid(&mut self) -> &mut Self {
+ self.validity = ActionValidity::Valid;
+ self
+ }
+
+ /// invalid sets the validity of the next action to invalid. It resets to
+ /// valid after the next action.
+ pub fn invalid(&mut self) -> &mut Self {
+ self.validity = ActionValidity::Invalid;
+ self
+ }
+
+ /// assert asserts that the channel is in a valid state according to all
+ /// actions that have been performed on it. This also includes the
+ /// surrounding context for this channel.
+ ///
+ /// If a channel was closed, it will also assert that all participants
+ /// were properly paid.
+ pub fn assert(&self) {
+ let expected_state: S = self
+ .history
+ .iter()
+ .fold(Default::default(), |acc, act| acc.apply(act));
+ assert_eq!(expected_state, self.current_state)
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/error.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/error.rs
new file mode 100644
index 0000000..6af554f
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/error.rs
@@ -0,0 +1,65 @@
+use molecule::error::VerificationError;
+use std::{error, fmt};
+
+use ckb_testtool::ckb_error;
+
+#[derive(Debug)]
+pub struct Error {
+ details: String,
+}
+
+impl Error {
+ pub fn new(msg: &str) -> Error {
+ Error {
+ details: msg.to_string(),
+ }
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.details)
+ }
+}
+
+impl error::Error for Error {
+ fn description(&self) -> &str {
+ &self.details
+ }
+}
+
+impl From<&str> for Error {
+ fn from(err: &str) -> Error {
+ Error::new(err)
+ }
+}
+
+impl From for Error {
+ fn from(err: ckb_occupied_capacity::Error) -> Error {
+ Error::new(&err.to_string())
+ }
+}
+
+impl From for Error {
+ fn from(err: ckb_error::Error) -> Error {
+ Error::new(&err.to_string())
+ }
+}
+
+impl From for Error {
+ fn from(err: k256::ecdsa::Error) -> Error {
+ Error::new(&err.to_string())
+ }
+}
+
+impl From for Error {
+ fn from(err: VerificationError) -> Error {
+ Error::new(&err.to_string())
+ }
+}
+
+impl From>> for Error {
+ fn from(vs: Vec>) -> Error {
+ Error::new(&format!("converting from nested vectors: {:?}", vs))
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/harness.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/harness.rs
new file mode 100644
index 0000000..4e288fc
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/harness.rs
@@ -0,0 +1,285 @@
+use crate::perun;
+use crate::Loader;
+use ckb_occupied_capacity::{Capacity, IntoCapacity};
+use ckb_testtool::{
+ builtin::ALWAYS_SUCCESS,
+ ckb_types::{bytes::Bytes, packed::*, prelude::*},
+ context::Context,
+};
+use perun_common::cfalse;
+use perun_common::perun_types::ChannelStateBuilder;
+use perun_common::perun_types::ChannelStatusBuilder;
+use perun_common::perun_types::{self, ChannelStatus, ChannelToken};
+
+use super::test::ChannelId;
+use super::test::FundingAgreement;
+use super::test::FundingAgreementEntry;
+
+// Env contains all chain information required for running Perun
+// tests.
+pub struct Env {
+ // Perun contracts.
+ pub pcls_out_point: OutPoint,
+ pub pcts_out_point: OutPoint,
+ pub pfls_out_point: OutPoint,
+ // Auxiliary contracts.
+ pub always_success_out_point: OutPoint,
+ pub sample_udt_out_point: OutPoint,
+
+ // Perun scripts.
+ pcls_script: Script,
+ pcts_script: Script,
+ pfls_script: Script,
+ pub pcls_script_dep: CellDep,
+ pub pcts_script_dep: CellDep,
+ pub pfls_script_dep: CellDep,
+ // Auxiliary scripts.
+ pub always_success_script: Script,
+ pub always_success_script_dep: CellDep,
+ pub sample_udt_script: Script,
+ pub sample_udt_script_dep: CellDep,
+ // Maximum amount of cycles used when verifying TXs.
+ pub max_cycles: u64,
+ pub min_capacity_no_script: Capacity,
+ pub min_capacity_pfls: Capacity,
+ pub sample_udt_max_cap: Capacity,
+ pub challenge_duration: u64,
+}
+
+impl Env {
+ // prepare_env prepares the given context to be used for running Perun
+ // tests.
+ pub fn new(
+ context: &mut Context,
+ max_cycles: u64,
+ challenge_duration: u64,
+ ) -> Result {
+ // Perun contracts.
+ let pcls: Bytes = Loader::default().load_binary("perun-channel-lockscript");
+ let pcts: Bytes = Loader::default().load_binary("perun-channel-typescript");
+ let pfls: Bytes = Loader::default().load_binary("perun-funds-lockscript");
+ let sample_udt: Bytes = Loader::default().load_binary("sample-udt");
+ // Deploying the contracts returns the cell they are deployed in.
+ let pcls_out_point = context.deploy_cell(pcls);
+ let pcts_out_point = context.deploy_cell(pcts);
+ let pfls_out_point = context.deploy_cell(pfls);
+ let sample_udt_out_point = context.deploy_cell(sample_udt);
+ // Auxiliary contracts.
+ let always_success_out_point = context.deploy_cell(ALWAYS_SUCCESS.clone());
+
+ // Prepare scripts.
+ // Perun scripts.
+ let pcls_script = context
+ .build_script(&pcls_out_point, Default::default())
+ .ok_or("perun-channel-lockscript")?;
+ let pcts_script = context
+ .build_script(
+ &pcts_out_point,
+ perun_types::ChannelConstants::default().as_bytes(),
+ )
+ .ok_or("perun-channel-typescript")?;
+ let pfls_script = context
+ .build_script(&pfls_out_point, Default::default())
+ .ok_or("perun-funds-lockscript")?;
+ let sample_udt_script = context
+ .build_script(&sample_udt_out_point, Default::default())
+ .ok_or("sample-udt")?;
+ let pcls_script_dep = CellDep::new_builder()
+ .out_point(pcls_out_point.clone())
+ .build();
+ let pcts_script_dep = CellDep::new_builder()
+ .out_point(pcts_out_point.clone())
+ .build();
+ let pfls_script_dep = CellDep::new_builder()
+ .out_point(pfls_out_point.clone())
+ .build();
+ let sample_udt_script_dep = CellDep::new_builder()
+ .out_point(sample_udt_out_point.clone())
+ .build();
+ let sample_udt_max_cap = sample_udt_script.occupied_capacity()?.safe_mul(Capacity::shannons(10))?;
+ // Auxiliary scripts.
+ let always_success_script = context
+ .build_script(&always_success_out_point, Bytes::from(vec![0]))
+ .expect("always_success");
+ let always_success_script_dep = CellDep::new_builder()
+ .out_point(always_success_out_point.clone())
+ .build();
+
+ // Calculate minimum amount of capacity required for a cell using the always success script.
+ let tmp_output = CellOutput::new_builder()
+ .capacity(0u64.pack())
+ .lock(always_success_script.clone())
+ .build();
+ let min_capacity_no_script = tmp_output.occupied_capacity(0u64.into_capacity())?;
+
+ // Calculate minimum amount of capacity required for a cell using the PFLS script.
+ let tmp_output = CellOutput::new_builder()
+ .capacity(0u64.pack())
+ .lock(pfls_script.clone())
+ .build();
+ let pfls_args_capacity = pcts_script.calc_script_hash().as_bytes().len() as u64;
+ let min_capacity_pfls = tmp_output.occupied_capacity(pfls_args_capacity.into_capacity())?;
+ println!("pfls code hash: {}", pfls_script.code_hash());
+ println!("asset code hash: {}", sample_udt_script.code_hash());
+ println!("pcts code hash: {}", pcts_script.code_hash());
+ println!("pcls code hash: {}", pcls_script.code_hash());
+ println!("always_success code hash: {}", always_success_script.code_hash());
+ Ok(Env {
+ pcls_out_point,
+ pcts_out_point,
+ pfls_out_point,
+ always_success_out_point,
+ sample_udt_out_point,
+ pcls_script,
+ pcts_script,
+ pfls_script,
+ pcls_script_dep,
+ pcts_script_dep,
+ pfls_script_dep,
+ always_success_script,
+ always_success_script_dep,
+ sample_udt_script,
+ sample_udt_script_dep,
+ max_cycles,
+ min_capacity_no_script,
+ min_capacity_pfls,
+ sample_udt_max_cap,
+ challenge_duration,
+ })
+ }
+
+ pub fn build_pcls(&self, context: &mut Context, args: Bytes) -> Script {
+ let pcls_out_point = &self.pcls_out_point;
+ context
+ .build_script(pcls_out_point, args)
+ .expect("perun-channel-lockscript")
+ }
+
+ pub fn build_pcts(&self, context: &mut Context, args: Bytes) -> Script {
+ let pcts_out_point = &self.pcts_out_point;
+ context
+ .build_script(pcts_out_point, args)
+ .expect("perun-channel-typescript")
+ }
+
+ pub fn build_pfls(&self, context: &mut Context, args: Bytes) -> Script {
+ let pfls_out_point = &self.pfls_out_point;
+ context
+ .build_script(pfls_out_point, args)
+ .expect("perun-funds-lockscript")
+ }
+
+ pub fn build_lock_script(&self, context: &mut Context, args: Bytes) -> Script {
+ let always_success_out_point = &self.always_success_out_point;
+ context
+ .build_script(always_success_out_point, args)
+ .expect("always_success")
+ }
+
+ pub fn min_capacity_for_channel(&self, cs: ChannelStatus) -> Result {
+ let tmp_output = CellOutput::new_builder()
+ .capacity(0u64.pack())
+ .lock(self.pcls_script.clone())
+ .type_(Some(self.pcts_script.clone()).pack())
+ .build();
+ let cs_capacity = Capacity::bytes(cs.as_bytes().len())?;
+ let min_capacity = tmp_output.occupied_capacity(cs_capacity)?;
+ Ok(min_capacity)
+ }
+
+ pub fn create_channel_token(&self, context: &mut Context) -> (ChannelToken, OutPoint) {
+ let channel_token_outpoint = context.create_cell(
+ CellOutput::new_builder()
+ .capacity(self.min_capacity_no_script.pack())
+ .lock(self.always_success_script.clone())
+ .build(),
+ Bytes::default(),
+ );
+ let packed_outpoint = OutPointBuilder::default()
+ .tx_hash(channel_token_outpoint.tx_hash())
+ .index(channel_token_outpoint.index())
+ .build();
+ (
+ perun_types::ChannelTokenBuilder::default()
+ .out_point(packed_outpoint.clone())
+ .build(),
+ packed_outpoint,
+ )
+ }
+
+ /// create_funds_from_agreement creates a new cell with the funds for the given party index locked
+ /// by the always_success_script parameterized on the party index.
+ pub fn create_funds_from_agreement(
+ &self,
+ context: &mut Context,
+ party_index: u8,
+ funding_agreement: &FundingAgreement,
+ ) -> Result, perun::Error> {
+ let mut funds = self.create_ckbytes_funds_for_index(context, party_index, funding_agreement.expected_ckbytes_funding_for(party_index)?)?;
+ funds.append(self.create_sudts_funds_for_index(context, party_index, funding_agreement.expected_sudts_funding_for(party_index)?)?.as_mut());
+ return Ok(funds);
+ }
+
+ pub fn create_ckbytes_funds_for_index(
+ &self,
+ context: &mut Context,
+ party_index: u8,
+ required_funds: u64,
+ ) -> Result, perun::Error> {
+ // Create cell containing the required funds for this party.
+ let my_output = CellOutput::new_builder()
+ .capacity(required_funds.pack())
+ // Lock cell using the correct party index.
+ .lock(self.build_lock_script(context, Bytes::from(vec![party_index])))
+ .build();
+ let cell = context.create_cell(my_output.clone(), Bytes::default());
+ Ok(vec![(cell, required_funds.into_capacity())])
+ }
+
+ pub fn create_sudts_funds_for_index(&self, context: &mut Context, party_index: u8, required_funds: Vec<(Script, Capacity, u128)>) -> Result, perun::Error> {
+ let mut outs: Vec<(OutPoint, Capacity)> = Vec::new();
+ for (sudt_script, capacity, amount) in required_funds {
+ let my_output = CellOutput::new_builder()
+ .capacity(capacity.pack())
+ // Lock cell using the correct party index.
+ .lock(self.build_lock_script(context, Bytes::from(vec![party_index])))
+ .type_(Some(sudt_script).pack())
+ .build();
+ let cell = context.create_cell(my_output.clone(), Bytes::from(amount.to_le_bytes().to_vec()));
+ outs.push((cell, capacity));
+ }
+ Ok(outs)
+ }
+
+ pub fn create_min_cell_for_index(&self, context: &mut Context, party_index: u8) -> OutPoint {
+ self.create_ckbytes_funds_for_index(context, party_index, self.min_capacity_no_script.as_u64())
+ .unwrap()
+ .get(0).unwrap().clone().0
+ }
+
+ pub fn build_initial_channel_state(
+ &self,
+ channel_id: ChannelId,
+ client_index: u8,
+ funding_agreement: &FundingAgreement,
+ ) -> Result {
+ let all_indices = funding_agreement
+ .content()
+ .iter()
+ .map(|FundingAgreementEntry { index, .. }| *index)
+ .collect::>();
+ let channel_balances = funding_agreement.mk_balances(all_indices)?;
+ let channel_state = ChannelStateBuilder::default()
+ .channel_id(channel_id.to_byte32())
+ .balances(channel_balances)
+ .version(Default::default())
+ .is_final(cfalse!())
+ .build();
+ let channel_status = ChannelStatusBuilder::default()
+ .state(channel_state)
+ .funded(cfalse!())
+ .disputed(cfalse!())
+ .build();
+ Ok(channel_status)
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/mod.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/mod.rs
new file mode 100644
index 0000000..cbf1768
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/mod.rs
@@ -0,0 +1,22 @@
+#[cfg(test)]
+pub mod harness;
+
+mod error;
+pub use error::*;
+
+pub mod channel;
+
+pub mod mutators;
+
+pub mod test;
+
+mod action;
+pub use action::*;
+
+mod state;
+pub use state::*;
+
+pub mod random;
+
+mod account;
+pub use account::*;
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/mutators.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/mutators.rs
new file mode 100644
index 0000000..24abb8c
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/mutators.rs
@@ -0,0 +1,69 @@
+use crate::perun;
+use ckb_testtool::ckb_types::prelude::{Unpack, Pack};
+use molecule::prelude::{Entity, Builder};
+use perun_common::perun_types::{ChannelState, CKByteDistribution, SUDTDistribution};
+
+pub enum Direction {
+ AtoB,
+ BtoA,
+}
+
+/// id returns a mutator that does not change the channel state.
+pub fn id() -> impl Fn(&ChannelState) -> Result {
+ |s| Ok(s.clone())
+}
+
+/// bump_version returns a mutator that bumps the version number of the channel state.
+pub fn bump_version() -> impl Fn(&ChannelState) -> Result {
+ |s| Ok(s.clone().as_builder().version((Unpack::::unpack(&s.version()) + 1u64).pack()).build())
+}
+
+/// pay_ckbytes returns a mutator that transfers the given amount of CKBytes from one party to the other according to the
+/// specified direction. It also bumps the version number of the channel state.
+pub fn pay_ckbytes(direction: Direction, amount: u64) -> impl Fn(&ChannelState) -> Result {
+ let (sender_index, receiver_index) = get_indices(direction);
+ move |s| {
+ let s_bumped = bump_version()(s)?;
+ let mut distribution = s_bumped.balances().ckbytes().to_array();
+ if distribution[sender_index] < amount {
+ return Err(perun::Error::new("insufficient funds"));
+ }
+ distribution[sender_index] -= amount;
+ distribution[receiver_index] += amount;
+ let balances = s_bumped.balances().clone().as_builder().ckbytes(CKByteDistribution::from_array(distribution)).build();
+ Ok(s_bumped.clone().as_builder().balances(balances).build())
+ }
+}
+
+/// pay_sudt returns a mutator that transfers the given amount of the specified SUDT index from one party to the other according to the
+/// specified direction. It also bumps the version number of the channel state.
+pub fn pay_sudt(direction:Direction, amount: u128, asset_index: usize)-> impl Fn(&ChannelState) -> Result {
+ let (sender_index, receiver_index) = get_indices(direction);
+ move |s| {
+ let s_bumped = bump_version()(s)?;
+ let sudts = s_bumped.balances().sudts().clone();
+ if asset_index >= sudts.len() {
+ return Err(perun::Error::new("asset index out of bounds"));
+ }
+ let sudt = sudts.get(asset_index).unwrap();
+ let mut distribution = sudt.distribution().to_array();
+ if distribution[sender_index] < amount {
+ return Err(perun::Error::new("insufficient funds"));
+ }
+ distribution[sender_index] -= amount;
+ distribution[receiver_index] += amount;
+ let packed_sudt = sudt.clone().as_builder().distribution(SUDTDistribution::from_array(distribution)).build();
+ let mut new_sudts = sudts.clone().as_builder();
+ new_sudts.replace(asset_index, packed_sudt).unwrap();
+ let balances = s_bumped.balances().clone().as_builder().sudts(new_sudts.build()).build();
+ Ok(s_bumped.clone().as_builder().balances(balances).build())
+ }
+}
+
+/// get_indices returns (sender_index, receiver_index)
+fn get_indices(direction: Direction) -> (usize, usize) {
+ match direction {
+ Direction::AtoB => (0, 1),
+ Direction::BtoA => (1, 0),
+ }
+}
\ No newline at end of file
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/random.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/random.rs
new file mode 100644
index 0000000..3351e08
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/random.rs
@@ -0,0 +1,13 @@
+use rand::Rng;
+
+use super::TestAccount;
+
+pub fn nonce() -> [u8; 32] {
+ let mut rng = rand::thread_rng();
+ let nonce: [u8; 32] = rng.gen();
+ nonce
+}
+
+pub fn account(name: &str) -> TestAccount {
+ TestAccount::new_with_random_key(name.to_string())
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/state.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/state.rs
new file mode 100644
index 0000000..a06a364
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/state.rs
@@ -0,0 +1,17 @@
+use crate::perun::{Action, Applyable};
+
+#[derive(Debug, Clone, Default, PartialEq)]
+pub struct State {}
+
+impl Applyable for State {
+ fn apply(self, action: &Action) -> Self {
+ match action {
+ Action::Open(_) => self,
+ Action::Fund(_) => self,
+ Action::Abort(_) => self,
+ Action::Send(_) => self,
+ Action::Close(_) => self,
+ Action::ForceClose(_) => self,
+ }
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/cell.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/cell.rs
new file mode 100644
index 0000000..668d262
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/cell.rs
@@ -0,0 +1,74 @@
+use ckb_testtool::{ckb_types::{packed::{OutPoint, CellOutput}, prelude::{Unpack, Pack}}};
+use ckb_types::bytes;
+use molecule::prelude::{Entity, Builder};
+
+use super::{Asset, AssetRegister};
+
+
+#[derive(Debug, Clone)]
+
+pub enum FundingCell {
+ FundingCellCKBytes(FundingCellCKBytes),
+ FundingCellSUDT(FundingCellSUDT),
+}
+
+#[derive(Debug, Clone)]
+pub struct FundingCellCKBytes {
+ // Index of the party whose initial funds are contained in this cell.
+ pub index: u8,
+ // The amount of funding for the party given by index.
+ pub cap: u64,
+ // The outpoint of the cell containing the funds.
+ pub out_point: OutPoint,
+}
+
+#[derive(Debug, Clone)]
+pub struct FundingCellSUDT {
+ // Index of the party whose initial funds are contained in this cell.
+ pub index: u8,
+ // The amount of funding for the party given by index.
+ pub cap: u64,
+ // The outpoint of the cell containing the funds.
+ pub out_point: OutPoint,
+ pub asset: Asset,
+ pub asset_amount: u128,
+}
+
+impl Default for FundingCell {
+ fn default() -> Self {
+ FundingCell::FundingCellCKBytes(FundingCellCKBytes {
+ index: 0,
+ cap: 0,
+ out_point: OutPoint::default(),
+ })
+ }
+}
+
+pub fn mk_funding_cell(party_index: u8, out_point: OutPoint, cell_output: &CellOutput, data: bytes::Bytes, register: &AssetRegister) -> FundingCell {
+ if cell_output.type_().is_some(){
+ let asset = register.guess_asset_from_script(&cell_output.type_().to_opt().unwrap()).unwrap();
+ FundingCell::FundingCellSUDT(FundingCellSUDT {
+ index: party_index,
+ cap: cell_output.capacity().unpack(),
+ out_point,
+ asset: asset.clone(),
+ asset_amount: u128::from_le_bytes(data.to_vec().as_slice().try_into().unwrap()),
+ })
+ } else {
+ FundingCell::FundingCellCKBytes(FundingCellCKBytes {
+ index: party_index,
+ cap: cell_output.capacity().unpack(),
+ out_point,
+ })
+ }
+
+}
+
+impl FundingCell {
+ pub fn outpoint(&self) -> OutPoint {
+ match self {
+ FundingCell::FundingCellCKBytes(f) => f.out_point.clone(),
+ FundingCell::FundingCellSUDT(f) => f.out_point.clone(),
+ }
+ }
+}
\ No newline at end of file
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/channel_id.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/channel_id.rs
new file mode 100644
index 0000000..3b0f445
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/channel_id.rs
@@ -0,0 +1,38 @@
+use ckb_testtool::ckb_types::{
+ packed::{Byte, Byte32, Byte32Builder},
+ prelude::Builder,
+};
+use rand::Rng;
+
+#[derive(Debug, Clone, Copy)]
+pub struct ChannelId([u8; 32]);
+
+impl ChannelId {
+ pub fn new() -> Self {
+ ChannelId(Default::default())
+ }
+
+ pub fn new_random() -> Self {
+ ChannelId(rand::thread_rng().gen())
+ }
+
+ pub fn to_byte32(&self) -> Byte32 {
+ let mut byte32: [Byte; 32] = [0u8.into(); 32];
+ let x = self.0;
+ let y = x.iter().map(|x| (*x).into()).collect::>();
+ byte32.copy_from_slice(&y);
+ Byte32Builder::default().set(byte32).build()
+ }
+}
+
+impl From<[u8; 32]> for ChannelId {
+ fn from(bytes: [u8; 32]) -> Self {
+ ChannelId(bytes)
+ }
+}
+
+impl Default for ChannelId {
+ fn default() -> Self {
+ ChannelId([0u8; 32])
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/client.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/client.rs
new file mode 100644
index 0000000..cdc8aec
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/client.rs
@@ -0,0 +1,259 @@
+use ckb_testtool::ckb_traits::CellDataProvider;
+
+use ckb_testtool::ckb_types::core::ScriptHashType;
+use ckb_testtool::ckb_types::packed::{OutPoint, Script};
+use ckb_testtool::ckb_types::prelude::*;
+use ckb_testtool::context::Context;
+
+use k256::ecdsa::signature::hazmat::PrehashSigner;
+use perun_common::*;
+
+use perun_common::helpers::blake2b256;
+use perun_common::perun_types::{ChannelState, ChannelStatus};
+
+use crate::perun;
+use crate::perun::harness;
+use crate::perun::random;
+use crate::perun::test;
+use crate::perun::test::transaction::{AbortArgs, OpenResult};
+use crate::perun::test::{keys, transaction};
+
+use k256::ecdsa::{Signature, SigningKey};
+
+use super::cell::FundingCell;
+use super::ChannelId;
+
+#[derive(Clone, Debug)]
+pub struct Client {
+ index: u8,
+ signing_key: SigningKey,
+ name: String,
+}
+
+impl Client {
+ pub fn new(idx: u8, name: String, sk: SigningKey) -> Client {
+ Client {
+ index: idx,
+ name,
+ signing_key: sk,
+ }
+ }
+
+ // pubkey returns the public key of the client as a SEC1 encoded byte
+ // array.
+ pub fn pubkey(&self) -> [u8; 33] {
+ keys::verifying_key_to_byte_array(&self.signing_key.verifying_key())
+ }
+
+ pub fn name(&self) -> String {
+ self.name.clone()
+ }
+
+ pub fn open(
+ &self,
+ ctx: &mut Context,
+ env: &harness::Env,
+ funding_agreement: &test::FundingAgreement,
+ ) -> Result<(ChannelId, OpenResult), perun::Error> {
+ // Prepare environment so that this party has the required funds.
+ let inputs =
+ env.create_funds_from_agreement(ctx, self.index, funding_agreement)?;
+ // Create the channel token.
+ let (channel_token, channel_token_outpoint) = env.create_channel_token(ctx);
+
+ let pcls = env.build_pcls(ctx, Default::default());
+ let pcls_code_hash = pcls.code_hash();
+ let pfls_code_hash = ctx
+ .get_cell_data_hash(&env.pfls_out_point)
+ .expect("pfls hash");
+ let always_success_hash = ctx
+ .get_cell_data_hash(&env.always_success_out_point)
+ .expect("always success hash");
+
+ let parties = funding_agreement.mk_participants(ctx, env, env.min_capacity_no_script);
+
+ let chan_params = perun_types::ChannelParametersBuilder::default()
+ .party_a(parties[0].clone())
+ .party_b(parties[1].clone())
+ .nonce(random::nonce().pack())
+ .challenge_duration(env.challenge_duration.pack())
+ .app(Default::default())
+ .is_ledger_channel(ctrue!())
+ .is_virtual_channel(cfalse!())
+ .build();
+ let cid_raw = blake2b256(chan_params.as_slice());
+ let cid = ChannelId::from(cid_raw);
+ let chan_const = perun_types::ChannelConstantsBuilder::default()
+ .params(chan_params)
+ .pfls_code_hash(pfls_code_hash.clone())
+ .pfls_hash_type(ScriptHashType::Data1.into())
+ .pfls_min_capacity(env.min_capacity_pfls.pack())
+ .pcls_code_hash(pcls_code_hash.clone())
+ .pcls_hash_type(ScriptHashType::Data1.into())
+ .thread_token(channel_token.clone())
+ .build();
+
+ let pcts = env.build_pcts(ctx, chan_const.as_bytes());
+ let pfls = env.build_pfls(ctx, pcts.calc_script_hash().as_bytes());
+
+ let args = transaction::OpenArgs {
+ cid,
+ funding_agreement: funding_agreement.clone(),
+ channel_token_outpoint: channel_token_outpoint.clone(),
+ inputs: inputs,
+ party_index: self.index,
+ pcls_script: pcls,
+ pcts_script: pcts,
+ pfls_script: pfls,
+ };
+ let or = transaction::mk_open(ctx, env, args)?;
+
+ let cycles = ctx.verify_tx(&or.tx, env.max_cycles)?;
+ println!("consumed cycles: {}", cycles);
+ Ok((cid, or))
+ }
+
+ pub fn fund(
+ &self,
+ ctx: &mut Context,
+ env: &harness::Env,
+ _cid: test::ChannelId,
+ funding_agreement: &test::FundingAgreement,
+ channel_cell: OutPoint,
+ channel_state: ChannelStatus,
+ pcts: Script,
+ ) -> Result {
+ // Prepare environment so that this party has the required funds.
+ let inputs =
+ env.create_funds_from_agreement(ctx, self.index, funding_agreement)?;
+ let fr = transaction::mk_fund(
+ ctx,
+ env,
+ transaction::FundArgs {
+ channel_cell,
+ funding_agreement: funding_agreement.clone(),
+ party_index: self.index,
+ state: channel_state,
+ inputs,
+ pcts,
+ },
+ )?;
+ let cycles = ctx.verify_tx(&fr.tx, env.max_cycles)?;
+ println!("consumed cycles: {}", cycles);
+ Ok(fr)
+ }
+
+ pub fn send(&self, ctx: &mut Context, env: &harness::Env) -> Result<(), perun::Error> {
+ Ok(())
+ }
+
+ pub fn sign(&self, state: ChannelState) -> Result, perun::Error> {
+ let s: Signature = self
+ .signing_key
+ .sign_prehash(&blake2b256(state.as_slice()))?;
+ Ok(Vec::from(s.to_der().as_bytes()))
+ }
+
+ pub fn dispute(
+ &self,
+ ctx: &mut Context,
+ env: &harness::Env,
+ _cid: test::ChannelId,
+ channel_cell: OutPoint,
+ channel_state: ChannelStatus,
+ pcts: Script,
+ sigs: [Vec; 2],
+ ) -> Result {
+ let dr = transaction::mk_dispute(
+ ctx,
+ env,
+ transaction::DisputeArgs {
+ channel_cell,
+ state: channel_state,
+ party_index: self.index,
+ pcts_script: pcts,
+ sigs,
+ },
+ )?;
+ let cycles = ctx.verify_tx(&dr.tx, env.max_cycles)?;
+ println!("consumed cycles: {}", cycles);
+ Ok(dr)
+ }
+
+ pub fn abort(
+ &self,
+ ctx: &mut Context,
+ env: &harness::Env,
+ _cid: test::ChannelId,
+ state: ChannelStatus,
+ channel_cell: OutPoint,
+ funds: Vec,
+ ) -> Result {
+ let ar = transaction::mk_abort(
+ ctx,
+ env,
+ AbortArgs {
+ channel_cell,
+ funds,
+ state,
+ party_index: self.index,
+ },
+ )?;
+ let cycles = ctx.verify_tx(&ar.tx, env.max_cycles)?;
+ println!("consumed cycles: {}", cycles);
+ Ok(ar)
+ }
+
+ pub fn close(
+ &self,
+ ctx: &mut Context,
+ env: &harness::Env,
+ _cid: test::ChannelId,
+ channel_cell: OutPoint,
+ funds_cells: Vec,
+ state: ChannelStatus,
+ sigs: [Vec; 2],
+ ) -> Result {
+ let cr = transaction::mk_close(
+ ctx,
+ env,
+ transaction::CloseArgs {
+ channel_cell,
+ funds_cells,
+ party_index: self.index,
+ state,
+ sigs,
+ },
+ )?;
+ let cycles = ctx.verify_tx(&cr.tx, env.max_cycles)?;
+ println!("consumed cycles: {}", cycles);
+ Ok(cr)
+ }
+
+ pub fn force_close(
+ &self,
+ ctx: &mut Context,
+ env: &harness::Env,
+ _cid: test::ChannelId,
+ channel_cell: OutPoint,
+ funds_cells: Vec,
+ state: ChannelStatus,
+ ) -> Result {
+ // We will pass all available headers to the force close transaction.
+ let hs = ctx.headers.keys().cloned().collect();
+ let fcr = transaction::mk_force_close(
+ ctx,
+ env,
+ transaction::ForceCloseArgs {
+ headers: hs,
+ channel_cell,
+ party_index: self.index,
+ funds_cells,
+ state,
+ },
+ )?;
+ let cycles = ctx.verify_tx(&fcr.tx, env.max_cycles)?;
+ println!("consumed cycles: {}", cycles);
+ Ok(fcr)
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/funding_agreement.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/funding_agreement.rs
new file mode 100644
index 0000000..5723b99
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/funding_agreement.rs
@@ -0,0 +1,282 @@
+use ckb_occupied_capacity::Capacity;
+use ckb_testtool::ckb_types::packed::{Byte as PackedByte, Script};
+use ckb_testtool::ckb_types::prelude::*;
+use ckb_testtool::context::Context;
+use ckb_types::bytes::Bytes;
+use k256::elliptic_curve::sec1::ToEncodedPoint;
+use k256::PublicKey;
+use perun_common::perun_types::{
+ self, Balances, CKByteDistribution, ParticipantBuilder,
+ SEC1EncodedPubKeyBuilder, SUDTAllocation, SUDTAsset, SUDTBalances, SUDTDistribution,
+};
+
+use crate::perun;
+
+#[derive(Debug, Clone)]
+pub struct FundingAgreement {
+ entries: Vec,
+ register: AssetRegister,
+}
+
+impl FundingAgreement {
+ pub fn register(&self) -> &AssetRegister {
+ &self.register
+ }
+
+ pub fn has_udts(&self) -> bool {
+ self.register.len() > 0
+ }
+
+ pub fn new_with_capacities(caps: Vec<(P, u64)>) -> Self {
+ FundingAgreement {
+ entries: caps
+ .iter()
+ .enumerate()
+ .map(|(i, (acc, c))| FundingAgreementEntry {
+ ckbytes: *c,
+ sudts: Vec::new(),
+ index: i as u8,
+ pub_key: acc.public_key(),
+ })
+ .collect(),
+ register: AssetRegister::new(),
+ }
+ }
+
+ pub fn new_with_capacities_and_sudt(
+ caps: Vec<(P, u64)>,
+ asset: &Script,
+ max_cap: u64,
+ asset_amt: Vec<(P, u128)>,
+ ) -> Self {
+ let mut r = AssetRegister::new();
+ let a = r.register_asset(
+ SUDTAsset::new_builder()
+ .type_script(asset.clone())
+ .max_capacity(max_cap.pack())
+ .build(),
+ );
+ FundingAgreement {
+ entries: caps
+ .iter()
+ .enumerate()
+ .map(|(i, (acc, c))| FundingAgreementEntry {
+ ckbytes: *c,
+ sudts: vec![(a, asset_amt.get(i).unwrap().1)],
+ index: i as u8,
+ pub_key: acc.public_key(),
+ })
+ .collect(),
+ register: r,
+ }
+ }
+
+ pub fn content(&self) -> &Vec {
+ &self.entries
+ }
+
+ pub fn mk_participants(
+ &self,
+ ctx: &mut Context,
+ env: &perun::harness::Env,
+ payment_min_capacity: Capacity,
+ ) -> Vec {
+ self.entries
+ .iter()
+ .map(|entry| {
+ let sec1_encoded_bytes: Vec<_> = entry
+ .pub_key
+ .to_encoded_point(true)
+ .as_bytes()
+ .iter()
+ .map(|b| PackedByte::new(*b))
+ .collect();
+ let sec1_pub_key = SEC1EncodedPubKeyBuilder::default()
+ .set(sec1_encoded_bytes.try_into().unwrap())
+ .build();
+ let unlock_script = ctx
+ .build_script(
+ &env.always_success_out_point,
+ // NOTE: To be able to make sure we can distinguish between the payout of
+ // the participants, we will pass their corresponding index as an argument.
+ // This will have no effect on the execution of the always_success_script,
+ // because it does not bother checking its arguments, but will allow us to
+ // assert the correct indices once a channel is concluded.
+ Bytes::from(vec![entry.index]),
+ )
+ .expect("script");
+ let unlock_script_hash = unlock_script.calc_script_hash();
+ ParticipantBuilder::default()
+ // The payment script hash used to lock the funds after a channel close for
+ // this party.
+ .payment_script_hash(unlock_script_hash.clone())
+ // The minimum capacity required for the payment cell to be valid.
+ .payment_min_capacity(payment_min_capacity.pack())
+ // The unlock script hash used to identify this party. Normally this would be
+ // the lock args for a secp256k1 script or similar. Since we use the always
+ // success script, we will use the hash of said script parameterized by the
+ // party index.
+ .unlock_script_hash(unlock_script_hash.clone())
+ .pub_key(sec1_pub_key)
+ .build()
+ })
+ .collect()
+ }
+
+ /// mk_balances creates a Balances object from the funding agreement where the given indices
+ /// already funded their part.
+ pub fn mk_balances(&self, indices: Vec) -> Result {
+ let mut ckbytes = [0u64; 2];
+ let sudts = self.register.get_sudtassets();
+ let mut sudt_dist: Vec<[u128; 2]> = Vec::new();
+ for _ in 0..sudts.len() {
+ sudt_dist.push([0u128, 0]);
+ }
+ for fae in self.entries.iter() {
+ if indices.iter().find(|&&i| i == fae.index).is_none() {
+ continue;
+ }
+
+ ckbytes[fae.index as usize] = fae.ckbytes;
+ for (asset, amount) in fae.sudts.iter() {
+ sudt_dist[asset.0 as usize][fae.index as usize] = *amount;
+ }
+ }
+ let mut sudt_alloc: Vec = Vec::new();
+ for (i, asset) in sudts.iter().enumerate() {
+ sudt_alloc.push(
+ SUDTBalances::new_builder()
+ .asset(asset.clone())
+ .distribution(
+ SUDTDistribution::new_builder()
+ .nth0(sudt_dist[i][0].pack())
+ .nth1(sudt_dist[i][1].pack())
+ .build(),
+ )
+ .build(),
+ );
+ }
+
+ println!("mkbalances ckbytes: {:?}", ckbytes);
+
+ Ok(Balances::new_builder()
+ .ckbytes(
+ CKByteDistribution::new_builder()
+ .nth0(ckbytes[0].pack())
+ .nth1(ckbytes[1].pack())
+ .build(),
+ )
+ .sudts(SUDTAllocation::new_builder().set(sudt_alloc).build())
+ .build())
+ }
+
+ pub fn expected_ckbytes_funding_for(&self, index: u8) -> Result {
+ let entry = self
+ .entries
+ .iter()
+ .find(|entry| entry.index == index)
+ .ok_or("unknown index")?;
+ Ok(entry.ckbytes)
+ }
+
+ pub fn sudt_max_cap_sum(&self) -> u64 {
+ self.register.get_sudtassets().iter().fold(0u64, |old, asset| {
+ old + Capacity::shannons(asset.max_capacity().unpack()).as_u64()
+ })
+ }
+
+ pub fn expected_sudts_funding_for(
+ &self,
+ index: u8,
+ ) -> Result, perun::Error> {
+ let entry = self
+ .entries
+ .iter()
+ .find(|entry| entry.index == index)
+ .ok_or("unknown index")?;
+ entry
+ .sudts
+ .iter()
+ .map(|(asset, amount)| {
+ let sudt_asset = self.register.get_sudtasset(asset).ok_or("unknown asset")?;
+ let sudt_script = sudt_asset.type_script();
+ let sudt_capacity = Capacity::shannons(sudt_asset.max_capacity().unpack());
+ Ok((sudt_script, sudt_capacity, *amount))
+ })
+ .collect::, perun::Error>>()
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct FundingAgreementEntry {
+ pub ckbytes: u64,
+ pub sudts: Vec<(Asset, u128)>,
+ pub index: u8,
+ pub pub_key: PublicKey,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct Asset(pub u32);
+
+impl Asset {
+ pub fn new() -> Self {
+ Asset(0)
+ }
+}
+
+impl Default for Asset {
+ fn default() -> Self {
+ Asset(0)
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct AssetRegister {
+ assets: Vec<(Asset, SUDTAsset)>,
+}
+
+impl AssetRegister {
+ fn new() -> Self {
+ AssetRegister {
+ assets: Vec::new(),
+ }
+ }
+
+ pub fn len(&self) -> usize {
+ self.assets.len()
+ }
+
+ pub fn register_asset(&mut self, sudt_asset: SUDTAsset) -> Asset {
+ let asset = Asset(self.assets.len() as u32);
+ self.assets.push((asset, sudt_asset));
+ return asset;
+ }
+ pub fn get_sudtasset(&self, asset: &Asset) -> Option<&SUDTAsset> {
+ match self.assets.get(asset.0 as usize) {
+ Some((_, sudt_asset)) => Some(sudt_asset),
+ None => None,
+ }
+ }
+
+ pub fn get_asset(&self, sudt_asset: SUDTAsset) -> Option<&Asset> {
+ match self.assets.iter().find(|(_, a)| a.as_slice()[..] == sudt_asset.as_slice()[..]) {
+ Some((asset, _)) => Some(asset),
+ None => None,
+ }
+ }
+
+ pub fn guess_asset_from_script(&self, script: &Script) -> Option<&Asset> {
+ match self
+ .assets
+ .iter()
+ .find(|(_, sudt_asset)| sudt_asset.type_script().as_slice()[..] == script.as_slice()[..])
+ {
+ Some((asset, _)) => Some(asset),
+ None => None,
+ }
+ }
+
+ pub fn get_sudtassets(&self) -> Vec {
+ self.assets.iter().map(|(_, a)| a.clone()).collect()
+ }
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/keys.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/keys.rs
new file mode 100644
index 0000000..00c507d
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/keys.rs
@@ -0,0 +1,11 @@
+use k256::{ecdsa::VerifyingKey, elliptic_curve::sec1::ToEncodedPoint};
+
+pub fn verifying_key_to_byte_array(vk: &VerifyingKey) -> [u8; 33] {
+ vk.to_encoded_point(true)
+ .as_bytes()
+ .iter()
+ .map(|x| *x)
+ .collect::>()
+ .try_into()
+ .expect("public-key length 33")
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/mod.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/mod.rs
new file mode 100644
index 0000000..effc4c1
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/mod.rs
@@ -0,0 +1,15 @@
+#[cfg(test)]
+mod client;
+pub use client::*;
+
+mod funding_agreement;
+pub use funding_agreement::*;
+
+mod channel_id;
+pub use channel_id::*;
+
+pub mod keys;
+
+pub mod transaction;
+
+pub mod cell;
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/abort.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/abort.rs
new file mode 100644
index 0000000..c454be0
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/abort.rs
@@ -0,0 +1,85 @@
+use ckb_testtool::{
+ ckb_types::{
+ bytes::Bytes,
+ core::{TransactionBuilder, TransactionView},
+ packed::{CellInput, OutPoint},
+ prelude::{Builder, Entity, Pack},
+ },
+ context::Context,
+};
+use perun_common::{perun_types::ChannelStatus, redeemer};
+
+use crate::perun::{self, harness, test::{cell::FundingCell, transaction::common::add_cap_to_a}};
+
+use super::common::{channel_witness, create_cells};
+
+#[derive(Debug, Clone)]
+pub struct AbortArgs {
+ pub channel_cell: OutPoint,
+ pub funds: Vec,
+ pub state: ChannelStatus,
+ pub party_index: u8,
+}
+
+#[derive(Debug, Clone)]
+pub struct AbortResult {
+ pub tx: TransactionView,
+}
+
+impl Default for AbortResult {
+ fn default() -> Self {
+ AbortResult {
+ tx: TransactionBuilder::default().build(),
+ }
+ }
+}
+
+pub fn mk_abort(
+ ctx: &mut Context,
+ env: &harness::Env,
+ args: AbortArgs,
+) -> Result {
+ let payment_input = env.create_min_cell_for_index(ctx, args.party_index);
+ let abort_action = redeemer!(Abort);
+ let witness_args = channel_witness!(abort_action);
+ let mut inputs = vec![
+ CellInput::new_builder()
+ .previous_output(args.channel_cell)
+ .build(),
+ CellInput::new_builder()
+ .previous_output(payment_input)
+ .build(),
+ ];
+ inputs.extend(args.funds.iter().cloned().map(|op| {
+ CellInput::new_builder()
+ .previous_output(op.outpoint())
+ .build()
+ }));
+
+ let headers: Vec<_> = ctx.headers.keys().cloned().collect();
+ // TODO: We are expecting the output amounts to be greater than the minimum amount necessary to
+ // accomodate the space required for each output cell.
+ let f = |idx| env.build_lock_script(ctx, Bytes::from(vec![idx]));
+ let channel_cap = env.min_capacity_for_channel(args.state.clone())?;
+ let balances = add_cap_to_a(&args.state.state().balances(), channel_cap);
+ let outputs = balances.mk_outputs(f, vec![0]);
+ let outputs_data: Vec<_> = outputs.iter().map(|o| o.1.clone()).collect();
+
+ let cell_deps = vec![
+ env.pcls_script_dep.clone(),
+ env.pcts_script_dep.clone(),
+ env.always_success_script_dep.clone(),
+ ];
+
+ let rtx = TransactionBuilder::default()
+ .inputs(inputs)
+ .outputs(outputs.iter().cloned().map(|o| o.0))
+ .outputs_data(outputs_data.pack())
+ .cell_deps(cell_deps)
+ .header_deps(headers)
+ .witness(witness_args.as_bytes().pack())
+ .build();
+ let tx = ctx.complete_tx(rtx);
+ create_cells(ctx, tx.hash(), outputs);
+ Ok(AbortResult { tx })
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/close.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/close.rs
new file mode 100644
index 0000000..e40f6f7
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/close.rs
@@ -0,0 +1,96 @@
+use ckb_testtool::{
+ ckb_types::packed::{CellInput, OutPoint},
+ ckb_types::{
+ bytes::Bytes,
+ core::{TransactionBuilder, TransactionView},
+ prelude::{Builder, Entity, Pack},
+ },
+ context::Context,
+};
+use perun_common::{close, perun_types::ChannelStatus, redeemer};
+
+use crate::perun::{
+ self, harness,
+ test::{cell::FundingCell, transaction::common::channel_witness},
+};
+
+use super::common::{create_cells, add_cap_to_a};
+
+#[derive(Debug, Clone)]
+pub struct CloseArgs {
+ /// The channel cell which tracks the channel on-chain.
+ pub channel_cell: OutPoint,
+ /// All funding cells used to initially fund the channel.
+ pub funds_cells: Vec,
+ /// The channel state which shall be used for closing.
+ pub state: ChannelStatus,
+ /// The DER encoded signatures for the channel state in proper order of parties.
+ pub sigs: [Vec; 2],
+ pub party_index: u8,
+}
+
+#[derive(Debug, Clone)]
+pub struct CloseResult {
+ pub tx: TransactionView,
+}
+
+impl Default for CloseResult {
+ fn default() -> Self {
+ CloseResult {
+ tx: TransactionBuilder::default().build(),
+ }
+ }
+}
+
+pub fn mk_close(
+ ctx: &mut Context,
+ env: &harness::Env,
+ args: CloseArgs,
+) -> Result {
+ let payment_input = env.create_min_cell_for_index(ctx, args.party_index);
+ let mut inputs = vec![
+ CellInput::new_builder()
+ .previous_output(args.channel_cell)
+ .build(),
+ CellInput::new_builder()
+ .previous_output(payment_input)
+ .build(),
+ ];
+ inputs.extend(args.funds_cells.iter().cloned().map(|f| {
+ CellInput::new_builder()
+ .previous_output(f.outpoint())
+ .build()
+ }));
+
+ let cell_deps = vec![
+ env.pcls_script_dep.clone(),
+ env.pcts_script_dep.clone(),
+ env.pfls_script_dep.clone(),
+ env.always_success_script_dep.clone(),
+ ];
+ let channel_cap = env.min_capacity_for_channel(args.state.clone())?;
+ let balances = add_cap_to_a(&args.state.state().balances(), channel_cap);
+ let f = |idx| env.build_lock_script(ctx, Bytes::from(vec![idx]));
+ let outputs = balances.mk_outputs(f, vec![0, 1]);
+ let outputs_data: Vec<_> = outputs.iter().map(|o| o.1.clone()).collect();
+
+ let close_action = redeemer!(close!(
+ args.state.state(),
+ args.sigs[0].pack(),
+ args.sigs[1].pack()
+ ));
+ let witness_args = channel_witness!(close_action);
+
+ let headers: Vec<_> = ctx.headers.keys().cloned().collect();
+ let rtx = TransactionBuilder::default()
+ .inputs(inputs)
+ .outputs(outputs.iter().map(|o| o.0.clone()))
+ .outputs_data(outputs_data.pack())
+ .witness(witness_args.as_bytes().pack())
+ .cell_deps(cell_deps)
+ .header_deps(headers)
+ .build();
+ let tx = ctx.complete_tx(rtx);
+ create_cells(ctx, tx.hash(), outputs);
+ Ok(CloseResult { tx })
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/common.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/common.rs
new file mode 100644
index 0000000..3814aad
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/common.rs
@@ -0,0 +1,41 @@
+use ckb_occupied_capacity::Capacity;
+use ckb_testtool::{
+ bytes,
+ ckb_types::{packed::{Byte32, CellOutput, OutPoint}, prelude::{Unpack, Pack}},
+ context::Context,
+};
+use molecule::prelude::{Entity, Builder};
+use perun_common::perun_types::Balances;
+
+use crate::perun;
+
+/// Build witness args containing the given action.
+macro_rules! channel_witness {
+ ($action:expr) => {
+ ckb_testtool::ckb_types::packed::WitnessArgsBuilder::default()
+ .input_type(Some($action.as_bytes()).pack())
+ .build()
+ };
+}
+pub(crate) use channel_witness;
+
+pub fn create_funding_from(
+ available_capacity: Capacity,
+ wanted_capacity: Capacity,
+) -> Result {
+ Ok(available_capacity.safe_sub(wanted_capacity)?)
+}
+
+pub fn create_cells(ctx: &mut Context, hash: Byte32, outputs: Vec<(CellOutput, bytes::Bytes)>) {
+ for (i, (output, data)) in outputs.into_iter().enumerate() {
+ let out_point = OutPoint::new(hash.clone(), i as u32);
+ ctx.create_cell_with_out_point(out_point, output, data);
+ }
+}
+
+pub fn add_cap_to_a(balances: &Balances, cap: Capacity) -> Balances {
+ let bal_a: u64 = balances.ckbytes().nth0().unpack();
+ balances.clone().as_builder().ckbytes(
+ balances.ckbytes().as_builder().nth0(
+ (cap.as_u64() + bal_a).pack()).build()).build()
+}
\ No newline at end of file
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/dispute.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/dispute.rs
new file mode 100644
index 0000000..aa8a5e4
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/dispute.rs
@@ -0,0 +1,94 @@
+use ckb_testtool::{
+ ckb_types::packed::{CellInput, CellOutput, OutPoint},
+ ckb_types::{
+ core::{TransactionBuilder, TransactionView},
+ packed::Script,
+ prelude::{Builder, Entity, Pack},
+ },
+ context::Context,
+};
+use perun_common::{dispute, perun_types::ChannelStatus, redeemer};
+
+use crate::perun::{self, harness, test::transaction::common::channel_witness};
+
+use super::common::create_cells;
+
+#[derive(Debug, Clone)]
+pub struct DisputeArgs {
+ /// The channel cell which tracks the channel on-chain.
+ pub channel_cell: OutPoint,
+ /// The channel state which shall be used for closing.
+ pub state: ChannelStatus,
+ /// The DER encoded signatures for the channel state in proper order of parties.
+ pub sigs: [Vec; 2],
+ /// The Perun channel type script used for the current channel.
+ pub pcts_script: Script,
+ pub party_index: u8,
+}
+
+#[derive(Debug, Clone)]
+pub struct DisputeResult {
+ pub tx: TransactionView,
+ pub channel_cell: OutPoint,
+}
+
+impl Default for DisputeResult {
+ fn default() -> Self {
+ DisputeResult {
+ tx: TransactionBuilder::default().build(),
+ channel_cell: OutPoint::default(),
+ }
+ }
+}
+
+pub fn mk_dispute(
+ ctx: &mut Context,
+ env: &harness::Env,
+ args: DisputeArgs,
+) -> Result {
+ let payment_input = env.create_min_cell_for_index(ctx, args.party_index);
+ let inputs = vec![
+ CellInput::new_builder()
+ .previous_output(args.channel_cell)
+ .build(),
+ CellInput::new_builder()
+ .previous_output(payment_input)
+ .build(),
+ ];
+
+ let cell_deps = vec![
+ env.pcls_script_dep.clone(),
+ env.pcts_script_dep.clone(),
+ env.pfls_script_dep.clone(),
+ env.always_success_script_dep.clone(),
+ ];
+
+ let pcls_script = env.build_pcls(ctx, Default::default());
+ let capacity_for_cs = env.min_capacity_for_channel(args.state.clone())?;
+ let channel_cell = CellOutput::new_builder()
+ .capacity(capacity_for_cs.pack())
+ .lock(pcls_script.clone())
+ .type_(Some(args.pcts_script.clone()).pack())
+ .build();
+ let outputs = vec![(channel_cell.clone(), args.state.as_bytes())];
+ let outputs_data: Vec<_> = outputs.iter().map(|e| e.1.clone()).collect();
+
+ let dispute_action = redeemer!(dispute!(args.sigs[0].pack(), args.sigs[1].pack()));
+ let witness_args = channel_witness!(dispute_action);
+
+ let headers: Vec<_> = ctx.headers.keys().cloned().collect();
+ let rtx = TransactionBuilder::default()
+ .inputs(inputs)
+ .outputs(outputs.iter().map(|e| e.0.clone()))
+ .outputs_data(outputs_data.pack())
+ .header_deps(headers)
+ .witness(witness_args.as_bytes().pack())
+ .cell_deps(cell_deps)
+ .build();
+ let tx = ctx.complete_tx(rtx);
+ create_cells(ctx, tx.hash(), outputs);
+ Ok(DisputeResult {
+ channel_cell: OutPoint::new(tx.hash(), 0),
+ tx,
+ })
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/force_close.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/force_close.rs
new file mode 100644
index 0000000..a767ab5
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/force_close.rs
@@ -0,0 +1,94 @@
+use ckb_testtool::{
+ ckb_types::packed::{CellInput, OutPoint},
+ ckb_types::{
+ bytes::Bytes,
+ core::{TransactionBuilder, TransactionView},
+ packed::Byte32,
+ prelude::{Builder, Entity, Pack},
+ },
+ context::Context,
+};
+use perun_common::{perun_types::ChannelStatus, redeemer};
+
+use crate::perun::{
+ self, harness,
+ test::{cell::FundingCell, transaction::common::channel_witness},
+};
+
+use super::common::{create_cells, add_cap_to_a};
+
+#[derive(Debug, Clone)]
+pub struct ForceCloseArgs {
+ /// The channel cell which tracks the channel on-chain.
+ pub channel_cell: OutPoint,
+ /// The latest headers for the chain containing some timestamps.
+ pub headers: Vec,
+ /// All funding cells used to initially fund the channel.
+ pub funds_cells: Vec,
+ /// The channel state which shall be used for closing.
+ pub state: ChannelStatus,
+ pub party_index: u8,
+}
+
+#[derive(Debug, Clone)]
+pub struct ForceCloseResult {
+ pub tx: TransactionView,
+}
+
+impl Default for ForceCloseResult {
+ fn default() -> Self {
+ ForceCloseResult {
+ tx: TransactionBuilder::default().build(),
+ }
+ }
+}
+
+pub fn mk_force_close(
+ ctx: &mut Context,
+ env: &harness::Env,
+ args: ForceCloseArgs,
+) -> Result {
+ let payment_input = env.create_min_cell_for_index(ctx, args.party_index);
+ let mut inputs = vec![
+ CellInput::new_builder()
+ .previous_output(args.channel_cell)
+ .build(),
+ CellInput::new_builder()
+ .previous_output(payment_input)
+ .build(),
+ ];
+ inputs.extend(args.funds_cells.iter().cloned().map(|f| {
+ CellInput::new_builder()
+ .previous_output(f.outpoint())
+ .build()
+ }));
+
+ let cell_deps = vec![
+ env.pcls_script_dep.clone(),
+ env.pcts_script_dep.clone(),
+ env.pfls_script_dep.clone(),
+ env.always_success_script_dep.clone(),
+ ];
+
+ // Rust...
+ let channel_cap = env.min_capacity_for_channel(args.state.clone())?;
+ let balances = add_cap_to_a(&args.state.state().balances(), channel_cap);
+ let f = |idx| env.build_lock_script(ctx, Bytes::from(vec![idx]));
+ let outputs = balances.mk_outputs(f, vec![0, 1]);
+ let outputs_data: Vec<_> = outputs.iter().map(|o| o.1.clone()).collect();
+
+ let force_close_action = redeemer!(ForceClose);
+ let witness_args = channel_witness!(force_close_action);
+
+ let rtx = TransactionBuilder::default()
+ .inputs(inputs)
+ .outputs(outputs.iter().map(|o| o.0.clone()))
+ .outputs_data(outputs_data.pack())
+ .header_deps(args.headers)
+ .witness(witness_args.as_bytes().pack())
+ .cell_deps(cell_deps)
+ .build();
+ let tx = ctx.complete_tx(rtx);
+ create_cells(ctx, tx.hash(), outputs);
+ Ok(ForceCloseResult { tx })
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/fund.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/fund.rs
new file mode 100644
index 0000000..15bd968
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/fund.rs
@@ -0,0 +1,123 @@
+use ckb_occupied_capacity::{Capacity, IntoCapacity};
+use ckb_testtool::{
+ ckb_types::{
+ bytes::Bytes,
+ core::{TransactionBuilder, TransactionView},
+ packed::{CellInput, CellOutput, OutPoint, Script},
+ prelude::{Builder, Entity, Pack},
+ },
+ context::Context,
+};
+use perun_common::{fund, perun_types::ChannelStatus, redeemer};
+
+use crate::perun::{
+ self, harness,
+ test::{cell::{FundingCell, mk_funding_cell}, FundingAgreement},
+};
+
+use super::common::{channel_witness, create_cells, create_funding_from};
+
+#[derive(Debug, Clone)]
+pub struct FundArgs {
+ pub channel_cell: OutPoint,
+ pub funding_agreement: FundingAgreement,
+ pub party_index: u8,
+ pub inputs: Vec<(OutPoint, Capacity)>,
+ pub pcts: Script,
+ pub state: ChannelStatus,
+}
+
+#[derive(Debug, Clone)]
+pub struct FundResult {
+ pub tx: TransactionView,
+ pub channel_cell: OutPoint,
+ pub funds_cells: Vec,
+ pub state: ChannelStatus,
+}
+
+impl Default for FundResult {
+ fn default() -> Self {
+ FundResult {
+ tx: TransactionBuilder::default().build(),
+ channel_cell: OutPoint::default(),
+ funds_cells: vec![],
+ state: ChannelStatus::default(),
+ }
+ }
+}
+
+pub fn mk_fund(
+ ctx: &mut Context,
+ env: &harness::Env,
+ args: FundArgs,
+) -> Result {
+ let fund_action = redeemer!(fund!());
+ let witness_args = channel_witness!(fund_action);
+ let wanted = args
+ .funding_agreement
+ .expected_ckbytes_funding_for(args.party_index)?;
+ let pfls = env.build_pfls(ctx, args.pcts.calc_script_hash().as_bytes());
+ // TODO: Make sure enough funds available all cells!
+
+ // Note: we do not really need to shrink the balances to only contain the party's balances, as balances.mk_outputs will do so anyway.
+ let balances = args.funding_agreement.mk_balances(vec![args.party_index])?;
+ let pfls = |_| pfls.clone();
+ let mut outputs = balances.mk_outputs(pfls, vec![1]);
+ let num_fund_ouputs = outputs.len();
+
+ let my_available_funds = Capacity::shannons(args.inputs.iter().map(|(_, c)| c.as_u64()).sum());
+ let exchange_cell = create_funding_from(my_available_funds, (wanted + args.funding_agreement.sudt_max_cap_sum()).into_capacity())?;
+ let mut inputs = vec![
+ CellInput::new_builder()
+ .previous_output(args.channel_cell)
+ .build(),
+ ];
+ for (outpoint, _) in args.inputs.iter() {
+ inputs.push(CellInput::new_builder().previous_output(outpoint.clone()).build());
+ }
+ // NOTE: mk_fund currently expects the be called for the last party funding the channel.
+ // Otherwise the call to `mk_funded` returns a wrong channel state.
+ let updated_cs = args.state.mk_funded();
+ let capacity_for_new_cs = env.min_capacity_for_channel(updated_cs.clone())?;
+ let pcls = env.build_pcls(ctx, Default::default());
+ let new_channel_cell = CellOutput::new_builder()
+ .capacity(capacity_for_new_cs.pack())
+ .lock(pcls.clone())
+ .type_(Some(args.pcts.clone()).pack())
+ .build();
+ outputs.append(&mut vec![
+ (new_channel_cell.clone(), updated_cs.as_bytes()),
+ (
+ CellOutput::new_builder()
+ .capacity(exchange_cell.pack())
+ .lock(env.build_lock_script(ctx, Bytes::from(vec![args.party_index])))
+ .build(),
+ Bytes::new(),
+ ),
+ ]);
+ let outputs_data: Vec<_> = outputs.iter().map(|o| o.1.clone()).collect();
+ let cell_deps = vec![
+ env.always_success_script_dep.clone(),
+ env.pcts_script_dep.clone(),
+ env.pcls_script_dep.clone(),
+ env.sample_udt_script_dep.clone(), // TODO: Make this generic
+ ];
+ let headers: Vec<_> = ctx.headers.keys().cloned().collect();
+ let rtx = TransactionBuilder::default()
+ .inputs(inputs)
+ .witness(witness_args.as_bytes().pack())
+ .outputs(outputs.clone().into_iter().map(|o| o.0.clone()))
+ .outputs_data(outputs_data.pack())
+ .cell_deps(cell_deps)
+ .header_deps(headers)
+ .build();
+ let tx = ctx.complete_tx(rtx);
+ create_cells(ctx, tx.hash(), outputs.clone());
+ Ok(FundResult {
+ channel_cell: OutPoint::new(tx.hash(), num_fund_ouputs as u32),
+ funds_cells: outputs[..num_fund_ouputs].iter().enumerate().map(|(i, (co, bytes))|
+ mk_funding_cell(args.party_index, OutPoint::new(tx.hash(), i as u32), co, bytes.clone(), args.funding_agreement.register())).collect(),
+ state: updated_cs,
+ tx,
+ })
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/mod.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/mod.rs
new file mode 100644
index 0000000..b1d6673
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/mod.rs
@@ -0,0 +1,19 @@
+mod open;
+pub use open::*;
+
+mod fund;
+pub use fund::*;
+
+mod abort;
+pub use abort::*;
+
+mod close;
+pub use close::*;
+
+mod force_close;
+pub use force_close::*;
+
+mod dispute;
+pub use dispute::*;
+
+mod common;
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/open.rs b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/open.rs
new file mode 100644
index 0000000..808cd8e
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/perun/test/transaction/open.rs
@@ -0,0 +1,129 @@
+use std::vec;
+
+use ckb_occupied_capacity::{Capacity, IntoCapacity};
+use ckb_testtool::{
+ ckb_types::{
+ bytes::Bytes,
+ core::{TransactionBuilder, TransactionView},
+ packed::{CellInput, CellOutput, OutPoint, Script},
+ prelude::{Builder, Entity, Pack},
+ },
+ context::Context,
+};
+use perun_common::perun_types::ChannelStatus;
+
+use crate::perun::{
+ self, harness,
+ test::{cell::{FundingCell, mk_funding_cell}, ChannelId, FundingAgreement},
+};
+
+use super::common::{create_cells, create_funding_from};
+
+#[derive(Clone)]
+pub struct OpenArgs {
+ pub cid: ChannelId,
+ pub funding_agreement: FundingAgreement,
+ pub channel_token_outpoint: OutPoint,
+ pub inputs: Vec<(OutPoint, Capacity)>,
+ pub party_index: u8,
+ pub pcls_script: Script,
+ pub pcts_script: Script,
+ pub pfls_script: Script,
+}
+
+pub struct OpenResult {
+ pub tx: TransactionView,
+ pub channel_cell: OutPoint,
+ pub funds_cells: Vec,
+ pub pcts: Script,
+ pub state: ChannelStatus,
+}
+
+impl Default for OpenResult {
+ fn default() -> Self {
+ OpenResult {
+ tx: TransactionBuilder::default().build(),
+ channel_cell: OutPoint::default(),
+ funds_cells: Vec::new(),
+ pcts: Script::default(),
+ state: ChannelStatus::default(),
+ }
+ }
+}
+
+pub fn mk_open(
+ ctx: &mut Context,
+ env: &harness::Env,
+ args: OpenArgs,
+) -> Result {
+ let mut inputs = vec![
+ CellInput::new_builder()
+ .previous_output(args.channel_token_outpoint)
+ .build(),
+ ];
+ for (outpoint, _) in args.inputs.iter() {
+ inputs.push(
+ CellInput::new_builder()
+ .previous_output(outpoint.clone())
+ .build(),
+ );
+ }
+ let initial_cs =
+ env.build_initial_channel_state(args.cid, args.party_index, &args.funding_agreement)?;
+ let capacity_for_cs = env.min_capacity_for_channel(initial_cs.clone())?;
+ let channel_cell = CellOutput::new_builder()
+ .capacity(capacity_for_cs.pack())
+ .lock(args.pcls_script.clone())
+ .type_(Some(args.pcts_script.clone()).pack())
+ .build();
+ let wanted = args
+ .funding_agreement
+ .expected_ckbytes_funding_for(args.party_index)?;
+
+ let pfls = |_| args.pfls_script.clone();
+
+ let balances = args.funding_agreement.mk_balances(vec![args.party_index])?;
+ let mut outputs = balances.mk_outputs(pfls, vec![0]);
+ let num_of_funds = outputs.len();
+ // TODO: Make sure enough funds available all cells!
+ let my_available_funds = Capacity::shannons(args.inputs.iter().map(|(_, c)| c.as_u64()).sum());
+ let exchange_cell_cap = create_funding_from(my_available_funds, (wanted + args.funding_agreement.sudt_max_cap_sum()).into_capacity())?;
+ // NOTE: The ORDER here is important. We need to reference the outpoints later on by using the
+ // correct index in the output array of the transaction we build.
+ outputs.append(
+ vec![
+ (channel_cell.clone(), initial_cs.as_bytes()),
+ (
+ CellOutput::new_builder()
+ .capacity(exchange_cell_cap.pack())
+ .lock(env.build_lock_script(ctx, Bytes::from(vec![args.party_index])))
+ .build(),
+ Bytes::new(),
+ ),
+ ].as_mut()
+ );
+
+ let outputs_data: Vec<_> = outputs.iter().map(|o| o.1.clone()).collect();
+ let cell_deps = vec![
+ env.always_success_script_dep.clone(),
+ env.pcts_script_dep.clone(),
+ env.sample_udt_script_dep.clone(), // TODO: Make this generic!
+ ];
+ let rtx = TransactionBuilder::default()
+ .inputs(inputs)
+ .outputs(outputs.iter().map(|o| o.0.clone()))
+ .outputs_data(outputs_data.pack())
+ .cell_deps(cell_deps)
+ .build();
+ let tx = ctx.complete_tx(rtx);
+ create_cells(ctx, tx.hash(), outputs.clone());
+ Ok(OpenResult {
+ // See NOTE above for magic indices.
+ channel_cell: OutPoint::new(tx.hash(), num_of_funds as u32),
+ funds_cells: outputs[..num_of_funds].iter().enumerate().map(|(i, (co, bytes))|
+ mk_funding_cell(args.party_index, OutPoint::new(tx.hash(), i as u32), co, bytes.clone(), args.funding_agreement.register())).collect(),
+ tx,
+ pcts: args.pcts_script,
+ state: initial_cs,
+ })
+}
diff --git a/payment-channel-ckb/devnet/contracts/tests/src/tests.rs b/payment-channel-ckb/devnet/contracts/tests/src/tests.rs
new file mode 100644
index 0000000..f21be32
--- /dev/null
+++ b/payment-channel-ckb/devnet/contracts/tests/src/tests.rs
@@ -0,0 +1,486 @@
+use crate::perun::mutators::*;
+use crate::perun::random;
+
+use super::*;
+use ckb_occupied_capacity::Capacity;
+use ckb_testtool::ckb_types::{bytes::Bytes, packed::*, prelude::*};
+use ckb_testtool::context::Context;
+use perun;
+use perun::test;
+use perun_common::helpers::blake2b256;
+use perun_common::perun_types::{Balances, Bool, ChannelState, SEC1EncodedPubKey, CKByteDistribution};
+use perun_common::sig::verify_signature;
+
+const MAX_CYCLES: u64 = 10 * 10_000_000;
+const CHALLENGE_DURATION_MS: u64 = 10 * 1000;
+
+#[test]
+fn test_signature() {
+ // This tests the interoperability between the on-chain signature verification
+ // and the key generation & signing in the perun-ckb-backend's wallet.
+
+ // This signature was generated by the wallet in the perun-ckb-backend
+ let sig_string = "3045022100a4f8768be2e5afdcbcfee600eb963caf1957d32edca49390e6f5a4933c2f6dcd02207fd9d2b5928266e9aeee039285508da1dbdbeec67cb995fd8735e1795bf53e5f";
+ let sig = hex::decode(sig_string).expect("decoding signature");
+ let sig_bytes: Bytes = sig.into();
+
+ // This public key was generated by the wallet in the perun-ckb-backend
+ let pubkey_string = "02d1ab4e7cbfb2262de6f3f816d9b044970162a6a2ae0e6b0ff9b082e315c5e152";
+ let pubkey = hex::decode(pubkey_string).expect("decoding pubkey");
+ let pubkey_bytes: [Byte; 33] = pubkey
+ .iter()
+ .map(|x| (*x).into())
+ .collect::>()
+ .try_into()
+ .unwrap();
+ SEC1EncodedPubKey::new_builder().set(pubkey_bytes).build();
+
+ let balances_array: [Uint64; 2] = [10u64.pack(), 11u64.pack()];
+ let balances = Balances::new_builder().ckbytes(CKByteDistribution::new_builder().set(balances_array).build()).build();
+ let channel_state = ChannelState::new_builder()
+ .channel_id(Byte32::zero())
+ .balances(balances)
+ .is_final(Bool::from_bool(true))
+ .version(10u64.pack())
+ .build();
+ let msg = channel_state.as_slice();
+ let msg_hash = blake2b256(msg);
+
+ verify_signature(&msg_hash, &sig_bytes, pubkey.as_slice()).expect("valid signature");
+}
+
+// TODO: Add mutator to channel state that can be passed to dispute, and close.
+#[test]
+fn channel_test_bench() -> Result<(), perun::Error> {
+ let res = [
+ test_funding_abort,
+ test_successful_funding_with_udt,
+ test_successful_funding_without_udt,
+ test_early_force_close,
+ test_close,
+ test_force_close,
+ test_multiple_disputes,
+ test_multiple_disputes_same_version,
+ test_multi_asset_payment,
+ test_multi_asset_abort,
+ test_multi_asset_abort_zero_sudt_balance,
+ test_multi_asset_force_close,
+ ]
+ .iter()
+ .map(|test| {
+ let mut context = Context::default();
+ let pe = perun::harness::Env::new(&mut context, MAX_CYCLES, CHALLENGE_DURATION_MS)
+ .expect("preparing environment");
+ test(&mut context, &pe)
+ })
+ .collect::