This repository has been archived by the owner on Feb 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 132
Replace pseudocode CAP-33 examples with real SDK code. #254
Merged
+249
−32
Merged
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e1ef88e
Add example of sponsorship transfer/revocation
Shaptic f50ed2b
Update CAP-33 example to be comprehensive
Shaptic 9da840b
Fix unclosed CodeExample tag
Shaptic daeab21
Link to SignAndSend(...)
Shaptic 9fc83ec
Add missing import
Shaptic 7d8d93f
Add error checks and use clearer variable name
Shaptic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -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. | ||
|
@@ -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, | ||
}, | ||
&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{}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like the transaction source account is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call, I'll consolidate this. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should
sponsoredAccount
beaAccount
here? If so, the other uses ofsponsoredAccount
should probably be updated to the corresponding account as well.There was a problem hiding this comment.
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, whereassponsoredAccount
is just its public key (you can seesponsoredAccount := 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?There was a problem hiding this comment.
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 theSponsoredID
, which is just another way to say sponsored account. You could add a comment to clarify that. What do you think?There was a problem hiding this comment.
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?There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to use
aAddress
sinceA
was taken lol but hopefully still gets the point across.