Skip to content

Commit

Permalink
Merge pull request #3 from mountainprotocol/chore/feedback-1
Browse files Browse the repository at this point in the history
Feedback: 1st iteration
  • Loading branch information
mattiascaricato authored Apr 24, 2023
2 parents f4ced0f + a055888 commit 70457e5
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 194 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,10 @@ artifacts

# Mac
.DS_Store

# VS Code
.vscode/*

# Forge
cache_forge/*
out/*
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# USDM
# Mountain Protocol USD

This smart contract implements a rebasing ERC-20 token with additional functionality such as rebasing, reward multiplier, blacklisting, permit support and upgradeability.
This smart contract implements a custom rebasing ERC-20 token with additional features like pausing, blacklisting, access control, and upgradability. The contract aims to reflect the T-Bills APY into the token value through a reward multiplier mechanism. Users receive a proportional number of shares when they deposit tokens, and the number of tokens they can withdraw is calculated based on the current reward multiplier. The addRewardMultiplier function is called once a day to adjust the reward multiplier, ensuring accurate reflection of the yield from 3 months maturity T-Bills.

## Features

- Rebasing token mechanism
- Minting and burning functionality
- Blacklisting addresses
- Pausing
- Reward multiplier system
- EIP-2612 permit support
- OpenZeppelin UUPS upgrade pattern
Expand All @@ -33,33 +34,33 @@ Try running some of the following tasks:

Testing
```shell
REPORT_GAS=true npx hh test
hh test
```

Coverage
```shell
npx hh coverage
hh coverage
```

Running local node
```shell
npx hh node
hh node
```

Compile
```shell
npx hh compile
hh compile
```

Deploying and contract verification
```shell
npx hh run scripts/deploy.ts--network goerli
npx hh verify --network goerli <contact-address>
hh run scripts/deploy.ts--network goerli
hh verify --network goerli <contact-address>
```

Help
```shell
npx hh help
hh help
```

### Functions
Expand All @@ -72,7 +73,8 @@ npx hh help
- `transfer(address to, uint256 amount)`: Transfers tokens between addresses.
- `pause()`: Pauses the contract, halting token transfers.
- `unpause()`: Unpauses the contract, allowing token transfers.
- `addRewardMultiplier(uint256 rewardMultiplier_)`: Adds a reward multiplier to the contract.
- `setRewardMultiplier(uint256 rewardMultiplier_)`: Sets the reward multiplier.
- `addRewardMultiplier(uint256 rewardMultiplier_)`: Adds the provided interest rate to the current reward multiplier.
- `approve(address spender, uint256 amount)`: Approves an allowance for a spender.
- `allowance(address owner, address spender)`: Returns the allowance for a spender.
- `DOMAIN_SEPARATOR()`: Returns the EIP-712 domain separator.
Expand All @@ -83,14 +85,14 @@ npx hh help

- `_mint(address to, uint256 shares)`: Internal function to mint tokens to the specified address.
- `_burn(address account, uint256 shares)`: Internal function to burn tokens from the specified address.
- `_transferShares(address from, address to, uint256 shares)`: Internal function to transfer tokens between addresses.
- `_transfer(address from, address to, uint256 amount)`: Internal function to transfer tokens between addresses.
- `_authorizeUpgrade(address newImplementation)`: Internal function to authorize an upgrade.

#### Events

- `AddressBlacklisted(address indexed addr)`: Emitted when an address is blacklisted.
- `AddressUnBlacklisted(address indexed addr)`: Emitted when an address is removed from the blacklist.
- `RewardMultiplier(uint256 indexed addr)`: Emitted when the reward multiplier is updated.
- `RewardMultiplier(uint256 indexed addr)`: Emitted when the reward multiplier has changed.
- `Transfer(from indexed addr, to uint256, amount uint256)`: Emitted transfering tokens.


Expand Down
153 changes: 75 additions & 78 deletions contracts/Token.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,22 @@ import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-IERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";

// TODO: Upgrade Time lock

// @author: @mattiascaricato
contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable, PausableUpgradeable, UUPSUpgradeable, IERC20PermitUpgradeable, EIP712Upgradeable {
using SafeMathUpgradeable for uint256;
contract USDM is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable, PausableUpgradeable, UUPSUpgradeable, IERC20PermitUpgradeable, EIP712Upgradeable {
using MathUpgradeable for uint256;
using CountersUpgradeable for CountersUpgradeable.Counter;

string private _name;
string private _symbol;
uint256 private _rewardMultiplier;
uint256 private _totalShares;
uint256 private constant BASE = 1e18;
uint256 public rewardMultiplier;

mapping (address => uint256) private _shares;
mapping(address => bool) private _blacklist;
Expand Down Expand Up @@ -53,7 +50,7 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
function initialize(string memory name_, string memory symbol_, uint256 initialShares) external initializer {
_name = name_;
_symbol = symbol_;
_rewardMultiplier = BASE;
rewardMultiplier = BASE;

__Ownable_init();
__AccessControl_init();
Expand Down Expand Up @@ -107,17 +104,17 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
* @param amount The amount of tokens to convert
* @return The equivalent amount of shares
*/
function amountToShares(uint256 amount) public view returns (uint256) {
return amount.mul(BASE).div(rewardMultiplier());
function convertToShares(uint256 amount) public view returns (uint256) {
return amount.mulDiv(BASE, rewardMultiplier, MathUpgradeable.Rounding.Down);
}

/**
* @notice Converts an amount of shares to tokens
* @param shares The amount of shares to convert
* @return The equivalent amount of tokens
*/
function sharesToAmount(uint256 shares) public view returns (uint256) {
return shares.mul(rewardMultiplier()).div(BASE);
function convertToAmount(uint256 shares) public view returns (uint256) {
return shares.mulDiv(rewardMultiplier, BASE, MathUpgradeable.Rounding.Down);
}

/**
Expand All @@ -133,7 +130,7 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
* @return The total supply of tokens
*/
function totalSupply() external view returns (uint256) {
return sharesToAmount(_totalShares);
return convertToAmount(_totalShares);
}

/**
Expand All @@ -153,7 +150,7 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
* @return The balance of the specified address
*/
function balanceOf(address account) external view returns (uint256) {
return sharesToAmount(sharesOf(account));
return convertToAmount(sharesOf(account));
}

/**
Expand All @@ -167,11 +164,11 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable

_beforeTokenTransfer(address(0), to, shares);

_totalShares = _totalShares.add(shares);
_totalShares += shares;

unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_shares[to] = _shares[to].add(shares);
_shares[to] += shares;
}

_afterTokenTransfer(address(0), to, shares);
Expand All @@ -193,35 +190,10 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
* @param amount The amount of tokens to mint
*/
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
uint256 shares = amountToShares(amount);
uint256 shares = convertToShares(amount);
_mint(to, shares);
}

/**
* @notice Transfers a specified number of shares from one address to another.
* @dev This is an internal function.
* @param from The address from which shares will be transferred.
* @param to The address to which shares will be transferred.
* @param shares The number of shares to transfer.
*/
function _transferShares(address from, address to, uint256 shares) private {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");

_beforeTokenTransfer(from, to, shares);

uint256 fromShares = _shares[from];
require(fromShares >= shares, "ERC20: transfer amount exceeds balance");
unchecked {
_shares[from] = fromShares.sub(shares);
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_shares[to] = _shares[to].add(shares);
}

_afterTokenTransfer(from, to, shares);
}

/**
* @notice Burns a specified number of shares from the given address.
* @dev This is an internal function.
Expand All @@ -236,9 +208,9 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
uint256 accountShares = sharesOf(account);
require(accountShares >= shares, "ERC20: burn amount exceeds balance");
unchecked {
_shares[account] = accountShares.sub(shares);
_shares[account] = accountShares - shares;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalShares = _totalShares.sub(shares);
_totalShares -= shares;
}

_afterTokenTransfer(account, address(0), shares);
Expand All @@ -261,10 +233,50 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
* @param amount The amount of tokens to burn.
*/
function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) {
uint256 shares = amountToShares(amount);
uint256 shares = convertToShares(amount);
_burn(from, shares);
}

