From 506c37f13e231e935f32a01ca206c3685fa48f57 Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Wed, 25 Oct 2023 22:58:43 +0800 Subject: [PATCH 1/5] Initial code --- contracts/LendingPoolRewards.sol | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 contracts/LendingPoolRewards.sol diff --git a/contracts/LendingPoolRewards.sol b/contracts/LendingPoolRewards.sol new file mode 100644 index 00000000..48072440 --- /dev/null +++ b/contracts/LendingPoolRewards.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +contract LendingPoolRewards { + uint256 public emissionPerSecond; + uint256 public lastUpdateTimestamp; + uint256 public index; + mapping(address => uint256) public users; + + /** + * @dev Returns the total of rewards of an user, already accrued + not yet accrued + * @param user The address of the user + * @return The rewards + **/ + function getRewardsBalance(address user) + external + view + override + returns (uint256) + { + uint256 unclaimedRewards = _usersUnclaimedRewards[user]; + + userState[i].underlyingAsset = assets[i]; + (userState[i].stakedByUser, userState[i].totalStaked) = IAToken(assets[i]) + .getScaledUserBalanceAndSupply(user); + + unclaimedRewards = unclaimedRewards.add(_getUnclaimedRewards(user, userState)); + return unclaimedRewards; + } + + /** + * @dev Calculates the next value of an specific distribution index, with validations + * @param currentIndex Current index of the distribution + * @param emissionPerSecond Representing the total rewards distributed per second per asset unit, on the distribution + * @param lastUpdateTimestamp Last moment this distribution was updated + * @param totalBalance of tokens considered for the distribution + * @return The new index. + **/ + function _getAssetIndex( + uint256 currentIndex, + uint256 emissionPerSecond, + uint128 lastUpdateTimestamp, + uint256 totalBalance + ) internal view returns (uint256) { + if ( + emissionPerSecond == 0 || + totalBalance == 0 || + lastUpdateTimestamp == block.timestamp || + lastUpdateTimestamp >= DISTRIBUTION_END + ) { + return currentIndex; + } + + uint256 currentTimestamp = + block.timestamp > DISTRIBUTION_END ? DISTRIBUTION_END : block.timestamp; + uint256 timeDelta = currentTimestamp.sub(lastUpdateTimestamp); + return + emissionPerSecond.mul(timeDelta).mul(10**uint256(PRECISION)).div(totalBalance).add( + currentIndex + ); + } +} \ No newline at end of file From d7a81d4b81b829f55b73228a59bc6ba2b647ccf5 Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Tue, 31 Oct 2023 03:36:17 +0800 Subject: [PATCH 2/5] Simple calculation --- contracts/LendingPoolRewards.sol | 62 -------- contracts/lendingpool/LendingPoolRewards.sol | 146 +++++++++++++++++++ 2 files changed, 146 insertions(+), 62 deletions(-) delete mode 100644 contracts/LendingPoolRewards.sol create mode 100644 contracts/lendingpool/LendingPoolRewards.sol diff --git a/contracts/LendingPoolRewards.sol b/contracts/LendingPoolRewards.sol deleted file mode 100644 index 48072440..00000000 --- a/contracts/LendingPoolRewards.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.16; - -contract LendingPoolRewards { - uint256 public emissionPerSecond; - uint256 public lastUpdateTimestamp; - uint256 public index; - mapping(address => uint256) public users; - - /** - * @dev Returns the total of rewards of an user, already accrued + not yet accrued - * @param user The address of the user - * @return The rewards - **/ - function getRewardsBalance(address user) - external - view - override - returns (uint256) - { - uint256 unclaimedRewards = _usersUnclaimedRewards[user]; - - userState[i].underlyingAsset = assets[i]; - (userState[i].stakedByUser, userState[i].totalStaked) = IAToken(assets[i]) - .getScaledUserBalanceAndSupply(user); - - unclaimedRewards = unclaimedRewards.add(_getUnclaimedRewards(user, userState)); - return unclaimedRewards; - } - - /** - * @dev Calculates the next value of an specific distribution index, with validations - * @param currentIndex Current index of the distribution - * @param emissionPerSecond Representing the total rewards distributed per second per asset unit, on the distribution - * @param lastUpdateTimestamp Last moment this distribution was updated - * @param totalBalance of tokens considered for the distribution - * @return The new index. - **/ - function _getAssetIndex( - uint256 currentIndex, - uint256 emissionPerSecond, - uint128 lastUpdateTimestamp, - uint256 totalBalance - ) internal view returns (uint256) { - if ( - emissionPerSecond == 0 || - totalBalance == 0 || - lastUpdateTimestamp == block.timestamp || - lastUpdateTimestamp >= DISTRIBUTION_END - ) { - return currentIndex; - } - - uint256 currentTimestamp = - block.timestamp > DISTRIBUTION_END ? DISTRIBUTION_END : block.timestamp; - uint256 timeDelta = currentTimestamp.sub(lastUpdateTimestamp); - return - emissionPerSecond.mul(timeDelta).mul(10**uint256(PRECISION)).div(totalBalance).add( - currentIndex - ); - } -} \ No newline at end of file diff --git a/contracts/lendingpool/LendingPoolRewards.sol b/contracts/lendingpool/LendingPoolRewards.sol new file mode 100644 index 00000000..a73a7a39 --- /dev/null +++ b/contracts/lendingpool/LendingPoolRewards.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +import './library/UtilLib.sol'; + +contract LendingPoolRewards { + uint256 public emissionPerSecond; + uint256 public lastUpdateTimestamp; + uint256 public index; + address public lendingPool; + struct UserState { + uint256 lastUpdateTimestamp; + uint256 pendingRewards; + uint256 lastIndex; + } + mapping(address => uint256) public users; + + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /** + * @dev Initializes the contract + * @param _emissionPerSecond The emission per second of the distribution + * @param _assets The assets to be distributed + * @param _lendingPool The address of the LendingPool contract + **/ + */ + function initialize(address _lendingPool) external initializer { + UtilLib.checkNonZeroAddress(_lendingPool); + + emissionPerSecond = 1; + lastUpdateTimestamp = block.timestamp; + index = 1e18; + lendingPool = _lendingPool; + + __AccessControl_init(); + } + + function onChange(address user, uint256 newBalance) external { + require(msg.sender == lendingPool, "Only the lending pool can call this function"); + + UserState storage userState = users[user]; + userState.pendingRewards = _getRewardsBalance(user); + userState.lastUpdateTimestamp = block.timestamp; + userState.lastIndex = index; + } + + /** + * @dev Returns the total of rewards of an user, already accrued + not yet accrued + * @param user The address of the user + * @return The rewards + **/ + function getRewardsBalance(address user) + external + view + override + returns (uint256) + { + uint256 unclaimedRewards = _usersUnclaimedRewards[user]; + + userState[i].underlyingAsset = assets[i]; + (userState[i].stakedByUser, userState[i].totalStaked) = IAToken(assets[i]) + .getScaledUserBalanceAndSupply(user); + + unclaimedRewards = unclaimedRewards.add(_getUnclaimedRewards(user, userState)); + return unclaimedRewards; + } + + /** + * @dev Calculates the next value of an specific distribution index, with validations + * @param currentIndex Current index of the distribution + * @param emissionPerSecond Representing the total rewards distributed per second per asset unit, on the distribution + * @param lastUpdateTimestamp Last moment this distribution was updated + * @param totalBalance of tokens considered for the distribution + * @return The new index. + **/ + function _getAssetIndex( + uint256 currentIndex, + uint256 emissionPerSecond, + uint128 lastUpdateTimestamp, + uint256 totalBalance + ) internal view returns (uint256) { + if ( + emissionPerSecond == 0 || + totalBalance == 0 || + lastUpdateTimestamp == block.timestamp || + lastUpdateTimestamp >= DISTRIBUTION_END + ) { + return currentIndex; + } + + uint256 currentTimestamp = + block.timestamp > DISTRIBUTION_END ? DISTRIBUTION_END : block.timestamp; + uint256 timeDelta = currentTimestamp.sub(lastUpdateTimestamp); + return + emissionPerSecond.mul(timeDelta).mul(10**uint256(PRECISION)).div(totalBalance).add( + currentIndex + ); + } + + modifier updateReward(address account) { + rewardPerTokenStored = rewardPerToken(); + lastUpdateTime = block.timestamp; + if(account != address(0)) { + rewards[account] = earned(account); + userRewardPerTokenPaid[account] = rewardPerTokenStored; + } + _; + } + + function updateRewardSnapshot() internal updateReward(address(0)) { + // Create a new snapshot with the current state. + rewardSnapshots.push(Snapshot({ + time: block.timestamp, + rewardPerToken: rewardPerTokenStored + })); + } + + function rewardPerToken() public view returns (uint256) { + if (cToken.totalSupply() == 0) { + return rewardPerTokenStored; + } + return rewardPerTokenStored + ( + (block.timestamp - lastUpdateTime) * rewardRatePerSecond * 1e18 / cToken.totalSupply() + ); + } + + function earned(address account) public view returns (uint256) { + uint256 currentBalance = cToken.balanceOf(account); + uint256 currentRewardPerToken = rewardPerToken(); + return (currentBalance * (currentRewardPerToken - userRewardPerTokenPaid[account]) / 1e18) + rewards[account]; + } + + function claimReward() external updateReward(msg.sender) { + uint256 reward = rewards[msg.sender]; + require(reward > 0, "No rewards to claim."); + rewards[msg.sender] = 0; + // Transfer the rewards to the user. + emit RewardClaimed(msg.sender, reward); + } + + event RewardClaimed(address indexed user, uint256 reward); +} \ No newline at end of file From fbc21a7faeb78161ed1f409f678c976c8f1376ca Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Thu, 2 Nov 2023 02:04:46 +0800 Subject: [PATCH 3/5] Simple implementation --- contracts/StaderConfig.sol | 9 ++ contracts/interfaces/IStaderConfig.sol | 4 + contracts/lendingpool/IncentiveController.sol | 118 ++++++++++++++ contracts/lendingpool/LendingPoolRewards.sol | 146 ------------------ 4 files changed, 131 insertions(+), 146 deletions(-) create mode 100644 contracts/lendingpool/IncentiveController.sol delete mode 100644 contracts/lendingpool/LendingPoolRewards.sol diff --git a/contracts/StaderConfig.sol b/contracts/StaderConfig.sol index f7ee8992..ad231a8c 100644 --- a/contracts/StaderConfig.sol +++ b/contracts/StaderConfig.sol @@ -59,6 +59,7 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { keccak256('NODE_EL_REWARD_VAULT_IMPLEMENTATION'); bytes32 public constant override VALIDATOR_WITHDRAWAL_VAULT_IMPLEMENTATION = keccak256('VALIDATOR_WITHDRAWAL_VAULT_IMPLEMENTATION'); + bytes32 public constant override LENDING_POOL_CONTRACT = keccak256('LENDING_POOL_CONTRACT'); //POR Feed Proxy bytes32 public constant override ETH_BALANCE_POR_FEED = keccak256('ETH_BALANCE_POR_FEED'); @@ -284,6 +285,10 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { setContract(ETHX_SUPPLY_POR_FEED, _ethXSupplyProxy); } + function updateLendingPoolProxy(address _lendingPoolProxy) external onlyRole(DEFAULT_ADMIN_ROLE) { + setContract(LENDING_POOL_CONTRACT, _lendingPoolProxy); + } + function updateStaderToken(address _staderToken) external onlyRole(DEFAULT_ADMIN_ROLE) { setToken(SD, _staderToken); } @@ -448,6 +453,10 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { return contractsMap[VALIDATOR_WITHDRAWAL_VAULT_IMPLEMENTATION]; } + function getLendingPool() external view override returns (address) { + return contractsMap[LENDING_POOL_CONTRACT]; + } + //POR Feed Proxy Getters function getETHBalancePORFeedProxy() external view override returns (address) { return contractsMap[ETH_BALANCE_POR_FEED]; diff --git a/contracts/interfaces/IStaderConfig.sol b/contracts/interfaces/IStaderConfig.sol index 280f0cc9..425a86b7 100644 --- a/contracts/interfaces/IStaderConfig.sol +++ b/contracts/interfaces/IStaderConfig.sol @@ -58,6 +58,8 @@ interface IStaderConfig { function VALIDATOR_WITHDRAWAL_VAULT_IMPLEMENTATION() external view returns (bytes32); + function LENDING_POOL_CONTRACT() external view returns (bytes32); + //POR Feed Proxy function ETH_BALANCE_POR_FEED() external view returns (bytes32); @@ -149,6 +151,8 @@ interface IStaderConfig { function getETHBalancePORFeedProxy() external view returns (address); function getETHXSupplyPORFeedProxy() external view returns (address); + + function getLendingPool() external view returns (address); // Tokens function getStaderToken() external view returns (address); diff --git a/contracts/lendingpool/IncentiveController.sol b/contracts/lendingpool/IncentiveController.sol new file mode 100644 index 00000000..8903087c --- /dev/null +++ b/contracts/lendingpool/IncentiveController.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol'; +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import '../library/UtilLib.sol'; +import '../interfaces/IStaderConfig.sol'; + +/// @title IncentiveController +/// @notice This contract handles the distribution of reward tokens for a lending pool. +contract IncentiveController is AccessControlUpgradeable { + // The emission rate of the reward tokens per second. + uint256 public emissionPerSecond; + + // The timestamp of the last reward calculation. + uint256 public lastUpdateTimestamp; + + // The stored value of the reward per token, used to calculate rewards. + uint256 public rewardPerTokenStored; + + // Reference to the lending pool token contract. + IERC20 public lendingPoolToken; + + // Reference to the reward token contract. + IERC20 public rewardToken; + + // Reference to the Stader configuration contract. + IStaderConfig public staderConfig; + + // A mapping of accounts to their pending reward amounts. + mapping(address => uint256) public rewards; + + // A mapping of accounts to the reward per token value at their last update. + mapping(address => uint256) public userRewardPerTokenPaid; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @notice Initializes the contract with necessary addresses. + /// @param _lendingPoolToken The address of the lending pool token contract. + /// @param _staderConfig The address of the Stader configuration contract. + /// @param _rewardToken The address of the reward token contract. + function initialize(address _lendingPoolToken, address _staderConfig, address _rewardToken) external initializer { + UtilLib.checkNonZeroAddress(_lendingPoolToken); + UtilLib.checkNonZeroAddress(_staderConfig); + UtilLib.checkNonZeroAddress(_rewardToken); + + lendingPoolToken = IERC20(_lendingPoolToken); + staderConfig = IStaderConfig(_staderConfig); + rewardToken = IERC20(_rewardToken); + + __AccessControl_init(); + } + + /// @notice Claims the accrued rewards for an account. + /// @param account The address of the account claiming rewards. + function claim(address account) external { + UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.LENDING_POOL_CONTRACT()); + + updateReward(account); + + uint256 reward = rewards[account]; + require(reward > 0, "No rewards to claim."); + rewards[account] = 0; + rewardToken.transfer(account, reward); + + emit RewardClaimed(account, reward); + } + + /// @notice Updates the reward on deposit in the lending pool. + /// @param account The account that made a deposit. + function onDeposit(address account) external { + UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.LENDING_POOL_CONTRACT()); + + updateReward(account); + } + + /// @notice Calculates the current reward per token. + /// @return The calculated reward per token. + function rewardPerToken() public view returns (uint256) { + if (lendingPoolToken.totalSupply() == 0) { + return rewardPerTokenStored; + } + return rewardPerTokenStored + ( + (block.timestamp - lastUpdateTimestamp) * emissionPerSecond * 1e18 / lendingPoolToken.totalSupply() + ); + } + + /// @notice Calculates the total accrued reward for an account. + /// @param account The account to calculate rewards for. + /// @return The total accrued reward for the account. + function earned(address account) public view returns (uint256) { + uint256 currentBalance = lendingPoolToken.balanceOf(account); + uint256 currentRewardPerToken = rewardPerToken(); + + return (currentBalance * (currentRewardPerToken - userRewardPerTokenPaid[account]) / 1e18) + rewards[account]; + } + + /// @dev Internal function to update the reward state for an account. + /// @param account The account to update the reward for. + function updateReward(address account) internal { + rewardPerTokenStored = rewardPerToken(); + lastUpdateTimestamp = block.timestamp; + + if(account != address(0)) { + rewards[account] = earned(account); + userRewardPerTokenPaid[account] = rewardPerTokenStored; + } + } + + /// @dev Emitted when a reward is claimed. + /// @param user The user who claimed the reward. + /// @param reward The amount of reward claimed. + event RewardClaimed(address indexed user, uint256 reward); +} diff --git a/contracts/lendingpool/LendingPoolRewards.sol b/contracts/lendingpool/LendingPoolRewards.sol deleted file mode 100644 index a73a7a39..00000000 --- a/contracts/lendingpool/LendingPoolRewards.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.16; - -import './library/UtilLib.sol'; - -contract LendingPoolRewards { - uint256 public emissionPerSecond; - uint256 public lastUpdateTimestamp; - uint256 public index; - address public lendingPool; - struct UserState { - uint256 lastUpdateTimestamp; - uint256 pendingRewards; - uint256 lastIndex; - } - mapping(address => uint256) public users; - - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - /** - * @dev Initializes the contract - * @param _emissionPerSecond The emission per second of the distribution - * @param _assets The assets to be distributed - * @param _lendingPool The address of the LendingPool contract - **/ - */ - function initialize(address _lendingPool) external initializer { - UtilLib.checkNonZeroAddress(_lendingPool); - - emissionPerSecond = 1; - lastUpdateTimestamp = block.timestamp; - index = 1e18; - lendingPool = _lendingPool; - - __AccessControl_init(); - } - - function onChange(address user, uint256 newBalance) external { - require(msg.sender == lendingPool, "Only the lending pool can call this function"); - - UserState storage userState = users[user]; - userState.pendingRewards = _getRewardsBalance(user); - userState.lastUpdateTimestamp = block.timestamp; - userState.lastIndex = index; - } - - /** - * @dev Returns the total of rewards of an user, already accrued + not yet accrued - * @param user The address of the user - * @return The rewards - **/ - function getRewardsBalance(address user) - external - view - override - returns (uint256) - { - uint256 unclaimedRewards = _usersUnclaimedRewards[user]; - - userState[i].underlyingAsset = assets[i]; - (userState[i].stakedByUser, userState[i].totalStaked) = IAToken(assets[i]) - .getScaledUserBalanceAndSupply(user); - - unclaimedRewards = unclaimedRewards.add(_getUnclaimedRewards(user, userState)); - return unclaimedRewards; - } - - /** - * @dev Calculates the next value of an specific distribution index, with validations - * @param currentIndex Current index of the distribution - * @param emissionPerSecond Representing the total rewards distributed per second per asset unit, on the distribution - * @param lastUpdateTimestamp Last moment this distribution was updated - * @param totalBalance of tokens considered for the distribution - * @return The new index. - **/ - function _getAssetIndex( - uint256 currentIndex, - uint256 emissionPerSecond, - uint128 lastUpdateTimestamp, - uint256 totalBalance - ) internal view returns (uint256) { - if ( - emissionPerSecond == 0 || - totalBalance == 0 || - lastUpdateTimestamp == block.timestamp || - lastUpdateTimestamp >= DISTRIBUTION_END - ) { - return currentIndex; - } - - uint256 currentTimestamp = - block.timestamp > DISTRIBUTION_END ? DISTRIBUTION_END : block.timestamp; - uint256 timeDelta = currentTimestamp.sub(lastUpdateTimestamp); - return - emissionPerSecond.mul(timeDelta).mul(10**uint256(PRECISION)).div(totalBalance).add( - currentIndex - ); - } - - modifier updateReward(address account) { - rewardPerTokenStored = rewardPerToken(); - lastUpdateTime = block.timestamp; - if(account != address(0)) { - rewards[account] = earned(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - } - _; - } - - function updateRewardSnapshot() internal updateReward(address(0)) { - // Create a new snapshot with the current state. - rewardSnapshots.push(Snapshot({ - time: block.timestamp, - rewardPerToken: rewardPerTokenStored - })); - } - - function rewardPerToken() public view returns (uint256) { - if (cToken.totalSupply() == 0) { - return rewardPerTokenStored; - } - return rewardPerTokenStored + ( - (block.timestamp - lastUpdateTime) * rewardRatePerSecond * 1e18 / cToken.totalSupply() - ); - } - - function earned(address account) public view returns (uint256) { - uint256 currentBalance = cToken.balanceOf(account); - uint256 currentRewardPerToken = rewardPerToken(); - return (currentBalance * (currentRewardPerToken - userRewardPerTokenPaid[account]) / 1e18) + rewards[account]; - } - - function claimReward() external updateReward(msg.sender) { - uint256 reward = rewards[msg.sender]; - require(reward > 0, "No rewards to claim."); - rewards[msg.sender] = 0; - // Transfer the rewards to the user. - emit RewardClaimed(msg.sender, reward); - } - - event RewardClaimed(address indexed user, uint256 reward); -} \ No newline at end of file From 05eeafd8a22532735aa43a6222a7092227b3ae15 Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Thu, 2 Nov 2023 02:18:07 +0800 Subject: [PATCH 4/5] LendingPool interfaces --- contracts/interfaces/ILendingPool.sol | 18 ++++++++++++++++++ contracts/lendingpool/LendingPool.sol | 26 ++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 contracts/interfaces/ILendingPool.sol create mode 100644 contracts/lendingpool/LendingPool.sol diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol new file mode 100644 index 00000000..de908bab --- /dev/null +++ b/contracts/interfaces/ILendingPool.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +import './IStaderConfig.sol'; + +interface ILendingPool { + + // events + event UpdatedStaderConfig(address staderConfig); + + // functions + function deposit(uint256 amount) external returns (uint256); + + function withdraw(uint256 amount) external returns (uint256); + + function claim(uint256 index) external returns (uint256); + +} \ No newline at end of file diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol new file mode 100644 index 00000000..81fc4274 --- /dev/null +++ b/contracts/lendingpool/LendingPool.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol'; + +import '../interfaces/ILendingPool.sol'; + +contract LendingPool is ILendingPool, AccessControlUpgradeable { + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function deposit(uint256 amount) external override returns (uint256) { + return 0; + } + + function withdraw(uint256 amount) external override returns (uint256) { + return 0; + } + + function claim(uint256 index) external override returns (uint256) { + return 0; + } +} \ No newline at end of file From d46c474451fdaddabcbcdd2beb67bb0bcd32317c Mon Sep 17 00:00:00 2001 From: arigatodl <99845398+dulguun-staderlabs@users.noreply.github.com> Date: Fri, 3 Nov 2023 00:21:07 +0800 Subject: [PATCH 5/5] Interfaces for erc20 and lendingPool --- contracts/interfaces/IIncentiveController.sol | 19 ++++ contracts/interfaces/ILendingPool.sol | 18 +++- contracts/interfaces/ILendingPoolToken.sol | 8 ++ contracts/lendingpool/IncentiveController.sol | 3 +- contracts/lendingpool/LendingPool.sol | 33 ++++++- contracts/lendingpool/SDX.sol | 90 +++++++++++++++++++ 6 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 contracts/interfaces/IIncentiveController.sol create mode 100644 contracts/interfaces/ILendingPoolToken.sol create mode 100644 contracts/lendingpool/SDX.sol diff --git a/contracts/interfaces/IIncentiveController.sol b/contracts/interfaces/IIncentiveController.sol new file mode 100644 index 00000000..dbd690f8 --- /dev/null +++ b/contracts/interfaces/IIncentiveController.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +import './IStaderConfig.sol'; + +interface IIncentiveController { + + // events + event UpdatedStaderConfig(address staderConfig); + + // functions + function claim(address account) external; + + function onDeposit(address account) external; + + function rewardPerToken() external view returns (uint256); + + function earned(address account) external view returns (uint256); +} diff --git a/contracts/interfaces/ILendingPool.sol b/contracts/interfaces/ILendingPool.sol index de908bab..3e7b6257 100644 --- a/contracts/interfaces/ILendingPool.sol +++ b/contracts/interfaces/ILendingPool.sol @@ -3,6 +3,13 @@ pragma solidity 0.8.16; import './IStaderConfig.sol'; +struct UserData { + uint256 totalInterestSD; + uint256 totalCollateralInSD; + uint256 ltv; + uint256 healthFactor; +} + interface ILendingPool { // events @@ -11,8 +18,17 @@ interface ILendingPool { // functions function deposit(uint256 amount) external returns (uint256); - function withdraw(uint256 amount) external returns (uint256); + function requestWithdraw(uint256 amount) external returns (uint256); function claim(uint256 index) external returns (uint256); + function borrow(uint256 amount) external returns (uint256); + + function repay(uint256 amount) external returns (uint256); + + function liquidationCall(address account) external returns (uint256); + + function claimLiquidation(uint256 index) external returns (uint256); + + function getUserData(address account) external view returns (UserData memory); } \ No newline at end of file diff --git a/contracts/interfaces/ILendingPoolToken.sol b/contracts/interfaces/ILendingPoolToken.sol new file mode 100644 index 00000000..46ffcaf8 --- /dev/null +++ b/contracts/interfaces/ILendingPoolToken.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +interface ILendingPoolToken { + function mint(address account, uint256 amount) external; + + function burn(address account, uint256 amount) external; +} diff --git a/contracts/lendingpool/IncentiveController.sol b/contracts/lendingpool/IncentiveController.sol index 8903087c..fdea65a7 100644 --- a/contracts/lendingpool/IncentiveController.sol +++ b/contracts/lendingpool/IncentiveController.sol @@ -6,10 +6,11 @@ import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import '../library/UtilLib.sol'; import '../interfaces/IStaderConfig.sol'; +import '../interfaces/IIncentiveController.sol'; /// @title IncentiveController /// @notice This contract handles the distribution of reward tokens for a lending pool. -contract IncentiveController is AccessControlUpgradeable { +contract IncentiveController is IIncentiveController, AccessControlUpgradeable { // The emission rate of the reward tokens per second. uint256 public emissionPerSecond; diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol index 81fc4274..78dc2c8b 100644 --- a/contracts/lendingpool/LendingPool.sol +++ b/contracts/lendingpool/LendingPool.sol @@ -4,8 +4,12 @@ pragma solidity 0.8.16; import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol'; import '../interfaces/ILendingPool.sol'; +import '../interfaces/IIncentiveController.sol'; +import '../interfaces/ILendingPoolToken.sol'; -contract LendingPool is ILendingPool, AccessControlUpgradeable { +contract LendingPool is ILendingPool, AccessControlUpgradeable { + IIncentiveController public incentiveController; + ILendingPoolToken public lendingPoolToken; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -13,14 +17,39 @@ contract LendingPool is ILendingPool, AccessControlUpgradeable { } function deposit(uint256 amount) external override returns (uint256) { + incentiveController.onDeposit(msg.sender); + lendingPoolToken.mint(msg.sender, amount); return 0; } - function withdraw(uint256 amount) external override returns (uint256) { + function requestWithdraw(uint256 amount) external override returns (uint256) { + incentiveController.claim(msg.sender); + lendingPoolToken.burn(msg.sender, amount); return 0; } function claim(uint256 index) external override returns (uint256) { + incentiveController.claim(msg.sender); return 0; } + + function borrow(uint256 amount) external override returns (uint256) { + return 0; + } + + function repay(uint256 amount) external override returns (uint256) { + return 0; + } + + function liquidationCall(address account) external override returns (uint256) { + return 0; + } + + function claimLiquidation(uint256 index) external override returns (uint256) { + return 0; + } + + function getUserData(address account) external override view returns (UserData memory) { + return UserData(0, 0, 0, 0); + } } \ No newline at end of file diff --git a/contracts/lendingpool/SDX.sol b/contracts/lendingpool/SDX.sol new file mode 100644 index 00000000..5dd2cab5 --- /dev/null +++ b/contracts/lendingpool/SDX.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.16; + +import '@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol'; +import '@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol'; + +import '../interfaces/IStaderConfig.sol'; +import '../library/UtilLib.sol'; + +contract SDX is Initializable, AccessControlUpgradeable, ERC20Upgradeable, PausableUpgradeable{ + event UpdatedStaderConfig(address indexed _staderConfig); + + IStaderConfig public staderConfig; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address _admin, address _staderConfig) external initializer { + UtilLib.checkNonZeroAddress(_admin); + UtilLib.checkNonZeroAddress(_staderConfig); + + __ERC20_init('Interest bearing SD token', 'SDx'); + __Pausable_init(); + __AccessControl_init(); + + staderConfig = IStaderConfig(_staderConfig); + _grantRole(DEFAULT_ADMIN_ROLE, _admin); + + emit UpdatedStaderConfig(_staderConfig); + } + + /** + * @notice Mints SDx when called by an authorized caller + * @param to the account to mint to + * @param amount the amount of SDx to mint + */ + function mint(address to, uint256 amount) external whenNotPaused { + UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.LENDING_POOL_CONTRACT()); + + _mint(to, amount); + } + + /** + * @notice Burns SDx when called by an authorized caller + * @param account the account to burn from + * @param amount the amount of SDx to burn + */ + function burnFrom(address account, uint256 amount) external whenNotPaused { + UtilLib.onlyStaderContract(msg.sender, staderConfig, staderConfig.LENDING_POOL_CONTRACT()); + + _burn(account, amount); + } + + /** + * @dev Triggers stopped state. + * Contract must not be paused. + */ + function pause() external { + UtilLib.onlyManagerRole(msg.sender, staderConfig); + + _pause(); + } + + /** + * @dev Returns to normal state. + * Contract must be paused + */ + function unpause() external { + UtilLib.onlyManagerRole(msg.sender, staderConfig); + + _unpause(); + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal override whenNotPaused { + super._beforeTokenTransfer(from, to, amount); + } + + function updateStaderConfig(address _staderConfig) external onlyRole(DEFAULT_ADMIN_ROLE) { + UtilLib.checkNonZeroAddress(_staderConfig); + staderConfig = IStaderConfig(_staderConfig); + emit UpdatedStaderConfig(_staderConfig); + } +}