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 all 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,7 +97,9 @@
| [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) | Reusable Module Cache | Graydon Hoare | Draft |
| [CAP-0067](cap-0065.md) | Unified Asset Events | Siddharth Suresh | Draft |

### Rejected Proposals
| Number | Title | Author | Status |
Expand Down
304 changes: 304 additions & 0 deletions core/cap-0067.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
## Preamble

```
CAP: 0067
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`, `mint`, `burn`, `clawback`, `set_authorized`, and `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.

## 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` of types `ASSET_TYPE_NATIVE`, `ASSET_TYPE_CREDIT_ALPHANUM4`, and `ASSET_TYPE_CREDIT_ALPHANUM12`. in Stellar classic. All of the added events will follow the format of the existing Stellar Asset Contract events, with the exception of a new `fee` event to track fees paid by the source account.
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.

## 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..ee10e20 100644
--- a/Stellar-contract.x
+++ b/Stellar-contract.x
@@ -179,7 +179,9 @@ case CONTRACT_EXECUTABLE_STELLAR_ASSET:
enum SCAddressType
{
SC_ADDRESS_TYPE_ACCOUNT = 0,
- SC_ADDRESS_TYPE_CONTRACT = 1
+ SC_ADDRESS_TYPE_CONTRACT = 1,
+ SC_ADDRESS_TYPE_CLAIMABLE_BALANCE = 2,
+ SC_ADDRESS_TYPE_LIQUIDITY_POOL = 3
};

union SCAddress switch (SCAddressType type)
@@ -188,6 +190,10 @@ case SC_ADDRESS_TYPE_ACCOUNT:
AccountID accountId;
case SC_ADDRESS_TYPE_CONTRACT:
Hash contractId;
+case SC_ADDRESS_TYPE_CLAIMABLE_BALANCE:
+ ClaimableBalanceID claimableBalanceID;
+case SC_ADDRESS_TYPE_LIQUIDITY_POOL:
+ PoolID liquidityPoolID;
};

%struct SCVal;
```

## 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. Note that the Stellar Asset Contract instance is not required to be deployed for the asset. The events will be published using the reserved contract address regardless of deployment status.

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs clarity on who from and to are, here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll address this in a follow up

```

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_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_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_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_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_TYPE_LIQUIDITY_POOL`.
* `to` is the claimable balance being created. The type of this address will be `SC_ADDRESS_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 unnecessary 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 SCAddressType types

This CAP adds two new `SCAddressType` types - `SC_ADDRESS_TYPE_CLAIMABLE_BALANCE` and `SC_ADDRESS_TYPE_LIQUIDITY_POOL`. These types are used in the topic of an event where the address is not a contract or a stellar account.

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