function _beforeTokenTransfer(address from, address to, uint256 amount) private view {
// Each blacklist check is an SLOAD, which is gas intensive.
// We only block sender not receiver, so we don't tax every user
require(!isBlacklisted(from), "Address is blacklisted");
// Useful for scenarios such as preventing trades until the end of an evaluation
// period, or having an emergency switch for freezing all token transfers in the
// event of a large bug.
require(!paused(), "Transfers not allowed while paused");
}

function _afterTokenTransfer(address from, address to, uint256 amount) private {
emit Transfer(from, to, amount);
}

/**
* @notice Transfers a specified number of tokens from one address to another.
* @dev This is an internal function.
* @param from The address from which shares will be transferred.
* @param to The address to which shares will be transferred.
* @param amount The number of tokens to transfer.
*/
function _transfer(address from, address to, uint256 amount) private {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");

_beforeTokenTransfer(from, to, amount);

uint256 shares = convertToShares(amount);
uint256 fromShares = _shares[from];
require(fromShares >= shares, "ERC20: transfer amount exceeds balance");
unchecked {
_shares[from] = fromShares - shares;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_shares[to] += shares;
}

_afterTokenTransfer(from, to, amount);
}

/**
* @notice Transfers a specified number of tokens from the caller's address to the recipient.
* @dev This function converts the token amount to shares and calls the _transferShares function.
Expand All @@ -278,8 +290,7 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
*/
function transfer(address to, uint256 amount) external returns (bool) {
address owner = _msgSender();
uint256 shares = amountToShares(amount);
_transferShares(owner, to, shares);
_transfer(owner, to, amount);

return true;
}
Expand All @@ -288,7 +299,7 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
* @notice Blacklists the specified address
* @param account The address to blacklist
*/
function _blacklistAccount(address account) internal onlyRole(BLACKLIST_ROLE) {
function _blacklistAccount(address account) internal {
require(!_blacklist[account], "Address already blacklisted");
_blacklist[account] = true;
emit AccountBlacklisted(account);
Expand All @@ -298,7 +309,7 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
* @notice Removes the specified address from the blacklist
* @param account The address to remove from the blacklist
*/
function _unblacklistAccount(address account) internal onlyRole(BLACKLIST_ROLE) {
function _unblacklistAccount(address account) internal {
require(_blacklist[account], "Address is not blacklisted");
_blacklist[account] = false;
emit AccountUnblacklisted(account);
Expand Down Expand Up @@ -335,20 +346,6 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
return _blacklist[account];
}

function _beforeTokenTransfer(address from, address to, uint256 amount) private view {
// Each blacklist check is an SLOAD, which is gas intensive.
// We only block sender not receiver, so we don't tax every user
require(!isBlacklisted(from), "Address is blacklisted");
// Useful for scenarios such as preventing trades until the end of an evaluation
// period, or having an emergency switch for freezing all token transfers in the
// event of a large bug.
require(!paused(), "Transfers not allowed while paused");
}

function _afterTokenTransfer(address from, address to, uint256 amount) private {
emit Transfer(from, to, amount);
}

/**
* @notice Pauses token transfers and other operations.
* @dev Only the contract owner can call this function. Inherits the _pause function from @openzeppelin/PausableUpgradeable contract.
Expand All @@ -366,25 +363,26 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
}

/**
* @notice Returns the current reward multiplier
* @return The current reward multiplier
* @notice Sets the reward multiplier.
* @dev Only users with ORACLE_ROLE can call this function.
* @param _rewardMultiplier The new reward multiplier.
*/
function rewardMultiplier() public view returns (uint256) {
return _rewardMultiplier;
function setRewardMultiplier(uint256 _rewardMultiplier) public onlyRole(ORACLE_ROLE) {
require(_rewardMultiplier > 1 ether, "Invalid reward multiplier");
rewardMultiplier = _rewardMultiplier;

emit RewardMultiplier(rewardMultiplier);
}

/**
* @notice Adds a new reward multiplier to the existing reward multiplier.
* @notice Adds the provided interest rate to the current reward multiplier.
* @dev Only users with ORACLE_ROLE can call this function.
* @param rewardMultiplier_ The new reward multiplier to be added.
* @param _rewardMultiplier The new reward multiplier.
*/
function addRewardMultiplier(uint256 rewardMultiplier_) external onlyRole(ORACLE_ROLE) {
require(rewardMultiplier_ > 0, "Invalid RewardMultiplier");
require(rewardMultiplier_ < 500000000000000, "Invalid RewardMultiplier"); // 5bps

_rewardMultiplier = _rewardMultiplier.add(rewardMultiplier_);
function addRewardMultiplier(uint256 _rewardMultiplier) external onlyRole(ORACLE_ROLE) {
require(_rewardMultiplier > 0, "Invalid reward multiplier");

emit RewardMultiplier(_rewardMultiplier);
setRewardMultiplier(rewardMultiplier + _rewardMultiplier);
}

/**
Expand Down Expand Up @@ -480,9 +478,8 @@ contract Usdm is IERC20Upgradeable, OwnableUpgradeable, AccessControlUpgradeable
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
address spender = _msgSender();
uint256 shares = amountToShares(amount);
_spendAllowance(from, spender, shares);
_transferShares(from, to, shares);
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);

return true;
}
Expand Down
1 change: 0 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import "@nomicfoundation/hardhat-toolbox";
import "@nomiclabs/hardhat-solhint";
import "@nomicfoundation/hardhat-chai-matchers";
import "@openzeppelin/hardhat-upgrades";
import "hardhat-gas-reporter";
import dotenv from "dotenv";

dotenv.config();
Expand Down
Loading

0 comments on commit 70457e5

Please sign in to comment.