From 4ba2c13fbec9b6297c6be73a29cff33adee0924d Mon Sep 17 00:00:00 2001 From: Hendrik Amler Date: Tue, 10 Sep 2024 21:17:29 +0200 Subject: [PATCH] add: stellar payment example Signed-off-by: Hendrik Amler --- .github/workflows/ci.yml | 15 +- payment-channel-xlm/Dockerfile | 18 + payment-channel-xlm/README.md | 23 ++ payment-channel-xlm/client/channel.go | 89 +++++ payment-channel-xlm/client/client.go | 118 ++++++ payment-channel-xlm/client/handle.go | 120 +++++++ payment-channel-xlm/go.mod | 33 ++ payment-channel-xlm/go.sum | 127 +++++++ payment-channel-xlm/main.go | 105 ++++++ payment-channel-xlm/quickstart.sh | 63 ++++ payment-channel-xlm/testdata/docker/build.sh | 7 + .../perun_soroban_multi_contract.wasm | Bin 0 -> 23664 bytes .../testdata/perun_soroban_token.wasm | Bin 0 -> 7347 bytes payment-channel-xlm/util/setup.go | 337 ++++++++++++++++++ payment-channel-xlm/util/token.go | 240 +++++++++++++ 15 files changed, 1294 insertions(+), 1 deletion(-) create mode 100644 payment-channel-xlm/Dockerfile create mode 100644 payment-channel-xlm/README.md create mode 100644 payment-channel-xlm/client/channel.go create mode 100644 payment-channel-xlm/client/client.go create mode 100644 payment-channel-xlm/client/handle.go create mode 100644 payment-channel-xlm/go.mod create mode 100644 payment-channel-xlm/go.sum create mode 100644 payment-channel-xlm/main.go create mode 100644 payment-channel-xlm/quickstart.sh create mode 100644 payment-channel-xlm/testdata/docker/build.sh create mode 100644 payment-channel-xlm/testdata/perun_soroban_multi_contract.wasm create mode 100644 payment-channel-xlm/testdata/perun_soroban_token.wasm create mode 100644 payment-channel-xlm/util/setup.go create mode 100644 payment-channel-xlm/util/token.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e07dcd..14ac400 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.22' - uses: actions/cache@v3 with: @@ -71,3 +71,16 @@ jobs: sleep 5 go run . docker stop ganache + + - name: Payment Channel XLM + working-directory: payment-channel-xlm + run: | + chmod +x ./testdata/docker/build.sh + ./testdata/docker/build.sh + + chmod +x ./quickstart.sh + ./quickstart.sh standalone & + + sleep 25 + go run ./ + diff --git a/payment-channel-xlm/Dockerfile b/payment-channel-xlm/Dockerfile new file mode 100644 index 0000000..cadb8e4 --- /dev/null +++ b/payment-channel-xlm/Dockerfile @@ -0,0 +1,18 @@ +# Based on Preview 7 +# https://soroban.stellar.org/docs/reference/releases + +FROM ubuntu:20.04 + +RUN apt update && apt install -y curl + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rust_install.sh +RUN sh rust_install.sh -y +RUN echo $PATH +ENV PATH="$PATH:/root/.cargo/bin" +RUN rustup target add wasm32-unknown-unknown + +RUN apt install -y build-essential +# WORKDIR / +RUN mkdir /workspace +WORKDIR /workspace +ENV IS_USING_DOCKER=true diff --git a/payment-channel-xlm/README.md b/payment-channel-xlm/README.md new file mode 100644 index 0000000..2dd5c13 --- /dev/null +++ b/payment-channel-xlm/README.md @@ -0,0 +1,23 @@ +

Perun Stellar Example

