CAP: 0048
Title: Smart Contract Asset Interoperability
Working Group:
Owner: Jonathan Jove <@jonjove>
Authors:
Consulted: Nicolas Barry <@monsieurnicolas>, Leigh McCulloch <@leighmcculloch>, Tomer Weller <@tomerweller>
Status: Rejected
Created: 2022-05-02
Discussion: TBD
Protocol version: TBD
Allow smart contracts to interoperate with Stellar assets.
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.
Tokens are the basic unit of blockchain applications, and as such they should be very efficient to use.
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.
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.
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.
This proposal provides an ERC-20 compliant interface (excluding totalSupply()
)
for Stellar assets. This is achieved by introducing a new type of ledger entry,
AllowanceEntry
, which is used to record ERC-20 style allowances. 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 interface in terms of accounts, trustlines, and allowances.
diff --git a/src/xdr/Stellar-ledger-entries.x b/src/xdr/Stellar-ledger-entries.x
index 377309f9..0337128b 100644
--- a/src/xdr/Stellar-ledger-entries.x
+++ b/src/xdr/Stellar-ledger-entries.x
@@ -100,7 +100,8 @@ enum LedgerEntryType
CLAIMABLE_BALANCE = 4,
LIQUIDITY_POOL = 5,
CONTRACT_CODE = 6,
- CONTRACT_DATA = 7
+ CONTRACT_DATA = 7,
+ ALLOWANCE = 8
};
struct Signer
@@ -499,6 +500,21 @@ struct ContractDataEntry {
SCVal *val;
};
+struct AllowanceEntry
+{
+ union switch (int v)
+ {
+ case 0:
+ void;
+ }
+ ext;
+
+ AccountID transferFrom;
+ AccountID spender;
+ Asset asset;
+ int64 amount;
+};
+
struct LedgerEntryExtensionV1
{
SponsorshipDescriptor sponsoringID;
@@ -533,6 +549,8 @@ struct LedgerEntry
ContractCodeEntry contractCode;
case CONTRACT_DATA:
ContractDataEntry contractData;
+ case ALLOWANCE_ENTRY:
+ AllowanceEntry allowance;
}
data;
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
Asset adaptors implement the ERC-20 interface in terms of accounts, trustlines, and allowances. They allow smart contracts that can interoperate with ERC-20 tokens to interoperate with Stellar assets.
Specifically, asset adaptors implement the following
// 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 Account(owner).balance for ASSET_TYPE_NATIVE
// Returns TrustLine(owner, asset).balance for ASSET_TYPE_CREDIT_ALPHANUM4
// Returns TrustLine(owner, asset).balance for ASSET_TYPE_CREDIT_ALPHANUM12
fn balanceOf(owner: AccountID) -> uint256;
// Executes PaymentOp {
// sourceAccount: msg.sender,
// destination: to,
// asset: asset,
// amount: value,
// }
// Returns PaymentResultCode == PAYMENT_SUCCESS
fn transfer(to: AccountID, value: uint256) -> bool;
// Loads a = allowance(from, msg.sender)
// Returns false if a < amount
// Sets ApprovalEntry {
// transferFrom: msg.sender,
// spender: spender,
// asset: asset,
// amount: a - value,
// }
// Executes PaymentOp {
// sourceAccount: msg.sender,
// destination: to,
// asset: asset,
// amount: value,
// }
// Returns PaymentResultCode == PAYMENT_SUCCESS
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;
Note that totalSupply()
is not implemented.
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;
In this proposal, asset adaptors don't implement totalSupply()
because the
Stellar protocol does not track issuance of non-native assets. While being able
to determine the total supply of an arbitrary asset is useful for front-ends,
it doesn't seem to be essential for smart contracts.
If we determine that this function is necessary, we could implement issuance tracking although this would require a fairly significant amount of effort.
In this proposal, asset adaptors require a trustline to exist in order to complete a payment. This is done to preserve the existing behavior around trustline creation. Furthermore, it doesn't add any new edge cases because in general a trustline might require authorization.
This is a bit inconvenient compared to the usual ERC-20 implementation where you can simply send payments to any address, but should be acceptable nonetheless.
The issuer of a non-native asset doesn't have a balance of the asset in the traditional sense. When the issuer sends a payment, it mints the asset. When the issuer receives a payment, it burns the asset.
What should balance(issuer)
return? Should transfer()
mint when msg.sender
is the issuer?
A Stellar asset with total supply greater than INT64_MAX
can break contracts
that treat Stellar assets like any other ERC-20. For example, consider a
liquidity pool contract. It holds reserves of two assets. If both assets were
high supply Stellar assets and someone deposited enough of each asset that the
reserves were INT64_MAX
of each, then the contract would be frozen with only
withdrawals possible. This occurs because the contract trustlines can't hold
more than INT64_MAX
.
One potential option to alleviate this issue would be to support trustlines with higher limits.
This is a relatively minor user experience issue. An account can use a Stellar payment to send assets to a contract, but this is almost certain not to have the desired effect. The reason is that smart contracts do not execute while applying Stellar operations. Therefore, the contract will not be aware that it received assets or from whom.
Should we take any steps to prevent this?
The Stellar protocol allows part of an account or trustline to be encumbered as
a liability. This means that it is generally not true that a contract can check
an accounts balance to determine whether it can make a payment. Should the
balanceOf(owner)
function return the owner's balance or the owner's available
balance?
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.
This proposal adds a new type of ledger entry, which will likely lead to an increase in the total size of the ledger.
None yet.
None yet.