rUSDC Token Standard
The rUSDC Token Standard (ERC-7770) makes any ERC20 token borrowable by default.
Motivation
The Cambrian explosion of new L1s and L2s has given rise to bridged assets, which are synthetic by nature.
rUSDC allows an ecosystem (Layer 1s, Layer 2s, and other networks of economic activity) to increase stablecoin supply by allowing users to mint the asset not only by wrapping and bridging into the ecosystem, but also by borrowing it within the ecosystem (typically against bluechip collateral).
Example 2.1
Consider a rUSDC token, namely, rUSDC.x, that represents a synthetic USDC.
Such token will allow users to mint 1 rUSDC.x upon a deposit/ bridging of 1 USDC, or by providing collateral that is worth more than 1 USDC.
Quick redemption of rUSDC.x to USDC is available as long as there is still a USDC balance in the instance Local PSM.
In a scenario where there is no USDC in the instance Local PSM, the price of rUSDC may temporarily fluctuate until borrowers repay their debt or more USDC is added to the Local PSM from the Global PSM.
rUSDC instances may delegate minting capabilities for multiple risk curators and lending markets. Hence, a uniform standard for rUSDC minting is needed.
Specification
The proposed standard has the following requirements:
MUST be ERC-20 compatible.
Interface
interface IERC7770 is IERC20 {
// events
event MintrUSDC(address indexed minter, address to, uint256 amount);
event BurnrUSDC(address indexed burner, address from, uint256 amount);
event SetSegregatedAccount(address account, bool segregated);
// functions
// setters
function rUSDCMint(address _to, uint256 _amount) external;
function rUSDCBurn(address _from, uint256 _amount) external;
// getters
function totalBorrowedSupply() external view returns (uint256);
function requiredReserveRatio() external view returns (uint256);
function segregatedAccount(address _account) external view returns (bool);
function totalSegregatedSupply() external view returns (uint256);
}
Reserve ratio
The reserve ratio reflects the ratio between the token that is available as cash from the instance PSM, i.e., available for immediate redemption, and the total supply of the token.
Segregated accounts (see below) MUST be subtracted from the cash balance.
A lower reserve ratio gives rise to higher capital efficiency; however, it increases the likelihood of a depeg or a run on the bank, where token holders cannot immediately redeem their synthetic tokens.
Formally, the reserve ratio is denoted by
$$\frac{totalSupply() - totalBorrowedSupply() - \sum_{a \in \text{Segregated Accounts}} \text{balanceOf}(a)}{totalSupply()}$$.
Mint and burn functionality
The rUSDCMint
and rUSDCBurn
functions SHOULD be called by permissioned addresses, e.g., risk curators or lending markets. These entities SHOULD mint new tokens only to addresses that have locked collateral in a dedicated contract.
rUSDCMint
MUST revert if the reserve ratio, multiplied by e18
exceeds requiredReserveRatio()
.
A successful call to rUSDCMint(_to, _amount)
MUST increase the value of totalSupply()
, totalBorrowedSupply()
, and the token balance of address _to
, by _amount
units.
A call to rUSDCMint
MUST emit a MintrUSDC
event.
A call to rUSDCMint
MUST revert if after the mint, the reserve ratio multiplied by 1e18
exceeds the value of requiredReserveRatio()
.
Similarly, a successful call to rUSDCBurn(_from, _amount)
MUST decrease the value of totalSupply()
,totalBorrowedSupply()
, and the token balance of address _from
by _amount
units.
A call to rUSDCBurn
MUST emit a BurnrUSDC
event.
Account balance
The rUSDCMint
SHOULD be used in conjunction with a lending operation, where the rUSDC is borrowed. The lending operation SHOULD come with an interest rate, and some of the interest rate proceedings SHOULD be distributed to Global PSM suppliers.
This standard does not dictate how distribution should occur.
Rationale
The proposed standard aims to standardize how multiple lending markets and risk providers can interact with rUSDC. The actual lending operation should be done carefully by trusted entities to make sure the parties who have rUSDC minting credentials are reliable.
Understanding how much additional supply is available for borrowing and at what interest rate is the core of this token standard. The additional borrowable supply is deduced from the required reserve ratio and the total borrowable amount. The interest rate SHOULD monotonically increase with the current reserve ratio.
The standard does not dictate how the accrued interest rate is distributed.
While rUSDC is most useful when it is backed by a known asset, e.g., USDC, it can also be used in isolation. In such a case, a token will have a fixed initial supply, however additional supply can be borrowed. The supply may temporarily increase, but the net holdings (totalSupply() - totalBorrowedSupply()
) remain unchanged.
Backwards Compatibility
rUSDC tokens are backwards compatible with ERC-20.
Reference Implementation
// The code below is provided only for illustration, DO NOT use it in production
contract rUSDC is ERC20, Ownable {
event MintrUSDC(address indexed minter, address to, uint256 amount);
event BurnrUSDC(address indexed burner, address from, uint256 amount);
event SetSegregatedAccount(address account, bool segregated);
/// @notice token supply in these accounts is not counted towards the reserve, and
/// therefore, additional token supply cannot be minted against them.
mapping(address => bool) public segregatedAccount;
/// @notice ratio between the token that is available as cash (immediate redemption)
/// and the total supply of the token.
uint256 public requiredReserveRatio;
uint256 public totalBorrowedSupply;
constructor(
string memory _name,
string memory _symbol
) ERC20(_name, _symbol) Ownable(msg.sender) {}
function rUSDCMint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
totalBorrowedSupply += amount;
emit rUSDCReserve(msg.sender, to, amount);
uint256 reserveRatio = (totalSupply() - totalBorrowedSupply - segregatedSupply) * 1e18 / totalSupply();
require(reserveRatio >= requiredReserveRatio, "reserveRatio");
}
function rUSDCBurn(address from, uint256 amount) external onlyOwner {
_burn(from, amount);
totalBorrowedSupply -= amount;
emit BurnrUSDC(msg.sender, from, amount);
}
// ------------------------------------------------------------------------------
// Code below is not part of the proposed standard
// ------------------------------------------------------------------------------
uint256 internal segregatedSupply; // supply of segregated tokens
function _update(address from, address to, uint256 value) internal override {
// keep the reserve up to date on transfers
if (!segregatedAccount[from] && segregatedAccount[to]) {
segregatedSupply += value;
}
if (segregatedAccount[from] && !segregatedAccount[to]) {
segregatedSupply -= value;
}
ERC20._update(from, to, value);
}
function mint(address account, uint256 value) external onlyOwner {
_mint(account, value);
}
function burn(address account, uint256 value) external onlyOwner {
_burn(account, value);
}
function setSegregatedAccount(address account, bool segregated) external onlyOwner {
if (segregated) {
require(!segregatedAccount[account], "segregated");
segregatedSupply += balanceOf(account);
} else {
require(segregatedAccount[account], "!segregated");
segregatedSupply -= balanceOf(account);
}
segregatedAccount[account] = segregated;
emit SetSegregatedAccount(account, segregated);
}
function setRequiredReserveRatio(uint256 value) external onlyOwner {
requiredReserveRatio = value;
}
}
Last updated