|
| 1 | +//SPDX-License-Identifier: MIT |
| 2 | +pragma solidity ^0.8.24; |
| 3 | + |
| 4 | +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
| 5 | +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; |
| 6 | +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; |
| 7 | + |
| 8 | +/** |
| 9 | + * @title Rebase Token |
| 10 | + * @author CX |
| 11 | + * @notice This is a cross-chain rebase token that incentivizes users to deposit into a vault |
| 12 | + * @notice The interest rate in the smart contract can only decrease |
| 13 | + * @notice Each user will have their own interest rate which is the global inteste rate at |
| 14 | + */ |
| 15 | + |
| 16 | +contract RebaseToken is ERC20, Ownable, AccessControl { |
| 17 | + error RebaseToken__InterestRateCanOnlyDecrease( |
| 18 | + uint256 s_interestRate, |
| 19 | + uint256 _newInterestRate |
| 20 | + ); |
| 21 | + |
| 22 | + uint256 private constant PRECISION_FACTOR = 1e18; |
| 23 | + bytes32 private constant MINT_AND_BURN_ROLE = |
| 24 | + keccak256("MINT_AND_BURN_ROLE"); |
| 25 | + uint256 private s_interestRate = (5 * PRECISION_FACTOR) / 1e8; |
| 26 | + mapping(address => uint256) private s_userInterestRate; |
| 27 | + mapping(address => uint256) private s_userLastUpdatedTimeStamp; |
| 28 | + |
| 29 | + event InterestRateSet(uint256 _newInterestRate); |
| 30 | + |
| 31 | + constructor() ERC20("Rebase Token", "RBT") Ownable(msg.sender) {} |
| 32 | + |
| 33 | + function grantMintAndBurnRole(address _account) external onlyOwner { |
| 34 | + _grantRole(MINT_AND_BURN_ROLE, _account); |
| 35 | + } |
| 36 | + |
| 37 | + /** |
| 38 | + * @notice Set the interest rate in the contract |
| 39 | + * @param _newInterestRate The new interest rate to set |
| 40 | + * @dev The interest rate can only decrease |
| 41 | + */ |
| 42 | + function setInterestRate(uint256 _newInterestRate) external onlyOwner { |
| 43 | + // set interest rate |
| 44 | + if (_newInterestRate >= s_interestRate) { |
| 45 | + revert RebaseToken__InterestRateCanOnlyDecrease( |
| 46 | + s_interestRate, |
| 47 | + _newInterestRate |
| 48 | + ); |
| 49 | + } |
| 50 | + s_interestRate = _newInterestRate; |
| 51 | + emit InterestRateSet(_newInterestRate); |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * @notice Get the principle balance of a user, This is the number of tokens that have currently been minted to the user, not including any interest that has accrued since the last time the user interated with the protocl |
| 56 | + * @param _user The user whose princple balance is been fetched |
| 57 | + * @return The principle balance of the user |
| 58 | + */ |
| 59 | + function getPrincipleBalanceOf( |
| 60 | + address _user |
| 61 | + ) external view returns (uint256) { |
| 62 | + return super.balanceOf(_user); |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * @notice Mint the user tokens when they deposit into the vault |
| 67 | + * @param _to The user to mint the tokens to |
| 68 | + * @param amount The amount of tokens to mint |
| 69 | + */ |
| 70 | + function mint( |
| 71 | + address _to, |
| 72 | + uint256 amount |
| 73 | + ) external onlyRole(MINT_AND_BURN_ROLE) { |
| 74 | + _mintAccruedInterest(_to); |
| 75 | + s_userInterestRate[_to] = s_interestRate; |
| 76 | + _mint(_to, amount); |
| 77 | + } |
| 78 | + |
| 79 | + /** |
| 80 | + * @notice Burn the user tokens when they withdraw from the vault |
| 81 | + * @param _from The user to burn the tokens from |
| 82 | + * @param _amount The amount of tokens to burn |
| 83 | + */ |
| 84 | + function burn( |
| 85 | + address _from, |
| 86 | + uint256 _amount |
| 87 | + ) external onlyRole(MINT_AND_BURN_ROLE) { |
| 88 | + _mintAccruedInterest(_from); |
| 89 | + _burn(_from, _amount); |
| 90 | + } |
| 91 | + |
| 92 | + function balanceOf(address _user) public view override returns (uint256) { |
| 93 | + // get the current principle balance of the user (the number of tokens that have actually been minted to the user) |
| 94 | + // multiply the principle balance of the interest that has accumulated in the times since the balance was last updated |
| 95 | + return |
| 96 | + (super.balanceOf(_user) * |
| 97 | + _calculateUserAccumulatedInterestSinceUpdate(_user)) / |
| 98 | + PRECISION_FACTOR; |
| 99 | + } |
| 100 | + |
| 101 | + /** |
| 102 | + * @notice Transfer tokens from one user to another |
| 103 | + * @param _recipient The user to transfer the tokens to |
| 104 | + * @param _amount The amount of tokens to transfer to the user |
| 105 | + * @return True if the transfer succeeds |
| 106 | + */ |
| 107 | + function transfer( |
| 108 | + address _recipient, |
| 109 | + uint256 _amount |
| 110 | + ) public override returns (bool) { |
| 111 | + _mintAccruedInterest(msg.sender); |
| 112 | + _mintAccruedInterest(_recipient); |
| 113 | + if (_amount == type(uint256).max) { |
| 114 | + _amount = balanceOf(msg.sender); |
| 115 | + } |
| 116 | + if (balanceOf(_recipient) == 0) { |
| 117 | + s_userInterestRate[_recipient] = s_userInterestRate[msg.sender]; |
| 118 | + } |
| 119 | + return super.transfer(_recipient, _amount); |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * @notice Transfer tokens on behalf of the sender to the recipient |
| 124 | + * @param _sender The user on whose behalf the tokens are being transfered |
| 125 | + * @param _recipient The user thats receiving the transferred tokens |
| 126 | + * @param _amount The amount of tokens that are to be transfered |
| 127 | + * @return True is the token is transferred successfully |
| 128 | + */ |
| 129 | + function transferFrom( |
| 130 | + address _sender, |
| 131 | + address _recipient, |
| 132 | + uint256 _amount |
| 133 | + ) public override returns (bool) { |
| 134 | + _mintAccruedInterest(_sender); |
| 135 | + _mintAccruedInterest(_recipient); |
| 136 | + if (_amount == type(uint256).max) { |
| 137 | + _amount = balanceOf(_sender); |
| 138 | + } |
| 139 | + if (balanceOf(_recipient) == 0) { |
| 140 | + s_userInterestRate[_recipient] = s_userInterestRate[_sender]; |
| 141 | + } |
| 142 | + return super.transferFrom(_sender, _recipient, _amount); |
| 143 | + } |
| 144 | + |
| 145 | + /** |
| 146 | + * @notice Calculate the interest that has accumulated since the last update |
| 147 | + * @param _user The user to calculate the interest accumulated for |
| 148 | + * @return linearInterest The interest that has been accumulated since the last update |
| 149 | + */ |
| 150 | + function _calculateUserAccumulatedInterestSinceUpdate( |
| 151 | + address _user |
| 152 | + ) internal view returns (uint256 linearInterest) { |
| 153 | + // we need to calculate the interest that has accumulated since the last update |
| 154 | + // this is going to be linear growth with time |
| 155 | + // 1. calculate the time since the last update |
| 156 | + // 2. calculate the amount of linear growth |
| 157 | + uint256 timeElapsed = block.timestamp - |
| 158 | + s_userLastUpdatedTimeStamp[_user]; |
| 159 | + linearInterest = (PRECISION_FACTOR + |
| 160 | + (s_userInterestRate[_user] * timeElapsed)); |
| 161 | + } |
| 162 | + |
| 163 | + /** |
| 164 | + * @notice Mint the accrued interest to the user since the last time they interacted woth the protocl (e.g burn, mint, transfer) |
| 165 | + * @param _user The user to which the accrued interest is being minted to |
| 166 | + */ |
| 167 | + function _mintAccruedInterest(address _user) internal { |
| 168 | + // (1) find their current balance of rebase tokens that have been minted to the user -> Principal |
| 169 | + uint256 previousPrincipleBalance = super.balanceOf(_user); |
| 170 | + // (2) calculate their current balance including any interest -> balanceOf |
| 171 | + uint256 currentBalance = balanceOf(_user); |
| 172 | + // calculate the number of tokens that need to be minted to the user -> (2) - (1) |
| 173 | + uint256 balanceIncrease = currentBalance - previousPrincipleBalance; |
| 174 | + // set the users last updated timestamp |
| 175 | + s_userLastUpdatedTimeStamp[_user] = block.timestamp; |
| 176 | + // call _mint to mint the tokens to the user |
| 177 | + _mint(_user, balanceIncrease); |
| 178 | + } |
| 179 | + |
| 180 | + /** |
| 181 | + * @notice Get the contract's current interest rate, to which any new depostior would get |
| 182 | + * @return The interest rate of the contract |
| 183 | + */ |
| 184 | + function getInterestRate() external view returns (uint256) { |
| 185 | + return s_interestRate; |
| 186 | + } |
| 187 | + |
| 188 | + /** |
| 189 | + * @notice Get the interest rate for the user |
| 190 | + * @param _user The user to ge the interest rate for |
| 191 | + * @return The Interest rate for the user |
| 192 | + */ |
| 193 | + function getUserInterestrate( |
| 194 | + address _user |
| 195 | + ) external view returns (uint256) { |
| 196 | + return s_userInterestRate[_user]; |
| 197 | + } |
| 198 | +} |
0 commit comments