Skip to content
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 6 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
| [CAP-0061](cap-0061.md) | Smart Contract Standardized Asset (Stellar Asset Contract) Extension: Memo | Tomer Weller | Draft |
| [CAP-0062](cap-0062.md) | Soroban Live State Prioritization | Garand Tyson | Draft |
| [CAP-0063](cap-0063.md) | Parallelism-friendly Transaction Scheduling | Dmytro Kozhevin | Draft |
| [CAP-0064](cap-0064.md) | Memo Authorization for Soroban | Dmytro Kozhevin | Draft |
| [CAP-0065](cap-0065.md) | Unified Asset Events | Siddharth Suresh | Draft |

### Rejected Proposals
| Number | Title | Author | Status |
Expand Down
326 changes: 326 additions & 0 deletions core/cap-0065.md
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.
Copy link
Member

@leighmcculloch leighmcculloch Jan 13, 2025

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:

Suggested change
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.
Emit `transfer`, `mint`, `burn`, `clawback`, `set_authorized`, `fee` 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.

Copy link
Contributor Author

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?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, yes, fair enough.

Copy link
Member

@leighmcculloch leighmcculloch Jan 15, 2025

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.


## 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
Loading