-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CAP-0067 draft - Unified Asset Events #1613
Merged
Merged
Changes from 2 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f25770e
CAP-0064 - Unified Events
sisuresh 10f0e72
Update README
sisuresh b1f12b9
Update core/cap-0065.md
sisuresh e341960
Update
sisuresh 7df5bbb
Rename to CAP-67
sisuresh fb6e5be
Merge remote-tracking branch 'upstream/master' into unified
sisuresh 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
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 |
---|---|---|
@@ -0,0 +1,326 @@ | ||
## Preamble | ||
|
||
``` | ||
CAP: 0065 | ||
Title: Unified Asset Events | ||
Working Group: | ||
Owner: [email protected] | ||
Authors: Siddharth Suresh <@sisuresh>, Leigh McCulloch <@leighmcculloch> | ||
Consulted: Dmytro Kozhevin <@dmkozh>, Jake Urban <[email protected]>, Simon Chow <[email protected]> | ||
Status: Draft | ||
Created: 2025-01-13 | ||
Discussion: https://github.com/stellar/stellar-protocol/discussions/1553 | ||
Protocol version: 23 | ||
``` | ||
|
||
## Simple Summary | ||
|
||
Emit transfer events in Classic in the same format as what we see in Soroban so that the movement of assets can be tracked using a single stream of data. In addition to emitting events in Classic, update the events emitted in the Stellar Asset Contract to be semantically correct and compatible with SEP-41. | ||
|
||
## Motivation | ||
|
||
Tracking the movement of Stellar assets today is complex because you need to consume both Soroban events emitted by the Stellar Asset Contract and ledger entry changes for Classic operations. There are also differences between Stellar assets and custom Soroban tokens that this CAP will address so those differences will be made irrelevant to the end user. | ||
|
||
### Goals Alignment | ||
|
||
This CAP is aligned with the following Stellar Network Goals: | ||
|
||
- The Stellar Network should be secure and reliable, and should bias towards safety, simplicity, | ||
reliability, and performance over new functionality. | ||
|
||
## Abstract | ||
|
||
This CAP specifies three changes - | ||
1. Emit an event for every movement of an asset in Stellar classic. All of the added events will follow the format of the existing `transfer` event, with the exception of a new `fee` event to track fees paid by the source account. | ||
sisuresh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
2. Remove the admin from the topics of the `mint` and `clawback` events emitted in the SAC. | ||
3. Update issuer semantics in the SAC so that a `transfer` involving the issuer will emit the semantically correct event (`mint` or `burn`). | ||
|
||
The added events will not be a protocol change because they won't be hashed into the ledger, but the Stellar Asset Contract changes will be. | ||
sisuresh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Specification | ||
|
||
### XDR Changes | ||
|
||
This patch of XDR changes is based on the XDR files in commit `734bcccdbb6d1f7e794793ad3b8be51f3ba76f92` of stellar-xdr. | ||
``` | ||
diff --git a/Stellar-contract.x b/Stellar-contract.x | ||
index 5113005..112e784 100644 | ||
--- a/Stellar-contract.x | ||
+++ b/Stellar-contract.x | ||
@@ -70,7 +70,10 @@ enum SCValType | ||
// symbolic SCVals used as the key for ledger entries for a contract's | ||
// instance and an address' nonce, respectively. | ||
SCV_LEDGER_KEY_CONTRACT_INSTANCE = 20, | ||
- SCV_LEDGER_KEY_NONCE = 21 | ||
+ SCV_LEDGER_KEY_NONCE = 21, | ||
+ | ||
+ // Contains other address types that are not used within contracts | ||
+ SCV_ADDRESS_EXTENDED = 22 | ||
}; | ||
|
||
enum SCErrorType | ||
@@ -190,6 +193,20 @@ case SC_ADDRESS_TYPE_CONTRACT: | ||
Hash contractId; | ||
}; | ||
|
||
+enum SCAddressExtendedType | ||
+{ | ||
+ SC_ADDRESS_EXTENDED_TYPE_CLAIMABLE_BALANCE = 0, | ||
+ SC_ADDRESS_EXTENDED_TYPE_LIQUIDITY_POOL = 1 | ||
+}; | ||
+ | ||
+union SCAddressExtended switch (SCAddressExtendedType type) | ||
+{ | ||
+case SC_ADDRESS_EXTENDED_TYPE_CLAIMABLE_BALANCE: | ||
+ ClaimableBalanceID claimableBalanceID; | ||
+case SC_ADDRESS_EXTENDED_TYPE_LIQUIDITY_POOL: | ||
+ PoolID liquidityPoolID; | ||
+}; | ||
+ | ||
%struct SCVal; | ||
%struct SCMapEntry; | ||
|
||
@@ -271,6 +288,9 @@ case SCV_LEDGER_KEY_NONCE: | ||
|
||
case SCV_CONTRACT_INSTANCE: | ||
SCContractInstance instance; | ||
+ | ||
+case SCV_ADDRESS_EXTENDED: | ||
+ SCAddressExtended extendedAddress; | ||
}; | ||
|
||
struct SCMapEntry | ||
|
||
``` | ||
|
||
## Semantics | ||
|
||
### Remove the admin from the SAC `mint` and `clawback` events | ||
|
||
The `mint` event will look like: | ||
``` | ||
contract: asset, topics: ["mint", to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
The `clawback` event will look like: | ||
``` | ||
contract: asset, topics: ["clawback", from:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
### Emit the semantically correct event for a Stellar Asset Contract `transfer` when the issuer is involved | ||
|
||
At the moment, if the issuer is the sender in a Stellar Asset Contract `transfer`, the asset will be minted. If the issuer is the recipient, the asset will be burned. The event emitted in both scenarios, however, is the `transfer` event. This CAP changes that behavior to instead emit the `mint`/`burn` event. | ||
|
||
### New Events | ||
This section will go over the semantics of how the additional `transfer` events are emitted for each operation, as well as the `fee` event emitted for the fee paid by the source account. These events will be emitted through `TransactionMeta`, and will not be hashed into the ledger. Note that | ||
the `contract` field for these events corresponds to the Stellar Asset Contract address for the respective asset. | ||
sisuresh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#### Fees paid by source account | ||
|
||
For each transaction whose source account pays fees for the execution of a transaction, emit an event in the following format: | ||
``` | ||
contract: native asset, topics: ["fee", from:Address], data: [amount:i128] | ||
``` | ||
Where from is the account paying the fee, either the fee bump fee account or the tx source account. | ||
|
||
#### Payment | ||
Emit one of the following events - | ||
|
||
For a payment not involving the issuer, or if both the sender and receiver are the issuer: | ||
``` | ||
contract: asset, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
sisuresh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``` | ||
|
||
When sending from an issuer: | ||
``` | ||
contract: asset, topics: ["mint", to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
When sending to an issuer: | ||
``` | ||
contract: asset, topics: ["burn", from:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
#### Path Payment Strict Send / Path Payment Strict Receive | ||
For each movement of the asset created by the path payment, emit one of the following - | ||
|
||
For a movement not involving the issuer: | ||
``` | ||
contract: asset, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
When sending from an issuer: | ||
``` | ||
contract: asset, topics: ["mint", from:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
When sending to an issuer: | ||
``` | ||
contract: asset, topics: ["burn", from:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
#### Create Account | ||
Emit the following event: | ||
``` | ||
contract: native asset, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
* `from` is the account being debited (creator). | ||
* `to` is the account being credited (created). | ||
* `amount` is the starting native balance. | ||
|
||
#### Merge Account | ||
Emit the following event: | ||
``` | ||
contract: native asset, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
* `from` is the account being debited (merged). | ||
* `to` is the account being credited (merged into). | ||
* `amount` is the merged native balance. | ||
|
||
#### Create Claimable Balance | ||
Emit the following event: | ||
``` | ||
contract: asset, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
* from is the account being debited. | ||
* to is the claimable balance being created. The type of this address will be `SC_ADDRESS_EXTENDED_TYPE_CLAIMABLE_BALANCE`. | ||
* amount is the amount moved into the claimable balance. | ||
|
||
If an asset is a movement from the issuer of the asset, instead emit for the movement: | ||
``` | ||
contract: asset, topics: ["mint", to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
#### Claim Claimable Balance | ||
Emit the following event: | ||
``` | ||
contract: asset, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
* `from` is the claimable balance. The type of this address will be `SC_ADDRESS_EXTENDED_TYPE_CLAIMABLE_BALANCE`. | ||
* `to` is the account being credited | ||
* `amount` is the amount in the claimable balance | ||
|
||
If the claim is a movement to the issuer of the asset, instead emit for the movement: | ||
``` | ||
contract: asset, topics: ["burn", from:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
#### Liquidity Pool Deposit | ||
Emit the following events: | ||
``` | ||
contract: assetA, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
contract: assetB, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
* `from` is the account being debited. | ||
* `to` is the liquidity pool being credited. The type of this address will be `SC_ADDRESS_EXTENDED_TYPE_LIQUIDITY_POOL`. | ||
* `amount` is the amount moved into the liquidity pool. | ||
|
||
If an asset is a movement from the issuer of the asset, instead emit for the movement: | ||
``` | ||
contract: asset, topics: ["mint", to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
#### Liquidity Pool Withdraw | ||
Emit the following events: | ||
``` | ||
contract: assetA, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
contract: assetB, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
* `from` is the liquidity pool. The type of this address will be `SC_ADDRESS_EXTENDED_TYPE_LIQUIDITY_POOL`. | ||
* `to` is the account being credited. | ||
* `amount` is the amount moved out of the liquidity pool. | ||
|
||
|
||
If an asset is issued by the withdrawer, instead emit for the movement of the issued asset: | ||
``` | ||
contract: asset, topics: ["burn", from:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
#### Manage Buy Offer / Manage Sell Offer / Create Passive Sell Offer | ||
Emit two events per offer traded against. Each pair of events represents both sides of a trade. This does mean zero events can be emitted if the resulting offer is not marketable - | ||
|
||
``` | ||
contract: assetA, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
contract: assetB, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
If an asset is a movement from the issuer of the asset, instead emit for that movement: | ||
``` | ||
contract: asset, topics: ["mint", to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
If an asset is a movement to the issuer of the asset, instead emit for the movement: | ||
``` | ||
contract: asset, topics: ["burn", from:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
#### Clawback / Clawback Claimable Balance | ||
Emit the following event: | ||
|
||
``` | ||
contract: asset, topics: ["clawback", from:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
* `from` is the account or claimable balance being credited. | ||
* `amount` is the amount being moved out of the account and burned. | ||
|
||
|
||
#### Allow Trust / Set Trustline Flags | ||
If either operation is used to revoke authorization from a trustline that deposited into a liquidity pool then claimable balances will be created for the withdrawn assets (See [CAP-0038](cap-0038.md#SetTrustLineFlagsOp-and-AllowTrustOp) for more info). If any claimable balances are created due to this scenario, emit the following event: | ||
|
||
``` | ||
contract: asset, topics: ["transfer", from:Address, to:Address, sep0011_asset:String], data: amount:i128 | ||
``` | ||
|
||
* `from` is the liquidity pool. The type of this address will be `SC_ADDRESS_EXTENDED_TYPE_LIQUIDITY_POOL`. | ||
* `to` is the claimable balance being created. The type of this address will be `SC_ADDRESS_EXTENDED_TYPE_CLAIMABLE_BALANCE`. | ||
* `amount` is the amount moved into the claimable balance. | ||
|
||
## Design Rationale | ||
|
||
### Remove the admin from the SAC `mint` and `clawback` events | ||
|
||
The admin isn't relevant information when a mint or `clawback` occurs, and it hinders compatibility with SEP-41 for when these two events are added to it because the admin is an implementation detail. For a custom token, an admin doesn't need to be a single `Address`, or an admin may not required at all to emit either event. | ||
|
||
### No change to TransactionMeta XDR | ||
|
||
By using the existing `events<>` vector in `SorobanTransactionMeta`. We can avoid making any xdr changes. This does have some tradeoffs, mainly that | ||
1. All events for a given transaction will be emitted in a single vector, making it impossible to distinguish which operation emitted a specific event. The alternative would be to move Soroban meta from the transaction layer into the operation layer of transaction meta, but that would be a breaking change. | ||
2. Soroban events are hashed into the return value of `InvokeHostFunctionResult` which allows you to cryptographically verify the `events<>` vector. Using the same `events<>` vector may cause some confusion because the Classic events won't be hashed into anything. | ||
|
||
### Emit the semantically correct event instead of no longer allowing the issuer to transfer due to missing a trustline | ||
|
||
The Stellar Asset Contract special cases the issuer logic because issuers can't hold a trustline for their own assets. This matches the logic in Classic. The special case was unecessary however because the Stellar Asset Contract provides the `mint` and `burn` functions. This CAP could instead just remove the special case and allow `transfers` involving the issuer to fail due to a missing trustline, | ||
but this would break any contracts that rely on this behavior (it's not known at this time if contracts like this exist, but we could check if there are any `transfers` on pubnet that involve the issuer). That's why this CAP chooses to instead emit the correct event in this scenario. | ||
|
||
### New XDR SCV_ADDRESS_EXTENDED | ||
|
||
Instead of extending the existing `SCAddress` union, this CAP specifies a `SCAddressExtended` type for liquidity pool and claimable balance IDs. This new type can be used in the topic of an event where the address is not a contract or a stellar account. This new type is separate from `SCAddress` because they only exist to be used as IDs. | ||
sisuresh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Protocol Upgrade Transition | ||
On the protocol upgrade, the SAC will start emitting the `mint` and `clawback` events without the `admin` topic. Also, the `transfer` event will not be emitted for `transfers` involving the issuer. Instead, the appropriate `mint`/`burn` will be emitted. | ||
|
||
The unified events will not be part of the protocol, so they can be enabled with a configuration flag at anytime. | ||
|
||
### Backwards Incompatibilities | ||
|
||
|
||
### Resource Utilization | ||
The additional events will use more resources if a node chooses to emit them. | ||
|
||
|
||
## Security Concerns | ||
|
||
|
||
## Future work | ||
|
||
|
||
## Test Cases | ||
|
||
|
||
## Implementation |
Oops, something went wrong.
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.
Classic will need to emit other events also:
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 wait, I didn't add the
set_authorized
event to this CAP because it isn't required to track balances. What's the reasoning to include it?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, yes, fair enough.
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.
@sisuresh I had a moment above and forgot why we had included set_authorized. I understand it's a bit of an oddity given it's the one operation and event not involving the movement of value.
The why behind including it was that while this looks like we're just solving an issue with tracking balances, we're actually solving a slightly larger story about the things that classic and soroban both do, but then emit data about those happenings in different ways. Set authorized is an event that soroban emits, for something that happens both on soroban and classic, and the event when emitted only for soroban occurrences is a footgun and quite useless for downstream systems if it isn't also emitted for the classic occurences, much the same to transfers.