ERC-3475 Abstract Storage Bonds

Final · Tokens · created 2021-04-05

A standard that tokenizes obligations with redemption conditions, like bonds, using a two-level ID of class and nonce, and lets redemption conditions and metadata be stored directly onchain in a free-form structure.

Why it is needed

Existing token standards cannot represent bonds that carry multiple redemption conditions. For example, the LP token (Liquidity Provider token) received for providing liquidity to a decentralized exchange is a plain ERC-20 token, so it cannot hold complex reward and redemption rules onchain. Creating bonds with different terms for each issuance time requires deploying a separate contract each time, and external services such as wallets and exchanges cannot read the balance and redemption conditions of each bond in a consistent way. There was also no standardized way to bundle several bonds and move them in one step, or to split them up and trade them on a secondary market. This standard solves these problems by providing multiple token IDs, free-form onchain metadata, and batch processing in a single interface.

What to implement

In depth

The two-level ID structure of class and nonce

Within a single contract, a bond is identified by two values, classId and nonceId. The class represents the type of bond, and the nonce represents differences in issuance time or issuance terms within the same class. Nonces in the same class share the class metadata, but nonces are not fungible with each other, and each nonce can have its own metadata, supply, and redemption conditions. When a bond is transferred, all of its metadata passes to the new owner.

The Transaction struct and batch processing

Every state-changing function takes an array of Transaction structs. The struct has three fields, uint256 classId, uint256 nonceId, and uint256 _amount, which denote the bond class, the nonce that distinguishes issuance terms, and the amount. This allows several bonds of different classes and nonces to be issued, transferred, redeemed, burned, or approved in one transaction. Once execution finishes, the new owner has the right to claim the face value of each bond, and this bundled processing is useful for uses such as secondary market trading.

Permission rules for issuing, redeeming, and burning

issue() must be performed by a single entity (MUST), and the example given is a role-based ownership contract linked to a collateral liquidity pool. redeem() must restrict the call to the bond issuer contract and must run only after the maturity conditions (end time, total active liquidity, and so on) are met (MUST), and only the bank or a secondary market maker contract should call it (SHOULD). burn() must be called only by the controller contract (MUST) and likewise runs only after the maturity conditions are met. Here, the bank is the entity that issues, redeems, and burns bonds after receiving liquidity, and usually means a single entity with administrator authority over the pool.

Two approval schemes

approve() gives _spender the right to use a specific (classId, nonceId) bond up to the specified amount, and calling it again overwrites the existing allowance. setApprovalFor() turns on or off an operator's permission to manage all of the caller's bond classes. transferFrom() requires an isApprovedFor(_from, _to, classId) approval, and transferAllowanceFrom() requires allowance(_from, msg.sender, classId, nonceId) for each Transaction entry. Both approval functions must be callable only by the bank or the account owner.

Supply accounting and maturity progress lookup

Supply is counted in four ways. totalSupply() is the total supply including burned and redeemed amounts, activeSupply() is the active supply that has not been redeemed, redeemedSupply() is the redeemed amount, and burnedSupply() is the burned amount. balanceOf() returns the balance of a specific address, including redeemed bonds. getProgress() returns two values, progressAchieved and progressRemaining, to show the progress toward maturity. The basis may differ by implementation, such as a liquidity ratio or time, and the redemption conditions should be defined in an internal function (SHOULD).

Abstract storage of metadata onchain

Metadata defines its schema with a Metadata struct holding three strings, title, _type, and description, and the actual values are held in a Values struct with the fields stringValue, uintValue, addressValue, boolValue, and bytesValue. The schema is read with classMetadata() and nonceMetadata(), and the corresponding values are read by passing an index key to classValues() and nonceValues(). For a symbol entry, for example, the class ID is used as the key to find that class's symbol string in the values array. The standard's name (abstract storage) comes from the fact that it can hold every specification without external storage while not forcing a fixed storage pattern.

Events and compatibility with existing standards

The Issue, Redeem, Transfer, burn, and ApprovalFor events must be emitted for each corresponding transaction so that the bond lifecycle (issuance, redemption, transfer) can be indexed. Operations with an amount of 0 must not be included in events, and for transfers where _from is 0x0, the Issue event must be used instead of Transfer. This standard's interface is compatible with the EIP-20, EIP-721, and EIP-1155 interfaces, and an existing EIP-20 token can also issue bonds by delegating its minting authority to a bank contract that embeds this standard. Collateral can be a fungible token such as EIP-20, a non-fungible token such as EIP-721 and EIP-1155, or another bond built with this standard. A separate bank contract is recommended for lookups and future upgrades.

AMM liquidity pool consolidation

Early automated market makers (AMMs) managed liquidity with a separate contract and an EIP-20 LP token for each token pair, which reduced the liquidity gathered in any one pool and caused unnecessary gas costs and slippage. With this standard, the liquidity data structure that maps to class and nonce makes it possible to build one large liquidity pool containing every pair, and a bond holder's liquidity can be expressed as the ratio of the token pair within the pool. The effect is that EIP-20 LP tokens, which used to be deployed separately for each pool factory, are aggregated into a single bond and consolidated into one pool.

Things to watch out for

Original ERC · ← ERCs