Skip to content

Commit fbe48b9

Browse files
committed
Inital Commit of the Project
0 parents  commit fbe48b9

File tree

10 files changed

+520
-0
lines changed

10 files changed

+520
-0
lines changed

.github/workflows/test.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
env:
9+
FOUNDRY_PROFILE: ci
10+
11+
jobs:
12+
check:
13+
strategy:
14+
fail-fast: true
15+
16+
name: Foundry project
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
with:
21+
submodules: recursive
22+
23+
- name: Install Foundry
24+
uses: foundry-rs/foundry-toolchain@v1
25+
with:
26+
version: nightly
27+
28+
- name: Show Forge version
29+
run: |
30+
forge --version
31+
32+
- name: Run Forge fmt
33+
run: |
34+
forge fmt --check
35+
id: fmt
36+
37+
- name: Run Forge build
38+
run: |
39+
forge build --sizes
40+
id: build
41+
42+
- name: Run Forge tests
43+
run: |
44+
forge test -vvv
45+
id: test

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Compiler files
2+
cache/
3+
out/
4+
5+
# Ignores development broadcast logs
6+
!/broadcast
7+
/broadcast/*/31337/
8+
/broadcast/**/dry-run/
9+
10+
# Docs
11+
docs/
12+
13+
# Dotenv file
14+
.env

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Cross-chain Rebase Token
2+
3+
1. A protocl that allows users to deposit into a vault and in return, receive rebase tokens that represent their underlying balance.
4+
2. Rebase token -> balanceOf function that is dynamic to show the changing balance with time.
5+
- Balance increases linearly with time
6+
- mint tokens to our users every time they perform an action (mintin, burning, transferrring, or ... bridging)
7+
3. Interest Rate
8+
- Individually set an interest rate of each user based on some global interest rate of the protocol at the time the user deposits into the vault.
9+
- This global interest rate can only decrease to incentivize / reward the early adotpers.
10+
- Increase Token Adoption
11+
-

foundry.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[profile.default]
2+
src = "src"
3+
out = "out"
4+
libs = ["lib"]
5+
remappings = ["@openzeppelin/=lib/openzeppelin-contracts/"]
6+
7+
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

lib/forge-std

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 77041d2ce690e692d6e03cc812b57d1ddaa4d505

lib/openzeppelin-contracts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit e4f70216d759d8e6a64144a9e1f7bbeed78e7079

src/RebaseToken.sol

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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+
}

src/Vault.sol

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
import {IRebaseToken} from "./interface/IRebaseToken.sol";
5+
6+
contract Vault {
7+
// We need to pass the token address to the constructor
8+
// create a deposit function that mints token to the user equal to the amount of ETH the user transfers in
9+
// create a redeem function that burns tokens from the user and sends the user ETH
10+
// create a way to add rewards to the vault
11+
12+
IRebaseToken private immutable i_rebaseToken;
13+
14+
event Deposit(address indexed user, uint256 indexed amount);
15+
event Redeem(address indexed user, uint256 indexed amount);
16+
17+
error Vault__RedeemFailed();
18+
19+
constructor(IRebaseToken _rebaseToken) {
20+
i_rebaseToken = _rebaseToken;
21+
}
22+
23+
receive() external payable {}
24+
25+
/**
26+
*@notice Allows users to deposit ETH into the vault and mint rebase tokens in return
27+
*/
28+
function deposit() external payable {
29+
// we need to use the amount of Eth the user has sent to mint tokens to the user
30+
i_rebaseToken.mint(msg.sender, msg.value);
31+
emit Deposit(msg.sender, msg.value);
32+
}
33+
34+
/**
35+
* @notice Enables users to redeem their rebase tokens from the vault for Eth
36+
* @param _amount The is the amount of Rebase tokens to redeem
37+
*/
38+
function redeem(uint256 _amount) external {
39+
if (_amount == type(uint256).max) {
40+
_amount = i_rebaseToken.balanceOf(msg.sender);
41+
}
42+
// 1. burn the tokens from the user
43+
i_rebaseToken.burn(msg.sender, _amount);
44+
// 2. we need to send the user Eth
45+
(bool success, ) = payable(msg.sender).call{value: _amount}("");
46+
if (!success) {
47+
revert Vault__RedeemFailed();
48+
}
49+
emit Redeem(msg.sender, _amount);
50+
}
51+
52+
/**
53+
* @notice To retrive the rebase token address
54+
* @return The address of the rebase token
55+
*/
56+
function getRebaseTokenAddress() external view returns (address) {
57+
return address(i_rebaseToken);
58+
}
59+
}

src/interface/IRebaseToken.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
interface IRebaseToken {
5+
function mint(address _to, uint256 _amount) external;
6+
7+
function burn(address _from, uint256 _amount) external;
8+
9+
function balanceOf(address _user) external view returns (uint256);
10+
}

0 commit comments

Comments
 (0)