Skip to content
This repository has been archived by the owner on Feb 23, 2023. It is now read-only.

Replace pseudocode CAP-33 examples with real SDK code. #254

Merged
merged 6 commits into from
Oct 15, 2020
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 245 additions & 32 deletions content/docs/glossary/sponsored-reserves.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ title: Sponsored Reserves
order:
---

import { CodeExample } from "components/CodeExample";

Protocol 14 introduces operations that allow an account to pay the base reserves for another account. This is done by using the [Begin Sponsoring Future Reserves](../start/list-of-operations.mdx#begin-sponsoring-future-reserves) and [End Sponsoring Future Reserves](../start/list-of-operations.mdx#end-sponsoring-future-reserves) operations.

The sponsoring account establishes the is-sponsoring-future-reserves-for relationship, and the sponsored account terminates it. While this relationship exists, reserve requirements that would normally accumulate on the sponsored account will now accumulate on the sponsoring account. Both operations must appear in a single transaction, which guarantees that both the sponsoring and sponsored accounts agree to every sponsorship.
Expand Down Expand Up @@ -33,35 +35,6 @@ Anything that increases the minimum balance can be sponsored (Accounts, Offers,

At the end of any transaction, there must be no ongoing is-sponsoring-future-reserves-for relationships. This is why these two operations must be used together in a single transaction.

### Simple Example

In the example below, `A` is sponsoring a trustline for `B`. Notice how the `CHANGE_TRUST` operation is sandwiched between the begin and end sponsoring operations.

```
sourceAccount: B
operations[0]:
sourceAccount: A
body:
type: BEGIN_SPONSORING_FUTURE_RESERVES
sponsoredID: B
operations[1]:
sourceAccount: B
body:
type: CHANGE_TRUST
line: X
limit: INT64_MAX
operations[2]:
sourceAccount: B
body:
type: END_SPONSORING_FUTURE_RESERVES
```

#### Other Examples

https://github.com/stellar/stellar-protocol/blob/master/core/cap-0033.md#example-sponsoring-account-creation

https://github.com/stellar/stellar-protocol/blob/master/core/cap-0033.md#example-two-trust-lines-with-different-sponsors

### Revoke Sponsorship

[Revoke Sponsorship](../start/list-of-operations.mdx#revoke-sponsorship) is the third and final operation relevant to sponsorships. It allows the sponsoring account to remove/transfer sponsorships of existing ledgerEntries and signers. If the ledgerEntry/signer is not sponsored, the owner of the ledgerEntry/signer can establish a sponsorship if it is the beneficiary of a is-sponsoring-future-reserves-for relationship.
Expand All @@ -85,8 +58,248 @@ See [Revoke Sponsorship](../start/list-of-operations.mdx#revoke-sponsorship) for

The logic above does not detail any of the error cases, which are specified [here](../start/list-of-operations.mdx#revoke-sponsorship).

#### Examples
## Examples
Each example builds on itself, referencing variables from previous snippets. We'll demonstrate a few different things you can do with sponsoring:
- Sponsor creation of a trustline for another account.
- Sponsor **two** trustlines for an account via two *different* sponsors.
- Transfer sponsorship responsibility from one account to another.
- Revoke sponsorship by an account entirely.

(For brevity, we'll assume the existence of a `SignAndSend(...)` method (defined [below](#footnote)) which will creates and submits a transaction with the proper parameters and error-checking.

### Preamble
We'll start by including the boilerplate of account and asset creation.

<CodeExample>

```go
package main

import (
"fmt"
"net/http"

sdk "github.com/stellar/go/clients/horizonclient"
"github.com/stellar/go/keypair"
"github.com/stellar/go/network"
protocol "github.com/stellar/go/protocols/horizon"
"github.com/stellar/go/txnbuild"
)

func main() {
client := sdk.DefaultTestNetClient

// Both S1 and S2 will be sponsors for A at various points in time.
S1, A, S2 := keypair.MustRandom(), keypair.MustRandom(), keypair.MustRandom()
sponsoredAccount := A.Address()

for _, pair := range []*keypair.Full{S1, A, S2} {
resp, err := http.Get("https://friendbot.stellar.org/?addr=" + pair.Address())
if err != nil {
panic(err)
}
resp.Body.Close()
fmt.Println("Funded", pair.Address())
}

//
// NOTE: Error checks are omitted for brevity; always check return values!
//

// Load the corresponding account for both A and C.
s1Account, err := client.AccountDetail(sdk.AccountRequest{AccountID: S1.Address()})
aAccount, err := client.AccountDetail(sdk.AccountRequest{AccountID: A.Address()})
s2Account, err := client.AccountDetail(sdk.AccountRequest{AccountID: S2.Address()})

// Arbitrary assets to sponsor trustlines for. Let's assume they make sense.
assets := []txnbuild.CreditAsset{
txnbuild.CreditAsset{Code: "ABCD", Issuer: S1.Address()},
txnbuild.CreditAsset{Code: "EFGH", Issuer: S1.Address()},
txnbuild.CreditAsset{Code: "IJKL", Issuer: S2.Address()},
}
}
```

</CodeExample>


### Sponsoring a Trustline
Now, let's sponsor trustlines for Account A. Notice how the `CHANGE_TRUST` operation is sandwiched between the begin and end sponsoring operations and that all relevant accounts need to sign the transaction.

<CodeExample>

```go
//
// 1. S1 will sponsor a trustline for Account A.
//
sponsorTrustline := []txnbuild.Operation{
&txnbuild.BeginSponsoringFutureReserves{
SourceAccount: &s1Account,
SponsoredID: sponsoredAccount,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should sponsoredAccount be aAccount here? If so, the other uses of sponsoredAccount should probably be updated to the corresponding account as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aAccount is the full key-pair, whereas sponsoredAccount is just its public key (you can see sponsoredAccount := aAccount.Address() happen earlier). I figured it was a more descriptive variable name, but would you say it's more confusing since it implies a difference between the two?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. Yeah I was a little confused about what was going on here, but that's my fault for not clearly reading the preamble. I think the example would be easier to understand if you just used A.Address() though. That line already specifies that you're setting the SponsoredID, which is just another way to say sponsored account. You could add a comment to clarify that. What do you think?

Copy link
Contributor Author

@Shaptic Shaptic Oct 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're right. I was trying to figure out why I went with a separate variable in the first place and it's because of this line [L206 if the link doesn't work] (and other revocations). The Account: field should be a *string type, but you can't pass &A.Address() (I'm not sure why, pretty new to Go myself; I think it's because it's an rvalue?).

I think renaming the variable to A := aAccount.Address() will kill two birds with one stone: consolidate all references to the public key and make it clear that it's still Account A, not some newly-introduced sponsored account. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that sounds like a good idea!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to use aAddress since A was taken lol but hopefully still gets the point across.

},
&txnbuild.ChangeTrust{
SourceAccount: &aAccount,
Line: &assets[0],
Limit: txnbuild.MaxTrustlineLimit,
},
&txnbuild.EndSponsoringFutureReserves{
SourceAccount: &aAccount,
},
}

// Note that while A can submit this transaction, both sign it.
SignAndSend(client, &aAccount, []*keypair.Full{S1, A}, sponsorTrustline...)
fmt.Println("Sponsored a trustline of", A.Address())

https://github.com/stellar/stellar-protocol/blob/master/core/cap-0033.md#example-revoke-sponsorship
//
// 2. Both S1 and S2 sponsor trustlines for Account A for different assets.
//
sponsorTrustline = []txnbuild.Operation{
&txnbuild.BeginSponsoringFutureReserves{
SourceAccount: &s1Account,
SponsoredID: sponsoredAccount,
},
&txnbuild.ChangeTrust{
SourceAccount: &aAccount,
Line: &assets[1],
Limit: txnbuild.MaxTrustlineLimit,
},
&txnbuild.EndSponsoringFutureReserves{},

&txnbuild.BeginSponsoringFutureReserves{
SourceAccount: &s2Account,
SponsoredID: sponsoredAccount,
},
&txnbuild.ChangeTrust{
SourceAccount: &aAccount,
Line: &assets[2],
Limit: txnbuild.MaxTrustlineLimit,
},
&txnbuild.EndSponsoringFutureReserves{},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the transaction source account is aAccount, so the source account here doesn't have to be specified. I noticed in some of your other examples you do specify the source account even if it's equal to the transaction source account. Might be a good idea to keep the usage consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, I'll consolidate this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed, I believe

}

// Note that all 3 accounts must approve/sign this transaction.
SignAndSend(client, &aAccount, []*keypair.Full{S1, S2, A}, sponsorTrustline...)
fmt.Println("Sponsored two trustlines of", A.Address())
```

</CodeExample>


### Transferring Sponsorship
Suppose that now Signer 1 wants to transfer responsibility of sponsoring reserves for the trustline to Sponsor 2. This is accomplished by sandwiching the transfer between the `BEGIN/END_SPONSORING_FUTURE_RESERVES` operations. Both of the participants must sign the transaction, though either can submit it.

<CodeExample>

```go
//
// 3. Transfer sponsorship of B's second trustline from S1 to S2.
//
transferOps := []txnbuild.Operation{
&txnbuild.BeginSponsoringFutureReserves{
SourceAccount: &s2Account,
SponsoredID: S1.Address(),
},
&txnbuild.RevokeSponsorship{
SourceAccount: &s1Account,
SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine,
Account: &sponsoredAccount,
TrustLine: &txnbuild.TrustLineID{
Account: sponsoredAccount,
Asset: assets[1],
},
},
&txnbuild.EndSponsoringFutureReserves{},
}

// Notice that while either sponsor *sends* the transaction, both sponsors
// must *approve* the transfer.
SignAndSend(client, &s1Account, []*keypair.Full{S1, S2}, transferOps...)
fmt.Println("Transferred sponsorship for", A.Address())
```

</CodeExample>

At this point, Signer 1 is only sponsoring the first asset (arbitrarily coded as `ABCD`), while Signer 2 is sponsoring the other two assets.

### Sponsorship Revocation
Finally, we can demonstrate complete revocation of sponsorships. Below, Signer 2 removes themselves from all responsibility over the two asset trustlines. Notice that Account A is not involved at all, since revocation should be performable purely at the sponsor's discretion.

<CodeExample>

```go
//
// 4. S2 revokes sponsorship of B's trustlines entirely.
//
revokeOps := []txnbuild.Operation{
&txnbuild.RevokeSponsorship{
SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine,
Account: &sponsoredAccount,
TrustLine: &txnbuild.TrustLineID{
Account: sponsoredAccount,
Asset: assets[1],
},
},
&txnbuild.RevokeSponsorship{
SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine,
Account: &sponsoredAccount,
TrustLine: &txnbuild.TrustLineID{
Account: sponsoredAccount,
Asset: assets[2],
},
},
}

SignAndSend(client, &s2Account, []*keypair.Full{S2}, revokeOps...)
fmt.Println("Revoked sponsorship for", A.Address())
```

</CodeExample>


### Other Examples
If you'd like other examples, or want to view a more-generic pseudocode breakdown of these sponsorship scenarios, you can refer to [CAP-0033](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0033.md#example-revoke-sponsorship) directly.


### Footnote
An implementation of `SignAndSend` might look something like this:

<CodeExample>

```go
// Builds a transaction containing `operations...`, signed (by `signers`), and
// submitted using the given `client` on behalf of `account`.
func SignAndSend(
client *sdk.Client,
account txnbuild.Account,
signers []*keypair.Full,
operations ...txnbuild.Operation,
) protocol.Transaction {
tx, err := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: account,
Operations: operations,
BaseFee: txnbuild.MinBaseFee,
Timebounds: txnbuild.NewInfiniteTimeout(),
IncrementSequenceNum: true,
},
)

for _, signer := range signers {
tx, err = tx.Sign(network.TestNetworkPassphrase, signer)
}

txResp, err := client.SubmitTransaction(tx)
if err != nil {
if prob := sdk.GetError(err); prob != nil {
fmt.Printf(" problem: %s\n", prob.Problem.Detail)
fmt.Printf(" extras: %s\n", prob.Problem.Extras["result_codes"])
}
panic(err)
}

return txResp
}
```

https://github.com/stellar/stellar-protocol/blob/master/core/cap-0033.md#example-transfer-sponsorship
</CodeExample>