+ +This example shows how to set up a payment channel on Stellar, which utilizes the [go-perun](https://github.com/perun-network/go-perun) channel library, and also the [Stellar payment channel backend](https://github.com/perun-network/perun-stellar-backend). + +# Setup + +Spin up the local Stellar blockchain, serving as a local testnet for demonstration purposes. + +``` + $ ./quickstart.sh standalone +``` + +This will start the Stellar, Horizon and Soroban nodes in the background. This is the platform on which we deploy the Stellar Asset Contract (SAC), and the Perun Payment Channel contract. This allows us to create and utilize L2 channels on Stellar for any customized Stellar asset tokens. + +# Using the example + +You can start the demo by simply running + +``` + $ go run main.go +``` + +The accounts for Alice and Bob used in the example are generated randomly and funded at the initialization stage of the demo. \ No newline at end of file diff --git a/payment-channel-xlm/client/channel.go b/payment-channel-xlm/client/channel.go new file mode 100644 index 0000000..e0f860f --- /dev/null +++ b/payment-channel-xlm/client/channel.go @@ -0,0 +1,89 @@ +package client + +import ( + "context" + "math/big" + + "perun.network/go-perun/channel" + "perun.network/go-perun/client" +) + +type PaymentChannel struct { + ch *client.Channel + currencies []channel.Asset +} + +func (c *PaymentChannel) GetChannel() *client.Channel { + return c.ch +} +func (c *PaymentChannel) GetChannelParams() *channel.Params { + return c.ch.Params() +} + +func (c *PaymentChannel) GetChannelState() *channel.State { + return c.ch.State() +} + +func newPaymentChannel(ch *client.Channel, currencies []channel.Asset) *PaymentChannel { + return &PaymentChannel{ + ch: ch, + currencies: currencies, + } +} + +// SendPayment sends a payment to the channel peer. +func (c PaymentChannel) SendPayment(amount int64, assetIdx int) { + // 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) { + icp := big.NewInt(amount) + actor := c.ch.Idx() + peer := 1 - actor + state.Allocation.TransferBalance(actor, peer, c.currencies[assetIdx], icp) + }) + if err != nil { + panic(err) + } +} + +// PerformSwap performs a swap by "swapping" the balances of the two +// participants for both assets. +func (c PaymentChannel) PerformSwap() { + err := c.ch.Update(context.TODO(), func(state *channel.State) { // We use context.TODO to keep the code simple. + // We simply swap the balances for the two assets. + state.Balances = channel.Balances{ + {state.Balances[0][1], state.Balances[0][0]}, //asset 1 Nutzer 1 Nutzer 2 + {state.Balances[1][1], state.Balances[1][0]}, //asset 2 Nutzer 1 Nutzer 2 + } + + // Set the state to final because we do not expect any other updates + // than this swap. + state.IsFinal = true + }) + 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() { + // If the channel is not finalized: 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(), false) + if err != nil { + panic(err) + } + + // Close frees up channel resources. + c.ch.Close() +} diff --git a/payment-channel-xlm/client/client.go b/payment-channel-xlm/client/client.go new file mode 100644 index 0000000..70a9da3 --- /dev/null +++ b/payment-channel-xlm/client/client.go @@ -0,0 +1,118 @@ +package client + +import ( + "fmt" + "log" + "math/big" + + "perun.network/go-perun/wire/net/simple" + + "context" + "errors" + + pchannel "perun.network/go-perun/channel" + pclient "perun.network/go-perun/client" + "perun.network/go-perun/watcher/local" + "perun.network/go-perun/wire" + "perun.network/perun-stellar-backend/channel" + "perun.network/perun-stellar-backend/wallet" +) + +type PaymentClient struct { + perunClient *pclient.Client + account *wallet.Account + currencies []pchannel.Asset + channels chan *PaymentChannel + Channel *PaymentChannel + wAddr wire.Address + balance *big.Int +} + +func SetupPaymentClient( + w *wallet.EphemeralWallet, + acc *wallet.Account, + stellarTokenIDs []pchannel.Asset, + bus *wire.LocalBus, + funder *channel.Funder, + adj *channel.Adjudicator, + +) (*PaymentClient, error) { + watcher, err := local.NewWatcher(adj) + if err != nil { + return nil, fmt.Errorf("intializing watcher: %w", err) + } + // Setup Perun client. + wireAddr := simple.NewAddress(acc.Address().String()) + perunClient, err := pclient.New(wireAddr, bus, funder, adj, w, watcher) + if err != nil { + return nil, errors.New("creating client") + } + + c := &PaymentClient{ + perunClient: perunClient, + account: acc, + currencies: stellarTokenIDs, + channels: make(chan *PaymentChannel, 1), + wAddr: wireAddr, + balance: big.NewInt(0), + } + + go perunClient.Handle(c, c) + return c, nil +} + +// startWatching starts the dispute watcher for the specified channel. +func (c *PaymentClient) startWatching(ch *pclient.Channel) { + go func() { + err := ch.Watch(c) + if err != nil { + log.Printf("watcher returned with error: %v", err) + } + }() +} + +func (c *PaymentClient) OpenChannel(peer wire.Address, balances pchannel.Balances) { + // We define the channel participants. The proposer has always index 0. Here + // we use the on-chain addresses as off-chain addresses, but we could also + // use different ones. + + participants := []wire.Address{c.WireAddress(), peer} + + initAlloc := pchannel.NewAllocation(2, c.currencies...) + initAlloc.Balances = balances + + // Prepare the channel proposal by defining the channel parameters. + challengeDuration := uint64(10) // On-chain challenge duration in seconds. + proposal, err := pclient.NewLedgerChannelProposal( + challengeDuration, + c.account.Address(), + initAlloc, + participants, + ) + if err != nil { + panic(err) + } + + // Send the proposal. + ch, err := c.perunClient.ProposeChannel(context.TODO(), proposal) + if err != nil { + panic(err) + } + + // Start the on-chain event watcher. It automatically handles disputes. + c.startWatching(ch) + c.Channel = newPaymentChannel(ch, c.currencies) +} + +func (p *PaymentClient) WireAddress() wire.Address { + return p.wAddr +} + +func (c *PaymentClient) AcceptedChannel() *PaymentChannel { + return <-c.channels +} + +// Shutdown gracefully shuts down the client. +func (c *PaymentClient) Shutdown() { + c.perunClient.Close() +} diff --git a/payment-channel-xlm/client/handle.go b/payment-channel-xlm/client/handle.go new file mode 100644 index 0000000..a1d7eee --- /dev/null +++ b/payment-channel-xlm/client/handle.go @@ -0,0 +1,120 @@ +package client + +import ( + "context" + "errors" + "fmt" + "log" + "math/big" + "time" + + "perun.network/go-perun/channel" + "perun.network/go-perun/client" +) + +// HandleProposal is the callback for incoming channel proposals. +func (c *PaymentClient) HandleProposal(p client.ChannelProposal, r *client.ProposalResponder) { + lcp, err := func() (*client.LedgerChannelProposalMsg, error) { + // Ensure that we got a ledger channel proposal. + lcp, ok := p.(*client.LedgerChannelProposalMsg) + if !ok { + return nil, errors.New("invalid proposal type: expected *client.LedgerChannelProposalMsg") + } + + // 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. + const assetIdx, clientIdx, peerIdx = 0, 0, 1 + + pAssets := make([]channel.Asset, len(c.currencies)) + for i, asset := range c.currencies { + pAssets[i] = channel.Asset(asset) + } + + if err := channel.AssertAssetsEqual(lcp.InitBals.Assets, pAssets); err != nil { + return nil, fmt.Errorf("invalid assets: %v", err) + } else if lcp.FundingAgreement[assetIdx][clientIdx].Cmp(big.NewInt(0)) == 0 && lcp.FundingAgreement[assetIdx][peerIdx].Cmp(big.NewInt(0)) == 0 { + return nil, errors.New("invalid funding balance: both client and peer cannot have zero funding") + } + return lcp, nil + }() + if err != nil { + errReject := r.Reject(context.TODO(), err.Error()) + if errReject != nil { + // Log the error or take other action as needed + log.Printf("error rejecting proposal: %v\n", errReject) + } + } + + // Create a channel accept message and send it. + accept := lcp.Accept( + c.account.Address(), // 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\n", err) + return + } + + // Start the on-chain event watcher. It automatically handles disputes. + c.startWatching(ch) + + // Store channel. + c.channels <- newPaymentChannel(ch, c.currencies) + +} + +// HandleUpdate is the callback for incoming channel updates. +func (c *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. + // curBal0 := cur.Allocation.Balance(receiverIdx, c.currencies[0]) + // nextBal0 := next.State.Allocation.Balance(receiverIdx, c.currencies[0]) + // if nextBal0.Cmp(curBal0) < 0 { + // return fmt.Errorf("Invalid balance: %v", nextBal0) + // } + for _, currency := range c.currencies { + curBal := cur.Allocation.Balance(receiverIdx, currency) + nextBal := next.State.Allocation.Balance(receiverIdx, currency) + if nextBal.Cmp(curBal) < 0 { + return fmt.Errorf("invalid balance for asset %v: %v", currency, nextBal) + } + } + + return nil + }() + if err != nil { + r.Reject(context.TODO(), err.Error()) //nolint:errcheck // It's OK if rejection fails. + } + + // Send the acceptance message. + err = r.Accept(context.TODO()) + if err != nil { + panic(err) + } +} + +// HandleAdjudicatorEvent is the callback for smart contract events. +func (c *PaymentClient) HandleAdjudicatorEvent(e channel.AdjudicatorEvent) { + log.Printf("Adjudicator event: type = %T, client = %v", e, c.account.Address()) +} + +func (c *PaymentClient) GetChannel() (*PaymentChannel, error) { + select { + case channel := <-c.channels: + c.channels <- channel // Put the channel back into the channels channel. + return channel, nil + case <-time.After(time.Second): // Set a timeout duration (e.g., 1 second). + return nil, errors.New("no channel available") + } +} diff --git a/payment-channel-xlm/go.mod b/payment-channel-xlm/go.mod new file mode 100644 index 0000000..47751e6 --- /dev/null +++ b/payment-channel-xlm/go.mod @@ -0,0 +1,33 @@ +module payment_channel_xlm + +go 1.22 + +require ( + perun.network/go-perun v0.11.0 + perun.network/perun-stellar-backend v0.2.1-0.20240718123359-71880f44c57e +) + +require ( + github.com/creachadair/jrpc2 v1.1.0 // indirect + github.com/creachadair/mds v0.0.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-chi/chi v4.1.2+incompatible // indirect + github.com/go-errors/errors v1.5.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/schema v1.2.0 // indirect + github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/stellar/go v0.0.0-20240426134303-a387ffb88fd5 // indirect + github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 // indirect + github.com/stretchr/objx v0.5.1 // indirect + github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37 // indirect +) diff --git a/payment-channel-xlm/go.sum b/payment-channel-xlm/go.sum new file mode 100644 index 0000000..c00ef9b --- /dev/null +++ b/payment-channel-xlm/go.sum @@ -0,0 +1,127 @@ +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/creachadair/jrpc2 v1.1.0 h1:SgpJf0v1rVCZx68+4APv6dgsTFsIHlpgFD1NlQAWA0A= +github.com/creachadair/jrpc2 v1.1.0/go.mod h1:5jN7MKwsm8qvgfTsTzLX3JIfidsAkZ1c8DZSQmp+g38= +github.com/creachadair/mds v0.0.1 h1:2nX6Sww4dXpScx3b6aYjH1n7iuEH715+jj+cKkKw9BY= +github.com/creachadair/mds v0.0.1/go.mod h1:caBACU+n1Q/rZ252FTzfnG0/H+ZUi+UnIQtEOraMv/g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/monotime v0.0.0-20161010190848-47d58efa6955 h1:gmtGRvSexPU4B1T/yYo0sLOKzER1YT+b4kPxPpm0Ty4= +github.com/gavv/monotime v0.0.0-20161010190848-47d58efa6955/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA= +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5 h1:oERTZ1buOUYlpmKaqlO5fYmz8cZ1rYu5DieJzF4ZVmU= +github.com/google/go-querystring v0.0.0-20160401233042-9235644dd9e5/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/jarcoal/httpmock v0.0.0-20161210151336-4442edb3db31 h1:Aw95BEvxJ3K6o9GGv5ppCd1P8hkeIeEJ30FO+OhOJpM= +github.com/jarcoal/httpmock v0.0.0-20161210151336-4442edb3db31/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739 h1:ykXz+pRRTibcSjG1yRhpdSHInF8yZY/mfn+Rz2Nd1rE= +github.com/manucorporat/sse v0.0.0-20160126180136-ee05b128a739/go.mod h1:zUx1mhth20V3VKgL5jbd1BSQcW4Fy6Qs4PZvQwRFwzM= +github.com/moul/http2curl v0.0.0-20161031194548-4e24498b31db h1:eZgFHVkk9uOTaOQLC6tgjkzdp7Ays8eEVecBcfHZlJQ= +github.com/moul/http2curl v0.0.0-20161031194548-4e24498b31db/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2 h1:S4OC0+OBKz6mJnzuHioeEat74PuQ4Sgvbf8eus695sc= +github.com/segmentio/go-loggly v0.5.1-0.20171222203950-eb91657e62b2/go.mod h1:8zLRYR5npGjaOXgPSKat5+oOh+UHd8OdbS18iqX9F6Y= +github.com/sergi/go-diff v0.0.0-20161205080420-83532ca1c1ca h1:oR/RycYTFTVXzND5r4FdsvbnBn0HJXSVeNAnwaTXRwk= +github.com/sergi/go-diff v0.0.0-20161205080420-83532ca1c1ca/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stellar/go v0.0.0-20240426134303-a387ffb88fd5 h1:acTkI/sflfu0qZ7lh1oH8G5qdNJhSU6TJIONQe0IZ7k= +github.com/stellar/go v0.0.0-20240426134303-a387ffb88fd5/go.mod h1:akvnE6X/mbM4XFwlbF9D1/Wtgh65neAf5FXXQdcFjmY= +github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 h1:OzCVd0SV5qE3ZcDeSFCmOWLZfEWZ3Oe8KtmSOYKEVWE= +github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/xdrpp/goxdr v0.1.1 h1:E1B2c6E8eYhOVyd7yEpOyopzTPirUeF6mVOfXfGyJyc= +github.com/xdrpp/goxdr v0.1.1/go.mod h1:dXo1scL/l6s7iME1gxHWo2XCppbHEKZS7m/KyYWkNzA= +github.com/xeipuuv/gojsonpointer v0.0.0-20151027082146-e0fe6f683076 h1:KM4T3G70MiR+JtqplcYkNVoNz7pDwYaBxWBXQK804So= +github.com/xeipuuv/gojsonpointer v0.0.0-20151027082146-e0fe6f683076/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c h1:XZWnr3bsDQWAZg4Ne+cPoXRPILrNlPNQfxBuwLl43is= +github.com/xeipuuv/gojsonreference v0.0.0-20150808065054-e02fc20de94c/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20161231055540-f06f290571ce h1:cVSRGH8cOveJNwFEEZLXtB+XMnRqKLjUP6V/ZFYQCXI= +github.com/xeipuuv/gojsonschema v0.0.0-20161231055540-f06f290571ce/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/yalp/jsonpath v0.0.0-20150812003900-31a79c7593bb h1:06WAhQa+mYv7BiOk13B/ywyTlkoE/S7uu6TBKU6FHnE= +github.com/yalp/jsonpath v0.0.0-20150812003900-31a79c7593bb/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M= +github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20150405163532-d1c525dea8ce h1:888GrqRxabUce7lj4OaoShPxodm3kXOMpSa85wdYzfY= +github.com/yudai/golcs v0.0.0-20150405163532-d1c525dea8ce/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/gavv/httpexpect.v1 v1.0.0-20170111145843-40724cf1e4a0 h1:r5ptJ1tBxVAeqw4CrYWhXIMr0SybY3CDHuIbCg5CFVw= +gopkg.in/gavv/httpexpect.v1 v1.0.0-20170111145843-40724cf1e4a0/go.mod h1:WtiW9ZA1LdaWqtQRo1VbIL/v4XZ8NDta+O/kSpGgVek= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +perun.network/go-perun v0.11.0 h1:25aL0MsyXQ2rHziOnMwJMe70K6NTCbopZMwX67qxt/k= +perun.network/go-perun v0.11.0/go.mod h1:pY/1pJ2OMlCQgEbnfGh9wVfRMJtqN0iAKsiJBLH0/Gc= +perun.network/perun-stellar-backend v0.2.1-0.20240718123359-71880f44c57e h1:ol2v47C/tqq7PvaDTJsevtkQnK1NFb/cU51VoaSdPF4= +perun.network/perun-stellar-backend v0.2.1-0.20240718123359-71880f44c57e/go.mod h1:QN9OM1aOwg/mMnjNs+Fzezqzvkh4Cji0RPY1rGhjl1c= +polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37 h1:iA5GzEa/hHfVlQpimEjPV09NATwHXxSjWNB0VVodtew= +polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37/go.mod h1:XUBrNtqgEhN3EEOP/5gh7IBd3xVHKidCjXDZfl9+kMU= diff --git a/payment-channel-xlm/main.go b/payment-channel-xlm/main.go new file mode 100644 index 0000000..b988569 --- /dev/null +++ b/payment-channel-xlm/main.go @@ -0,0 +1,105 @@ +// Copyright 2024 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package main + +import ( + "log" + "math/big" + "payment_channel_xlm/client" + "payment_channel_xlm/util" + + "perun.network/go-perun/channel" + "perun.network/go-perun/wire" +) + +func main() { + // Initialize setup (replaces the test setup) + log.Println("Starting initial setup") + setup, err := util.NewExampleSetup() + if err != nil { + panic(err) + } + + payment_example(setup) +} + +func payment_example(setup *util.Setup) { + log.Println("Creating Accounts for Alice and Bob") + accAlice := setup.GetAccounts()[0] + accBob := setup.GetAccounts()[1] + wAlice := setup.GetWallets()[0] + wBob := setup.GetWallets()[1] + funderAlice := setup.GetFunders()[0] + funderBob := setup.GetFunders()[1] + adjAlice := setup.GetAdjudicators()[0] + adjBob := setup.GetAdjudicators()[1] + + log.Println("Initializing a connection between Alice and Bob") + bus := wire.NewLocalBus() + log.Println("Setup payment clients for Alice and Bob") + alicePerun, err := client.SetupPaymentClient(wAlice, accAlice, setup.GetTokenAsset(), bus, funderAlice, adjAlice) + if err != nil { + panic(err) + } + bobPerun, err := client.SetupPaymentClient(wBob, accBob, setup.GetTokenAsset(), bus, funderBob, adjBob) + if err != nil { + panic(err) + } + + log.Println("Setting initial balances") + balances := channel.Balances{ + {big.NewInt(1000), big.NewInt(100)}, + {big.NewInt(0), big.NewInt(1000)}, + } + + log.Println("Alice opens a channel with Bob") + alicePerun.OpenChannel(bobPerun.WireAddress(), balances) + aliceChannel := alicePerun.Channel + bobChannel := bobPerun.AcceptedChannel() + + printBalances(alicePerun.Channel.GetChannelState().Balances) + + log.Println("Alice sends payment to Bob") + + aliceChannel.SendPayment(500, 1) + printBalances(alicePerun.Channel.GetChannelState().Balances) + + log.Println("Bob sends payment to Alice") + + bobChannel.SendPayment(250, 1) + printBalances(alicePerun.Channel.GetChannelState().Balances) + + log.Println("Channel is being settled") + aliceChannel.Settle() + bobChannel.Settle() + + alicePerun.Shutdown() + bobPerun.Shutdown() + + log.Println("Done") +} + +func printBalances(balances channel.Balances) { + log.Println("Channel Balances:") + + // Manually print for Asset 1 + log.Printf("Asset 1:\n") + log.Printf(" Alice: %s\n", balances[0][0].String()) + log.Printf(" Bob: %s\n", balances[0][1].String()) + + // Manually print for Asset 2 + log.Printf("Asset 2:\n") + log.Printf(" Alice: %s\n", balances[1][0].String()) + log.Printf(" Bob: %s\n", balances[1][1].String()) +} diff --git a/payment-channel-xlm/quickstart.sh b/payment-channel-xlm/quickstart.sh new file mode 100644 index 0000000..229017c --- /dev/null +++ b/payment-channel-xlm/quickstart.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +set -e + +case "$1" in +standalone) + echo "Using standalone network" + ARGS="--local" + ;; +futurenet) + echo "Using Futurenet network" + ARGS="--futurenet" + ;; +*) + echo "Usage: $0 standalone|futurenet" + exit 1 + ;; +esac + +# this is set to the quickstart `soroban-dev` image annointed as the release +# for a given Soroban Release, it is captured on Soroban Releases - https://soroban.stellar.org/docs/reference/releases +QUICKSTART_SOROBAN_DOCKER_SHA=stellar/quickstart:latest@sha256:1a82b17a4fae853d24189dd25d4e6b774fa7a1b6356a993e618c6e9bd2f3e04c + +shift + +# Run the soroban-preview container +# Remember to do: +# make build-docker + +echo "Creating docker soroban network" +(docker network inspect soroban-network -f '{{.Id}}' 2>/dev/null) \ + || docker network create soroban-network + +echo "Searching for a previous soroban-preview docker container" +containerID=$(docker ps --filter="name=soroban-preview" --all --quiet) +if [[ ${containerID} ]]; then + echo "Start removing soroban-preview container." + docker rm --force soroban-preview + echo "Finished removing soroban-preview container." +else + echo "No previous soroban-preview container was found" +fi + +currentDir=$(pwd) +docker run -d \ + --volume ${currentDir}:/workspace \ + --name soroban-preview \ + -p 8001:8000 \ + --ipc=host \ + --network soroban-network \ + soroban-preview:10 + +# Run the stellar quickstart image + +docker run --rm \ + --name stellar \ + --pull always \ + --network soroban-network \ + -p 8000:8000 \ + "$QUICKSTART_SOROBAN_DOCKER_SHA" \ + $ARGS \ + --enable-soroban-rpc \ + "$@" # Pass through args from the CLI diff --git a/payment-channel-xlm/testdata/docker/build.sh b/payment-channel-xlm/testdata/docker/build.sh new file mode 100644 index 0000000..8f9f02d --- /dev/null +++ b/payment-channel-xlm/testdata/docker/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Build image and tag it with image name and version +docker build . \ + --tag soroban-preview:10 \ + --force-rm \ + --rm diff --git a/payment-channel-xlm/testdata/perun_soroban_multi_contract.wasm b/payment-channel-xlm/testdata/perun_soroban_multi_contract.wasm new file mode 100644 index 0000000000000000000000000000000000000000..05701f50851a6b9e15273cbc2c2b7d954d058725 GIT binary patch literal 23664 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 S)v=+8@u4W19U7gTRsSE%Z0Dl@ literal 0 HcmV?d00001 diff --git a/payment-channel-xlm/testdata/perun_soroban_token.wasm b/payment-channel-xlm/testdata/perun_soroban_token.wasm new file mode 100644 index 0000000000000000000000000000000000000000..eb74d2c9a016a27d20801b119054c90836a25601 GIT binary patch literal 7347 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 H_&EI=n4R?k literal 0 HcmV?d00001 diff --git a/payment-channel-xlm/util/setup.go b/payment-channel-xlm/util/setup.go new file mode 100644 index 0000000..abb3970 --- /dev/null +++ b/payment-channel-xlm/util/setup.go @@ -0,0 +1,337 @@ +// Copyright 2024 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "context" + "crypto/rand" + "encoding/binary" + "fmt" + "log" + mathrand "math/rand" + "path/filepath" + "runtime" + "time" + + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/keypair" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" + pchannel "perun.network/go-perun/channel" + "perun.network/perun-stellar-backend/channel" + "perun.network/perun-stellar-backend/channel/types" + "perun.network/perun-stellar-backend/client" + "perun.network/perun-stellar-backend/wallet" + "perun.network/perun-stellar-backend/wire/scval" +) + +const ( + PerunContractPath = "payment-channel-xlm/testdata/perun_soroban_multi_contract.wasm" + StellarAssetContractPath = "payment-channel-xlm/testdata/perun_soroban_token.wasm" + initLumensBalance = "10000000" + initTokenBalance = uint64(2000000) + DefaultTestTimeout = 30 +) + +type Setup struct { + accs []*wallet.Account + ws []*wallet.EphemeralWallet + cbs []*client.ContractBackend + Rng *mathrand.Rand + funders []*channel.Funder + adjs []*channel.Adjudicator + assetIDs []pchannel.Asset +} + +func (s *Setup) GetStellarClients() []*client.ContractBackend { + return s.cbs +} + +func (s *Setup) GetFunders() []*channel.Funder { + return s.funders +} + +func (s *Setup) GetAdjudicators() []*channel.Adjudicator { + return s.adjs +} + +func (s *Setup) GetTokenAsset() []pchannel.Asset { + return s.assetIDs +} + +func (s *Setup) GetAccounts() []*wallet.Account { + return s.accs +} + +func (s *Setup) GetWallets() []*wallet.EphemeralWallet { + return s.ws +} + +func getProjectRoot() (string, error) { + _, b, _, _ := runtime.Caller(1) + basepath := filepath.Dir(b) + + fp, err := filepath.Abs(filepath.Join(basepath, "../..")) //filepath.Abs(filepath.Join(basepath, "../..")) + return fp, err +} + +func getDataFilePath(filename string) (string, error) { + root, err := getProjectRoot() + if err != nil { + return "", err + } + + fp := filepath.Join(root, "", filename) + return fp, nil +} + +func NewExampleSetup() (*Setup, error) { + + accs, kpsToFund, ws := MakeRandPerunAccsWallets(5) + + if err := CreateFundStellarAccounts(kpsToFund, initLumensBalance); err != nil { + return nil, fmt.Errorf("error funding accounts: %w", err) + } + + depTokenOneKp := kpsToFund[2] + depTokenTwoKp := kpsToFund[3] + + depTokenKps := []*keypair.Full{depTokenOneKp, depTokenTwoKp} + + epPerunKp := kpsToFund[4] + + relPathPerun, err := getDataFilePath(PerunContractPath) + if err != nil { + return nil, fmt.Errorf("error getting Perun contract path: %w", err) + } + relPathAsset, err := getDataFilePath(StellarAssetContractPath) + if err != nil { + return nil, fmt.Errorf("error getting asset contract path: %w", err) + } + + perunAddress, _ := Deploy(epPerunKp, relPathPerun) + + tokenAddressOne, _ := Deploy(depTokenOneKp, relPathAsset) + tokenAddressTwo, _ := Deploy(depTokenTwoKp, relPathAsset) + + tokenAddresses := []xdr.ScAddress{tokenAddressOne, tokenAddressTwo} + + InitTokenContract(depTokenOneKp, tokenAddressOne) + InitTokenContract(depTokenTwoKp, tokenAddressTwo) + + SetupAccountsAndContracts(depTokenKps, kpsToFund[:2], tokenAddresses, initTokenBalance) + + var assetContractIDs []pchannel.Asset + + for _, tokenAddress := range tokenAddresses { + assetContractID, err := types.NewStellarAssetFromScAddress(tokenAddress) + if err != nil { + return nil, err + } + assetContractIDs = append(assetContractIDs, assetContractID) + } + + cbs := NewContractBackendsFromKeys(kpsToFund[:2]) + + aliceCB := cbs[0] + aliceWallet := ws[0] + + bobCB := cbs[1] + bobWallet := ws[1] + + channelAccs := []*wallet.Account{accs[0], accs[1]} + channelCBs := []*client.ContractBackend{aliceCB, bobCB} + channelWallets := []*wallet.EphemeralWallet{aliceWallet, bobWallet} + + funders, adjs := CreateFundersAndAdjudicators(channelAccs, cbs, perunAddress, tokenAddresses) + + setup := Setup{ + accs: channelAccs, + ws: channelWallets, + cbs: channelCBs, + funders: funders, + adjs: adjs, + assetIDs: assetContractIDs, + } + + return &setup, nil +} + +func SetupAccountsAndContracts(deployerKps []*keypair.Full, kps []*keypair.Full, tokenAddresses []xdr.ScAddress, tokenBalance uint64) { + + for i := range deployerKps { + for _, kp := range kps { + addr, err := types.MakeAccountAddress(kp) + if err != nil { + panic(err) + } + MintToken(deployerKps[i], tokenAddresses[i], tokenBalance, addr) + } + } +} +func CreateFundersAndAdjudicators(accs []*wallet.Account, cbs []*client.ContractBackend, perunAddress xdr.ScAddress, tokenScAddresses []xdr.ScAddress) ([]*channel.Funder, []*channel.Adjudicator) { + funders := make([]*channel.Funder, len(accs)) + adjs := make([]*channel.Adjudicator, len(accs)) + + tokenVecAddresses := scval.MakeScVecFromScAddresses(tokenScAddresses) + + for i, acc := range accs { + funders[i] = channel.NewFunder(acc, cbs[i], perunAddress, tokenVecAddresses) + adjs[i] = channel.NewAdjudicator(acc, cbs[i], perunAddress, tokenVecAddresses) + } + return funders, adjs +} + +func NewContractBackendsFromKeys(kps []*keypair.Full) []*client.ContractBackend { + cbs := make([]*client.ContractBackend, len(kps)) + // generate Configs + for i, kp := range kps { + cbs[i] = NewContractBackendFromKey(kp) + } + return cbs +} + +func NewContractBackendFromKey(kp *keypair.Full) *client.ContractBackend { + trConfig := client.TransactorConfig{} + trConfig.SetKeyPair(kp) + return client.NewContractBackend(&trConfig) +} + +func MakeRandPerunAccsWallets(count int) ([]*wallet.Account, []*keypair.Full, []*wallet.EphemeralWallet) { + accs := make([]*wallet.Account, count) + kps := make([]*keypair.Full, count) + ws := make([]*wallet.EphemeralWallet, count) + + for i := 0; i < count; i++ { + acc, kp, w := MakeRandPerunAccWallet() + accs[i] = acc + kps[i] = kp + ws[i] = w + } + return accs, kps, ws +} + +func MakeRandPerunAcc() (*wallet.Account, *keypair.Full) { + w := wallet.NewEphemeralWallet() + + var b [8]byte + _, err := rand.Read(b[:]) + if err != nil { + panic(err) + } + + seed := binary.LittleEndian.Uint64(b[:]) + + r := mathrand.New(mathrand.NewSource(int64(seed))) + + acc, kp, err := w.AddNewAccount(r) + if err != nil { + panic(err) + } + return acc, kp +} + +func MakeRandPerunAccWallet() (*wallet.Account, *keypair.Full, *wallet.EphemeralWallet) { + w := wallet.NewEphemeralWallet() + + var b [8]byte + _, err := rand.Read(b[:]) + if err != nil { + panic(err) + } + + seed := binary.LittleEndian.Uint64(b[:]) + + r := mathrand.New(mathrand.NewSource(int64(seed))) + + acc, kp, err := w.AddNewAccount(r) + if err != nil { + panic(err) + } + return acc, kp, w +} + +func CreateFundStellarAccounts(pairs []*keypair.Full, initialBalance string) error { + + numKps := len(pairs) + + masterClient := client.NewHorizonMasterClient() + masterHzClient := masterClient.GetHorizonClient() + sourceKey := keypair.Root(client.NETWORK_PASSPHRASE) + + hzClient := client.NewHorizonClient() + + ops := make([]txnbuild.Operation, numKps) + + accReq := horizonclient.AccountRequest{AccountID: masterClient.GetAddress()} + sourceAccount, err := masterHzClient.AccountDetail(accReq) + if err != nil { + panic(err) + } + + masterAccount := txnbuild.SimpleAccount{ + AccountID: masterClient.GetAddress(), + Sequence: sourceAccount.Sequence, + } + + for i := 0; i < numKps; i++ { + pair := pairs[i] + + ops[i] = &txnbuild.CreateAccount{ + SourceAccount: masterAccount.AccountID, + Destination: pair.Address(), + Amount: initialBalance, + } + } + + txParams := client.GetBaseTransactionParamsWithFee(&masterAccount, txnbuild.MinBaseFee, ops...) + + txSigned, err := client.CreateSignedTransactionWithParams([]*keypair.Full{sourceKey}, txParams) + + if err != nil { + panic(err) + } + _, err = hzClient.SubmitTransaction(txSigned) + if err != nil { + panic(err) + } + + accounts := make([]txnbuild.Account, numKps) + for i, kp := range pairs { + request := horizonclient.AccountRequest{AccountID: kp.Address()} + account, err := hzClient.AccountDetail(request) + if err != nil { + panic(err) + } + + accounts[i] = &account + } + + for _, keys := range pairs { + log.Printf("Funded Stellar L1 account %s (%s) with %s XLM.\n", + keys.Seed(), keys.Address(), initialBalance) + } + + return nil +} + +func (s *Setup) NewCtx(testTimeout float64) context.Context { + timeout := time.Duration(testTimeout * float64(time.Second)) + ctx, cancel := context.WithTimeout(context.Background(), timeout) + if cancel != nil { + return nil + } + return ctx +} diff --git a/payment-channel-xlm/util/token.go b/payment-channel-xlm/util/token.go new file mode 100644 index 0000000..163d06e --- /dev/null +++ b/payment-channel-xlm/util/token.go @@ -0,0 +1,240 @@ +// Copyright 2024 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package util + +import ( + "errors" + + "github.com/stellar/go/clients/horizonclient" + "github.com/stellar/go/keypair" + "github.com/stellar/go/xdr" + "perun.network/perun-stellar-backend/channel" + "perun.network/perun-stellar-backend/channel/types" + "perun.network/perun-stellar-backend/client" + "perun.network/perun-stellar-backend/event" + "perun.network/perun-stellar-backend/wire/scval" +) + +const tokenDecimals = uint32(7) +const tokenName = "PerunToken" +const tokenSymbol = "PRN" + +type TokenParams struct { + decimals uint32 + name string + symbol string +} + +func NewTokenParams() *TokenParams { + return &TokenParams{ + decimals: tokenDecimals, + name: tokenName, + symbol: tokenSymbol, + } +} + +func (t *TokenParams) GetDecimals() uint32 { + return t.decimals +} + +func (t *TokenParams) GetName() string { + return t.name +} + +func (t *TokenParams) GetSymbol() string { + return t.symbol +} + +func BuildInitTokenArgs(adminAddr xdr.ScAddress, decimals uint32, tokenName string, tokenSymbol string) (xdr.ScVec, error) { + + adminScAddr, err := scval.WrapScAddress(adminAddr) + if err != nil { + panic(err) + } + + decim := xdr.Uint32(decimals) + scvaltype := xdr.ScValTypeScvU32 + decimSc, err := xdr.NewScVal(scvaltype, decim) + if err != nil { + panic(err) + } + + tokenNameScString := xdr.ScString(tokenName) + tokenNameXdr := scval.MustWrapScString(tokenNameScString) + + tokenSymbolString := xdr.ScString(tokenSymbol) + tokenSymbolXdr := scval.MustWrapScString(tokenSymbolString) + + initTokenArgs := xdr.ScVec{ + adminScAddr, + decimSc, + tokenNameXdr, + tokenSymbolXdr, + } + + return initTokenArgs, nil +} + +func InitTokenContract(kp *keypair.Full, contractIDAddress xdr.ScAddress) error { + + cb := NewContractBackendFromKey(kp) + + adminScAddr, err := types.MakeAccountAddress(kp) + if err != nil { + panic(err) + } + + tokenParams := NewTokenParams() + decimals := tokenParams.GetDecimals() + name := tokenParams.GetName() + symbol := tokenParams.GetSymbol() + + initArgs, err := BuildInitTokenArgs(adminScAddr, decimals, name, symbol) + if err != nil { + panic(err) + } + + txMeta, err := cb.InvokeSignedTx("initialize", initArgs, contractIDAddress) + if err != nil { + return errors.New("error while invoking and processing host function: initialize" + err.Error()) + } + + _, err = event.DecodeEventsPerun(txMeta) + if err != nil { + return err + } + + return nil +} + +func GetTokenName(kp *keypair.Full, contractAddress xdr.ScAddress) error { + + cb := NewContractBackendFromKey(kp) + TokenNameArgs := xdr.ScVec{} + + _, err := cb.InvokeSignedTx("name", TokenNameArgs, contractAddress) + if err != nil { + panic(err) + } + + return nil +} + +func BuildGetTokenBalanceArgs(balanceOf xdr.ScAddress) (xdr.ScVec, error) { + + recScAddr, err := scval.WrapScAddress(balanceOf) + if err != nil { + panic(err) + } + + GetTokenBalanceArgs := xdr.ScVec{ + recScAddr, + } + + return GetTokenBalanceArgs, nil +} + +func BuildTransferTokenArgs(from xdr.ScAddress, to xdr.ScAddress, amount xdr.Int128Parts) (xdr.ScVec, error) { + + fromScAddr, err := scval.WrapScAddress(from) + if err != nil { + panic(err) + } + + toScAddr, err := scval.WrapScAddress(to) + if err != nil { + panic(err) + } + + amountSc, err := scval.WrapInt128Parts(amount) + if err != nil { + panic(err) + } + + GetTokenBalanceArgs := xdr.ScVec{ + fromScAddr, + toScAddr, + amountSc, + } + + return GetTokenBalanceArgs, nil +} + +func Deploy(kp *keypair.Full, contractPath string) (xdr.ScAddress, xdr.Hash) { + deployerCB := NewContractBackendFromKey(kp) + tr := deployerCB.GetTransactor() + hzClient := tr.GetHorizonClient() + deployerAccReq := horizonclient.AccountRequest{AccountID: kp.Address()} + deployerAcc, err := hzClient.AccountDetail(deployerAccReq) + if err != nil { + panic(err) + } + + installContractOpInstall := channel.AssembleInstallContractCodeOp(kp.Address(), contractPath) + preFlightOp, _ := client.PreflightHostFunctions(hzClient, &deployerAcc, *installContractOpInstall) + + minFeeInstallCustom := 500000 + txParamsInstall := client.GetBaseTransactionParamsWithFee(&deployerAcc, int64(minFeeInstallCustom), &preFlightOp) + txSignedInstall, err := client.CreateSignedTransactionWithParams([]*keypair.Full{kp}, txParamsInstall) + if err != nil { + panic(err) + } + + _, err = hzClient.SubmitTransaction(txSignedInstall) + if err != nil { + panic(err) + } + + createContractOp := channel.AssembleCreateContractOp(kp.Address(), contractPath, "a1", client.NETWORK_PASSPHRASE) + preFlightOpCreate, _ := client.PreflightHostFunctions(hzClient, &deployerAcc, *createContractOp) + txParamsCreate := client.GetBaseTransactionParamsWithFee(&deployerAcc, int64(minFeeInstallCustom), &preFlightOpCreate) + txSignedCreate, err := client.CreateSignedTransactionWithParams([]*keypair.Full{kp}, txParamsCreate) + if err != nil { + panic(err) + } + + _, err = hzClient.SubmitTransaction(txSignedCreate) + if err != nil { + panic(err) + } + + contractID := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadWrite[0].MustContractData().Contract.ContractId + contractHash := preFlightOpCreate.Ext.SorobanData.Resources.Footprint.ReadOnly[0].MustContractCode().Hash + contractIDAddress := xdr.ScAddress{ + Type: xdr.ScAddressTypeScAddressTypeContract, + ContractId: contractID, + } + + return contractIDAddress, contractHash +} + +func MintToken(kp *keypair.Full, contractAddr xdr.ScAddress, amount uint64, recipientAddr xdr.ScAddress) error { + cb := NewContractBackendFromKey(kp) + + amountTo128Xdr := xdr.Int128Parts{Hi: 0, Lo: xdr.Uint64(amount)} + + amountSc, err := xdr.NewScVal(xdr.ScValTypeScvI128, amountTo128Xdr) + if err != nil { + panic(err) + } + mintTokenArgs, err := client.BuildMintTokenArgs(recipientAddr, amountSc) + if err != nil { + panic(err) + } + _, err = cb.InvokeSignedTx("mint", mintTokenArgs, contractAddr) + if err != nil { + panic(err) + } + return nil +}