Skip to content

Latest commit

 

History

History
414 lines (338 loc) · 13.1 KB

cap-0049.md

File metadata and controls

414 lines (338 loc) · 13.1 KB
CAP: 0049
Title: Smart Contract Asset Interoperability with Wrapper
Working Group:
    Owner: Jonathan Jove <@jonjove>
    Authors:
    Consulted: Nicolas Barry <@monsieurnicolas>, Leigh McCulloch <@leighmcculloch>, Tomer Weller <@tomerweller>
Status: Rejected
Created: 2022-05-05
Discussion: TBD
Protocol version: TBD

Simple Summary

Allow smart contracts to interoperate with Stellar assets.

Motivation

There is an existing ecosystem of assets on the Stellar Network. Smart contracts on the Stellar Network will be significantly less useful if it is not possible to interact with those assets. Therefore, we must build an interoperability layer.

Requirements

Performance

Tokens are the basic unit of blockchain applications, and as such they should be very efficient to use.

Trust Transitivity

It is not guaranteed that every anchor will take steps to maximize interoperability between their existing asset and smart contracts. Other organizations may try to fill that gap, but they will not necessarily have the same level of trust. For example, consider an asset X issued by account A. If A deploys smart contracts to maximize interoperability between their existing asset and other smart contracts, then these smart contracts don't require trusting an additional organization. Native support to interoperate between an existing asset and smart contracts would avoid this issue by using trust in the Stellar protocol.

Restricted Privileges

ERC-20 tokens use an allowance model to limit how much of a user's balance can be spent by a specific smart contract. This makes it relatively safe to interact with a smart contract, because it cannot steal arbitrarily large amounts of money. Existing Stellar assets have no such functionality. As a consequence, it is extremely dangerous to let a smart contract execute a payment with any account (other than the contract account) as source.

Goals Alignment

This CAP is aligned with the following Stellar Network Goals:

  • The Stellar Network should make it easy for developers of Stellar projects to create highly usable products
  • The Stellar Network should enable cross-border payments, i.e. payments via exchange of assets, throughout the globe, enabling users to make payments between assets in a manner that is fast, cheap, and highly usable.

Abstract

This proposal provides an ERC-20 compliant interface for Stellar assets. This is achieved by introducing two new types of ledger entry, WrappedBalanceEntry and WrappedAllowanceEntry, which are used to record ERC-20 state. Atop this, a ContractID for cross-contract invocations is introduced which can refer to an actual smart contract or to native contracts. In this case, we provide a new type of native contract with type ASSET_ADAPTOR which implements the ERC-20 and compliance interfaces in terms of WrappedBalanceEntry and WrappedAllowanceEntry, and which implements the wrapper interface by transferring funds from accounts and trustlines to wrapped balance entries.

Specification

XDR Changes

diff --git a/src/xdr/Stellar-ledger-entries.x b/src/xdr/Stellar-ledger-entries.x
index 377309f9..f4847eb5 100644
--- a/src/xdr/Stellar-ledger-entries.x
+++ b/src/xdr/Stellar-ledger-entries.x
@@ -100,7 +100,9 @@ enum LedgerEntryType
     CLAIMABLE_BALANCE = 4,
     LIQUIDITY_POOL = 5,
     CONTRACT_CODE = 6,
-    CONTRACT_DATA = 7
+    CONTRACT_DATA = 7,
+    WRAPPED_BALANCE = 8,
+    WRAPPED_ALLOWANCE = 9
 };
 
 struct Signer
@@ -499,6 +501,36 @@ struct ContractDataEntry {
     SCVal *val;
 };
 
+struct WrappedBalanceEntry
+{
+    union switch (int v)
+    {
+    case 0:
+        void;
+    }
+    ext;
+
+    AccountID owner;
+    Asset asset;
+    uint256 amount;
+    bool authorized;
+};
+
+struct WrappedAllowanceEntry
+{
+    union switch (int v)
+    {
+    case 0:
+        void;
+    }
+    ext;
+
+    AccountID transferFrom;
+    AccountID spender;
+    Asset asset;
+    uint256 amount;
+};
+
 struct LedgerEntryExtensionV1
 {
     SponsorshipDescriptor sponsoringID;
@@ -533,6 +565,10 @@ struct LedgerEntry
         ContractCodeEntry contractCode;
     case CONTRACT_DATA:
         ContractDataEntry contractData;
+    case WRAPPED_BALANCE:
+        WrappedBalanceEntry wrappedBalance;
+    case WRAPPED_ALLOWANCE:
+        WrappedAllowanceEntry wrappedAllowance;
     }
     data;
 
