Usdn
Inherits: IUsdn, ERC20Permit, ERC20Burnable, AccessControl
The USDN token supports the USDN Protocol. It is minted when assets are deposited into the USDN Protocol vault and burned when withdrawn. The total supply and individual balances are periodically increased by modifying a global divisor, ensuring the token's value doesn't grow too far past 1 USD.
This contract extends OpenZeppelin's ERC-20 implementation, adapted to support growable balances. Unlike a traditional ERC-20, balances are stored as shares, which are converted into token amounts using the global divisor. This design allows for supply growth without updating individual balances. Any divisor modification can only make balances and total supply increase.
State Variables
MINTER_ROLE
Gets the minter role signature.
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
REBASER_ROLE
Gets the rebaser role signature.
bytes32 public constant REBASER_ROLE = keccak256("REBASER_ROLE");
MAX_DIVISOR
Gets the maximum value of the divisor, which is also the initial value.
uint256 public constant MAX_DIVISOR = 1e18;
MIN_DIVISOR
Gets the minimum acceptable value of the divisor.
The minimum divisor that can be set. This corresponds to a growth of 1B times. Technically, 1e5 would still work without precision errors.
uint256 public constant MIN_DIVISOR = 1e9;
NAME
The name of the USDN token.
string internal constant NAME = "Ultimate Synthetic Delta Neutral";
SYMBOL
The symbol of the USDN token.
string internal constant SYMBOL = "USDN";
_shares
Mapping of the number of shares held by each account.
mapping(address account => uint256) internal _shares;
_totalShares
The sum of all the shares.
uint256 internal _totalShares;
_divisor
The divisor used for conversion between shares and tokens.
uint256 internal _divisor = MAX_DIVISOR;
_rebaseHandler
Address of a contract to be called upon a rebase event.
IRebaseCallback internal _rebaseHandler;
Functions
constructor
constructor(address minter, address rebaser) ERC20(NAME, SYMBOL) ERC20Permit(NAME);
Parameters
Name | Type | Description |
---|---|---|
minter | address | Address to be granted the minter role (pass zero address to skip). |
rebaser | address | Address to be granted the rebaser role (pass zero address to skip). |
totalSupply
Returns the total supply of tokens in existence.
This value is derived from the total number of shares and the current divisor. It does not represent the exact sum of all token balances due to the divisor mechanism. For an accurate representation, consider using the total number of shares via totalShares.
function totalSupply() public view override(ERC20, IERC20) returns (uint256 totalSupply_);
Returns
Name | Type | Description |
---|---|---|
totalSupply_ | uint256 | The total supply of tokens as computed from shares. |
balanceOf
Returns the token balance of a given account.
The returned value is based on the current divisor and may not represent an accurate balance in terms of shares. For precise calculations, use the number of shares via sharesOf.
function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256 balance_);
Parameters
Name | Type | Description |
---|---|---|
account | address | The address of the account to query. |
Returns
Name | Type | Description |
---|---|---|
balance_ | uint256 | The token balance of the account as computed from shares. |
nonces
Returns the current nonce for owner
. This value must be
included whenever a signature is generated for {permit}.
Every successful call to {permit} increases owner
's nonce by one. This
prevents a signature from being used multiple times.
function nonces(address owner) public view override(IERC20Permit, ERC20Permit) returns (uint256);
burn
Destroys a value
amount of tokens from the caller, reducing the total supply.
function burn(uint256 value) public override(ERC20Burnable, IUsdn);
Parameters
Name | Type | Description |
---|---|---|
value | uint256 | Amount of tokens to burn, is internally converted to the proper shares amounts. |
burnFrom
Destroys a value
amount of tokens from account
, deducting from the caller's allowance.
function burnFrom(address account, uint256 value) public override(ERC20Burnable, IUsdn);
Parameters
Name | Type | Description |
---|---|---|
account | address | Account to burn tokens from. |
value | uint256 | Amount of tokens to burn, is internally converted to the proper shares amounts. |
sharesOf
Returns the number of shares owned by account
.
function sharesOf(address account) public view returns (uint256 shares_);
Parameters
Name | Type | Description |
---|---|---|
account | address | The account to query. |
Returns
Name | Type | Description |
---|---|---|
shares_ | uint256 | The number of shares. |
totalShares
Returns the total number of shares in existence.
function totalShares() external view returns (uint256 shares_);
Returns
Name | Type | Description |
---|---|---|
shares_ | uint256 | The number of shares. |
convertToTokens
Converts a number of shares to the corresponding amount of tokens.
The conversion never overflows as we are performing a division. The conversion rounds to the nearest amount of tokens that minimizes the error when converting back to shares.
function convertToTokens(uint256 amountShares) external view returns (uint256 tokens_);
Parameters
Name | Type | Description |
---|---|---|
amountShares | uint256 | The amount of shares to convert to tokens. |
Returns
Name | Type | Description |
---|---|---|
tokens_ | uint256 | The corresponding amount of tokens. |
convertToTokensRoundUp
Converts a number of shares to the corresponding amount of tokens, rounding up.
Use this function to determine the amount of a token approval, as we always round up when deducting from a token transfer allowance.
function convertToTokensRoundUp(uint256 amountShares) external view returns (uint256 tokens_);
Parameters
Name | Type | Description |
---|---|---|
amountShares | uint256 | The amount of shares to convert to tokens. |
Returns
Name | Type | Description |
---|---|---|
tokens_ | uint256 | The corresponding amount of tokens, rounded up. |
convertToShares
Converts a number of tokens to the corresponding amount of shares.
The conversion reverts with UsdnMaxTokensExceeded
if the corresponding amount of shares overflows.
function convertToShares(uint256 amountTokens) public view returns (uint256 shares_);
Parameters
Name | Type | Description |
---|---|---|
amountTokens | uint256 | The amount of tokens to convert to shares. |
Returns
Name | Type | Description |
---|---|---|
shares_ | uint256 | The corresponding amount of shares. |
divisor
Gets the current value of the divisor that converts between tokens and shares.
function divisor() external view returns (uint256 divisor_);
Returns
Name | Type | Description |
---|---|---|
divisor_ | uint256 | The current divisor. |
rebaseHandler
Gets the rebase handler address, which is called whenever a rebase happens.
function rebaseHandler() external view returns (IRebaseCallback rebaseHandler_);
Returns
Name | Type | Description |
---|---|---|
rebaseHandler_ | IRebaseCallback | The rebase handler address. |
maxTokens
Returns the current maximum tokens supply, given the current divisor.
This function is used to check if a conversion operation would overflow.
function maxTokens() public view returns (uint256 maxTokens_);
Returns
Name | Type | Description |
---|---|---|
maxTokens_ | uint256 | The maximum number of tokens that can exist. |
transferShares
Transfers a given amount of shares from the msg.sender
to to
.
function transferShares(address to, uint256 value) external returns (bool success_);
Parameters
Name | Type | Description |
---|---|---|
to | address | Recipient of the shares. |
value | uint256 | Number of shares to transfer. |
Returns
Name | Type | Description |
---|---|---|
success_ | bool | Indicates whether the transfer was successfully executed. |
transferSharesFrom
Transfers a given amount of shares from the from
to to
.
There should be sufficient allowance for the spender. Be mindful of the rebase logic. The allowance is in
tokens. So, after a rebase, the same amount of shares will be worth a higher amount of tokens. In that case,
the allowance of the initial approval will not be enough to transfer the new amount of tokens. This can
also happen when your transaction is in the mempool and the rebase happens before your transaction. Also note
that the amount of tokens deduced from the allowance is rounded up, so the convertToTokensRoundUp
function
should be used when converting shares into an allowance value.
function transferSharesFrom(address from, address to, uint256 value) external returns (bool success_);
Parameters
Name | Type | Description |
---|---|---|
from | address | The owner of the shares. |
to | address | Recipient of the shares. |
value | uint256 | Number of shares to transfer. |
Returns
Name | Type | Description |
---|---|---|
success_ | bool | Indicates whether the transfer was successfully executed. |
burnShares
Destroys a value
amount of shares from the caller, reducing the total supply.
function burnShares(uint256 value) external;
Parameters
Name | Type | Description |
---|---|---|
value | uint256 | Amount of shares to burn. |
burnSharesFrom
Destroys a value
amount of shares from account
, deducting from the caller's allowance.
There should be sufficient allowance for the spender. Be mindful of the rebase logic. The allowance is in
tokens. So, after a rebase, the same amount of shares will be worth a higher amount of tokens. In that case,
the allowance of the initial approval will not be enough to transfer the new amount of tokens. This can
also happen when your transaction is in the mempool and the rebase happens before your transaction. Also note
that the amount of tokens deduced from the allowance is rounded up, so the convertToTokensRoundUp
function
should be used when converting shares into an allowance value.
function burnSharesFrom(address account, uint256 value) public;
Parameters
Name | Type | Description |
---|---|---|
account | address | Account to burn shares from. |
value | uint256 | Amount of shares to burn. |
mint
Mints new shares, providing a token value.
Caller must have the MINTER_ROLE.
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE);
Parameters
Name | Type | Description |
---|---|---|
to | address | Account to receive the new shares. |
amount | uint256 | Amount of tokens to mint, is internally converted to the proper shares amounts. |
mintShares
Mints new shares, providing a share value.
Caller must have the MINTER_ROLE.
function mintShares(address to, uint256 amount) external onlyRole(MINTER_ROLE) returns (uint256 mintedTokens_);
Parameters
Name | Type | Description |
---|---|---|
to | address | Account to receive the new shares. |
amount | uint256 | Amount of shares to mint. |
Returns
Name | Type | Description |
---|---|---|
mintedTokens_ | uint256 | Amount of tokens that were minted (informational). |
rebase
Decreases the global divisor, which effectively grows all balances and the total supply.
If the provided divisor is larger than or equal to the current divisor value, no rebase will happen
If the new divisor is smaller than MIN_DIVISOR
, the value will be clamped to MIN_DIVISOR
.
Caller must have the REBASER_ROLE
.
function rebase(uint256 newDivisor)
external
onlyRole(REBASER_ROLE)
returns (bool rebased_, uint256 oldDivisor_, bytes memory callbackResult_);
Parameters
Name | Type | Description |
---|---|---|
newDivisor | uint256 | The new divisor, should be strictly smaller than the current one and greater or equal to MIN_DIVISOR . |
Returns
Name | Type | Description |
---|---|---|
rebased_ | bool | Whether a rebase happened. |
oldDivisor_ | uint256 | The previous value of the divisor. |
callbackResult_ | bytes | The result of the callback, if a rebase happened and a callback handler is defined. |
setRebaseHandler
Sets the rebase handler address.
Emits a RebaseHandlerUpdated
event.
If set to the zero address, no handler will be called after a rebase.
Caller must have the DEFAULT_ADMIN_ROLE
.
function setRebaseHandler(IRebaseCallback newHandler) external onlyRole(DEFAULT_ADMIN_ROLE);
Parameters
Name | Type | Description |
---|---|---|
newHandler | IRebaseCallback | The new handler address. |
_convertToTokens
Converts an amount of shares into the corresponding amount of tokens, rounding the division according to
rounding
.
If rounding to the nearest integer and the result is exactly at the halfway point, we round up.
function _convertToTokens(uint256 amountShares, Rounding rounding, uint256 d) internal pure returns (uint256 tokens_);
Parameters
Name | Type | Description |
---|---|---|
amountShares | uint256 | The amount of shares to convert to tokens. |
rounding | Rounding | The rounding direction: down, closest, or up. |
d | uint256 | The current divisor value used for the conversion. |
Returns
Name | Type | Description |
---|---|---|
tokens_ | uint256 | The calculated equivalent amount of tokens. |
_transferShares
Transfers a given amount of shares.
Reverts if the from
or to
address is the zero address.
function _transferShares(address from, address to, uint256 value, uint256 tokenValue) internal;
Parameters
Name | Type | Description |
---|---|---|
from | address | The address from which shares are transferred. |
to | address | The address to which shares are transferred. |
value | uint256 | The amount of shares to transfer. |
tokenValue | uint256 | The converted token value, used for the {IERC20.Transfer} event. |
_burnShares
Burns a given amount of shares from an account.
Reverts if the account
address is the zero address.
function _burnShares(address account, uint256 value, uint256 tokenValue) internal;
Parameters
Name | Type | Description |
---|---|---|
account | address | The account from which shares are burned. |
value | uint256 | The amount of shares to burn. |
tokenValue | uint256 | The converted token value, used for the {IERC20.Transfer} event. |
_updateShares
Updates the shares of accounts during transferShares, mintShares, or burnShares.
Emits a {IERC20.Transfer} event with the token equivalent of the operation.
If from
is the zero address, the operation is a mint.
If to
is the zero address, the operation is a burn.
function _updateShares(address from, address to, uint256 value, uint256 tokenValue) internal;
Parameters
Name | Type | Description |
---|---|---|
from | address | The source address. |
to | address | The destination address. |
value | uint256 | The number of shares to transfer, mint, or burn. |
tokenValue | uint256 | The converted token value, used for the {IERC20.Transfer} event. |
_update
Updates the shares of accounts during transfers, mints, or burns.
Emits a {IERC20.Transfer} event.
If from
is the zero address, the operation is a mint.
If to
is the zero address, the operation is a burn.
function _update(address from, address to, uint256 value) internal override;
Parameters
Name | Type | Description |
---|---|---|
from | address | The source address. |
to | address | The destination address. |
value | uint256 | The number of tokens to transfer, mint, or burn. |
Enums
Rounding
Enum representing the rounding options when converting from shares to tokens.
enum Rounding {
Down,
Closest,
Up
}