@@ -600,6 +636,21 @@ case CONTRACT_DATA:
         int64 contractID;
         SCVal *key;
     } contractData;
+
+case WRAPPED_BALANCE:
+    struct
+    {
+        AccountID owner;
+        Asset asset;
+    } wrappedBalance;
+
+case WRAPPED_ALLOWANCE:
+    struct
+    {
+        AccountID transferFrom;
+        AccountID spender;
+        Asset asset;
+    } wrappedAllowance;
 };
 
 // list of all envelope types used in the application
diff --git a/src/xdr/Stellar-transaction.x b/src/xdr/Stellar-transaction.x
index 3d9ee3ea..fa6297a7 100644
--- a/src/xdr/Stellar-transaction.x
+++ b/src/xdr/Stellar-transaction.x
@@ -14,6 +14,20 @@ case LIQUIDITY_POOL_CONSTANT_PRODUCT:
     LiquidityPoolConstantProductParameters constantProduct;
 };
 
+enum ContractType
+{
+    SMART_CONTRACT = 0,
+    ASSET_ADAPTOR = 1
+};
+
+union ContractID switch (ContractType type)
+{
+case SMART_CONTRACT:
+    int64 contractID;
+case ASSET_ADAPTOR:
+    Asset asset;
+};
+
 struct DecoratedSignature
 {
     SignatureHint hint;  // last 4 bytes of the public key, used as a hint

Semantics

Asset adaptors implement the ERC-20 interface, authorization/clawback, and wrap/unwrap. They allow smart contracts that can interoperate with ERC-20 tokens to interoperate with Stellar assets.

Specifically, asset adaptors implement the following

/******************************************************************************\
*
* ERC-20 Interface
*
\******************************************************************************/
// name is intended to comply with SEP-11
// Returns "NATIVE" for ASSET_TYPE_NATIVE
// Returns format!("{}:{}", assetCode, issuer) for ASSET_TYPE_CREDIT_ALPHANUM4
// Returns format!("{}:{}", assetCode, issuer) for ASSET_TYPE_CREDIT_ALPHANUM12
fn name() -> String;

// symbol is intended to comply with SEP-11
// Returns "NATIVE" for ASSET_TYPE_NATIVE
// Returns assetCode for ASSET_TYPE_CREDIT_ALPHANUM4
// Returns assetCode for ASSET_TYPE_CREDIT_ALPHANUM12
fn symbol() -> String;

// Returns 7
fn decimals() -> u8;

// Returns total supply
fn totalSupply() -> uint256;

// Returns balance of owner
fn balanceOf(owner: AccountID) -> uint256;

// Returns false if !isAuthorized(msg.sender)
// Returns false if !isAuthorized(to)
// Returns false if balanceOf(msg.sender) < value
// Returns false if balanceOf(to) > UINT256_MAX - value
// Debits value from msg.sender
// Credits value to to
// Returns true
fn transfer(to: AccountID, value: uint256) -> bool;

// Returns false if !isAuthorized(msg.sender)
// Returns false if !isAuthorized(from)
// Returns false if !isAuthorized(to)
// Returns false if balanceOf(from) < value
// Returns false if balanceOf(to) > UINT256_MAX - value
// Returns false if allowance(msg.sender) < value
// Debits value from Approval(from, msg.sender, asset)
// Debits value from from
// Credits value to to
// Returns true
fn transferFrom(from: AccountID, to: AccountID, value: uint256) -> bool;

// Sets ApprovalEntry {
//     transferFrom: msg.sender,
//     spender: spender,
//     asset: asset,
//     amount: value,
// }
// Returns true on success
fn approve(spender: AccountID, value: uint256) -> bool;

// Returns Allowance(owner, spender, asset).amount
fn allowance(owner: AccountID, spender: AccountID) -> uint256;

/******************************************************************************\
*
* Compliance Interface
*
\******************************************************************************/
// Returns false if msg.sender != asset.issuer
// Sets authorized for account
// Returns true
fn authorize(account: AccountID) -> bool;

// Returns false if msg.sender != asset.issuer
// Returns false if !(Account(msg.sender).flags & AUTH_REVOCABLE)
// Clears authorized for account
// Returns true
fn deauthorize(account: AccountID) -> bool;

// Returns false if account is not authorized
// Returns true
fn isAuthorized(account: AccountID) -> bool;

// Returns false if msg.sender != asset.issuer
// Returns false if !(Account(msg.sender).flags & AUTH_CLAWBACK_ENABLED_FLAG)
// Returns false if balanceOf(from) < value
// Debits value from from
// Decreases total supply by value
// Returns true
fn clawback(from: AccountID, value: uint256) -> bool;

/******************************************************************************\
*
* Wrapper Interface
*
\******************************************************************************/
// Returns false if !isAuthorized(msg.sender)
// Returns false if balanceOf(msg.sender) > UINT256_MAX - value
// Returns false if TrustLine(msg.sender, asset) doesn't exist
// Returns false if !(TrustLine(msg.sender, asset).flags & AUTHORIZED_FLAG)
// Returns false if TrustLine(msg.sender, asset).availableBalance() < value
// Debits value from TrustLine(msg.sender, asset)
// Credits value to msg.sender
// Increases total supply by value
// Returns true
fn wrap(value: uint256) -> bool;

// Returns false if !isAuthorized(msg.sender)
// Returns false if balanceOf(msg.sender) < value
// Returns false if TrustLine(msg.sender, asset) doesn't exist
// Returns false if !(TrustLine(msg.sender, asset).flags & AUTHORIZED_FLAG)
// Returns false if TrustLine(msg.sender, asset).availableLimit() < value
// Debits value from msg.sender
// Credits value to TrustLine(msg.sender, asset)
// Decreases total supply by value
// Returns true
fn unwrap(value: uint256) -> bool;

Asset adaptors don't have normal contract addresses, because they always exist and have no on-chain representation. Instead, an asset adaptor contract address is simply identified by a ContractID of type ASSET_ADAPTOR that contains the specified asset. We will need a host function like

fn get_asset_adaptor(asset: Asset) -> ContractID;

Design Rationale

Satisfies Requirements: Performance

The entire implementation will be on the host, meaning it can leverage direct access to the processor and data store.

Satisfies Requirements: Trust Transitivity

The asset adaptor is an extension of the issuer's authority to smart contracts, without requiring trust for any other party.

Satisfies Requirements: Restricted Privileges

An account will have to call wrap directly in order to prepare assets from their trustline for use in smart contracts, and these functions can be trusted because they are part of the protocol. Once assets are wrapped, they are subject to the ERC-20 allowance mechanism to protect them.

Wrap/Unwrap Friction Is Acceptable

The standard scheme to use an ERC-20 token with a smart contract is to set an allowance for that smart contract. Setting the allowance for the smart contract is also an opportunity to simultaneously wrap Stellar assets into the adaptor.

The core assumption here is that accounts are not doing a constant mixture of Stellar operations and smart contract interactions. I anticipate that usage is much more likely to go in phases: prepare Stellar asset for utilization in a smart contract, leave it there for some time possibly moving it to other smart contracts, bring Stellar asset back. This usage pattern is reasonable given that most smart contracts take control of the asset anyway, requiring it to be withdrawn before it can be used elsewhere.

Only Two Authorization States

While trustlines have three authoriztion states (unauthorized, authorized to maintain liabilities, and authorized), the asset adaptor only has two. This is because the asset adaptor has no notion of liabilities, so authorized to maintain liabilities is equivalent to unauthorized.

No Holder Clawback Enabled Flag

CAP-0035 introduced the trustline flag TRUSTLINE_CLAWBACK_ENABLED_FLAG to preserve backwards compatibility for existing trustlines that did not expect clawback to be possible. This is not an issue for asset adaptors because clawback already exists, so any wrapped asset will be subject to the behavior specified by the issuer's flags.

Solves Issues from CAP-0048

Issue: Issuer Balance

The issuer balance is whatever amount of asset the issuer has wrapped plus received, minus what they have unwrapped plus sent.

Issue: Not Compatible With High Supply Assets

Rather than using trustlines, the asset adaptor actually stores uint256 values.

Issue: Classic Payments To Contracts

Because the asset adaptor does not use trustlines, contracts cannot receive classic payments.

Issue: Available Balance vs Balance

Asset adaptors have no notion of liabilities, so the balance is the available balance.

Protocol Upgrade Transition

Backwards Incompatibilities

This proposal is completely backwards compatible. It describes a new interface to existing behavior that is accessible from smart contracts, but it does not modify the existing behavior.

Resource Utilization

This proposal adds a new type of ledger entry, which will likely lead to an increase in the total size of the ledger.

Test Cases

None yet.

Implementation

None yet.