diff --git a/src/modules/interfaces/IModuleAMO.sol b/src/modules/interfaces/IModuleAMO.sol index f8afe46..66aedc6 100644 --- a/src/modules/interfaces/IModuleAMO.sol +++ b/src/modules/interfaces/IModuleAMO.sol @@ -20,4 +20,7 @@ interface IModuleAMO { // Tracks earned amount per user function earned(address account) external view returns (uint256); + + // amount the user has staked in the AMO + function stakedAmount(address account) external view returns (uint256); } diff --git a/src/modules/liquityModule/LiquityDepositModule.sol b/src/modules/liquityModule/LiquityDepositModule.sol index 540f0fb..7887b4e 100644 --- a/src/modules/liquityModule/LiquityDepositModule.sol +++ b/src/modules/liquityModule/LiquityDepositModule.sol @@ -1,89 +1,75 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity ^0.8.13; - -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import "../interfaces/IModuleAMO.sol"; -import "./LiquityModuleAMO.sol"; -import "@modules/stablecoinDepositModule/StablecoinDepositModuleBase.sol"; - -/// @title LiquityDepositModule -/// @author Ekonomia: https://github.com/ekonomia-tech -/// @notice Accepts LUSD 1:1 and uses Liquity StabilityPool for AMO -contract LiquityDepositModule is StablecoinDepositModuleBase { - using SafeERC20 for IERC20Metadata; - - /// Errors - error CannotDepositZero(); - error CannotRedeemZeroTokens(); - - /// Events - event Deposited(address indexed depositor, uint256 depositAmount, uint256 phoMinted); - event Redeemed(address indexed redeemer, uint256 redeemAmount); - - /// State vars - address public liquityModuleAMO; - address public stakingToken = 0x5f98805A4E8be255a32880FDeC7F6728C6568bA0; // LUSD - address rewardToken = 0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D; // LQTY - - IStabilityPool public stabilityPool = IStabilityPool(0x66017D22b0f8556afDd19FC67041899Eb65a21bb); - - /// Constructor - constructor(address _moduleManager, address _stablecoin, address _pho) - StablecoinDepositModuleBase(_moduleManager, _stablecoin, _pho) - { - LiquityModuleAMO liquityModuleAMOInstance = new LiquityModuleAMO( - "LQTY-AMO", - "LQTYAMO", - stakingToken, - rewardToken, - msg.sender, - address(this), - _stablecoin - ); - - liquityModuleAMO = address(liquityModuleAMOInstance); - } - - /// @notice user deposits their stablecoin - /// @param depositAmount deposit amount (in stablecoin decimals) - function deposit(uint256 depositAmount) external override nonReentrant { - if (depositAmount == 0) { - revert CannotDepositZero(); - } - uint256 scaledDepositAmount = depositAmount; - - // Call AMO - which transfers LUSD from caller - IModuleAMO(liquityModuleAMO).stakeFor(msg.sender, depositAmount); - - issuedAmount[msg.sender] += scaledDepositAmount; - - // mint PHO - moduleManager.mintPHO(msg.sender, scaledDepositAmount); - - emit Deposited(msg.sender, depositAmount, scaledDepositAmount); - } - - /// @notice user redeems PHO for LUSD - function redeem() external nonReentrant { - uint256 redeemAmount = issuedAmount[msg.sender]; - if (redeemAmount == 0) { - revert CannotRedeemZeroTokens(); - } - - issuedAmount[msg.sender] -= redeemAmount; - - // burn PHO - moduleManager.burnPHO(msg.sender, redeemAmount); - - // scale if decimals < 18 - uint256 scaledRedeemAmount = redeemAmount; - scaledRedeemAmount = redeemAmount / (10 ** (18 - stablecoinDecimals)); - - // Note: Always a full withdrawal - IModuleAMO(liquityModuleAMO).withdrawAllFor(msg.sender); - - emit Redeemed(msg.sender, redeemAmount); - } -} +// // SPDX-License-Identifier: GPL-3.0-or-later + +// pragma solidity ^0.8.13; + +// import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +// import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +// import "../interfaces/IModuleAMO.sol"; +// import "./LiquityModuleAMO.sol"; +// import "@modules/stablecoinDepositModule/StablecoinDepositModuleBase.sol"; + +// /// @title LiquityDepositModule +// /// @author Ekonomia: https://github.com/ekonomia-tech +// /// @notice Accepts LUSD 1:1 and uses Liquity StabilityPool for AMO +// contract LiquityDepositModule is StablecoinDepositModuleBase { +// using SafeERC20 for IERC20Metadata; + +// /// Errors +// error CannotDepositZero(); +// error CannotRedeemZeroTokens(); + +// /// Events +// event Deposited(address indexed depositor, uint256 depositAmount, uint256 phoMinted); +// event Redeemed(address indexed redeemer, uint256 redeemAmount); + +// /// State vars +// IModuleAMO public liquityModuleAMO; + +// /// Constructor +// constructor( +// address _moduleManager, +// address _stablecoin, +// address _pho, +// address _stakingToken, +// address _rewardToken +// ) StablecoinDepositModuleBase(_moduleManager, _stablecoin, _pho) { +// liquityModuleAMO = new LiquityModuleAMO( +// "Photon Liquity AMO", +// "LQTY-AMO", +// _stakingToken, +// _rewardToken, +// msg.sender, +// address(this), +// _stablecoin +// ); +// } + +// /// @notice user deposits their stablecoin +// /// @param depositAmount deposit amount (in stablecoin decimals) +// function deposit(uint256 depositAmount) external override nonReentrant { +// if (depositAmount == 0) { +// revert CannotDepositZero(); +// } +// // Call AMO - which transfers LUSD from caller +// liquityModuleAMO.stakeFor(msg.sender, depositAmount); +// issuedAmount[msg.sender] += depositAmount; +// moduleManager.mintPHO(msg.sender, depositAmount); + +// // pho minted == depositAmount, since 1 to 1 with LUSD +// emit Deposited(msg.sender, depositAmount, depositAmount); +// } + +// /// @notice user redeems PHO for LUSD +// function redeem() external nonReentrant { +// uint256 redeemAmount = issuedAmount[msg.sender]; +// if (redeemAmount == 0) { +// revert CannotRedeemZeroTokens(); +// } +// issuedAmount[msg.sender] -= redeemAmount; +// moduleManager.burnPHO(msg.sender, redeemAmount); + +// // Note: Always a full withdrawal +// liquityModuleAMO.withdrawAllFor(msg.sender); +// emit Redeemed(msg.sender, redeemAmount); +// } +// } diff --git a/src/modules/liquityModule/LiquityModuleAMO.sol b/src/modules/liquityModule/LiquityModuleAMO.sol index 1318886..4588009 100644 --- a/src/modules/liquityModule/LiquityModuleAMO.sol +++ b/src/modules/liquityModule/LiquityModuleAMO.sol @@ -1,220 +1,220 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -pragma solidity ^0.8.13; - -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "../interfaces/IModuleAMO.sol"; -import "./interfaces/IStabilityPool.sol"; -import "forge-std/console2.sol"; - -/// @title LiquityModuleAMO -/// @notice Liquity Module AMO -/// @author Ekonomia: https://github.com/Ekonomia -contract LiquityModuleAMO is IModuleAMO, ERC20 { - using SafeMath for uint256; - using SafeERC20 for IERC20; - - // Errors - error CannotReceiveZeroMPT(); - error ZeroAddressDetected(); - error CannotStakeZero(); - error CannotWithdrawMoreThanDeposited(); - - /// State vars - address public rewardToken; - address public stakingToken; - address public operator; - address public module; - uint256 private _totalDeposits; - uint256 private _totalShares; - uint256 private _totalRewards; // rewards in LQTY - uint256 private _totalEthRewards; // rewards in ETH - - mapping(address => uint256) public depositedAmount; // MPL deposited - mapping(address => uint256) public stakedAmount; // MPL staked - mapping(address => uint256) public claimedRewards; // rewards claimed - mapping(address => uint256) private _shares; - - // Needed for interactions w/ external contracts - address public depositToken; - IERC20 public lqty = IERC20(0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D); - IStabilityPool public stabilityPool = IStabilityPool(0x66017D22b0f8556afDd19FC67041899Eb65a21bb); - - // Events - event RewardAdded(uint256 reward); - event Staked(address indexed user, uint256 amount, uint256 shares); - event Withdrawn(address indexed user, uint256 amount, uint256 shares); - event RewardPaid(address indexed user, uint256 reward); - event LiquityRewardsReceived(uint256 totalRewards); - - modifier onlyOperator() { - require(msg.sender == address(operator), "Only Operator"); - _; - } - - modifier onlyModule() { - require(msg.sender == address(module), "Only Module"); - _; - } - - /// Constructor - constructor( - string memory _name, - string memory _symbol, - address _stakingToken, - address _rewardToken, - address _operator, - address _module, - address _depositToken - ) ERC20(_name, _symbol) { - if ( - _stakingToken == address(0) || _rewardToken == address(0) || _operator == address(0) - || _module == address(0) || _depositToken == address(0) - ) { - revert ZeroAddressDetected(); - } - stakingToken = _stakingToken; - rewardToken = _rewardToken; - operator = _operator; - module = _module; - depositToken = _depositToken; - } - - /// @notice Get total shares - function totalShares() public view returns (uint256) { - return _totalShares; - } - - /// @notice Get shares of account - /// @param account Account - function sharesOf(address account) public view returns (uint256) { - return _shares[account]; - } - - /// @notice Get earned amount by account (total available - claimed) - /// @param account Account - function earned(address account) public view returns (uint256) { - uint256 ts = totalSupply(); - if (ts == 0) { - return 0; - } - uint256 earnedRewards = (balanceOf(account) * _totalRewards) / ts - claimedRewards[account]; - return earnedRewards; - } - - /// @notice Convert a deposit/withdraw amount into shares - function _toShares(uint256 amount) private view returns (uint256) { - if (_totalShares == 0) { - return amount; - } - return (amount * _totalShares) / _totalDeposits; - } - - /// @notice Tracks shares for deposits - function _trackDepositShares(address account, uint256 amount) private returns (uint256) { - uint256 shares = _toShares(amount); - _shares[account] += shares; - _totalShares += shares; - _mint(account, shares); - return shares; - } - - /// @notice Tracks shares for withdrawals - function _trackWithdrawShares(address account) private returns (uint256) { - uint256 shares = _shares[account]; - _shares[account] = 0; - _totalShares -= shares; - _burn(account, shares); - return shares; - } - - /// @notice Stake for - /// @param account For - /// @param amount Amount - function stakeFor(address account, uint256 amount) public onlyModule returns (bool) { - if (amount == 0) { - revert CannotStakeZero(); - } - - // Get depositToken from user - IERC20(depositToken).safeTransferFrom(account, address(this), amount); - - stabilityPool.provideToSP(amount, address(0)); - - depositedAmount[account] += amount; - stakedAmount[account] += amount; - _totalDeposits += amount; - - uint256 shares = _trackDepositShares(account, amount); - emit Staked(account, amount, shares); - return true; - } - - /// @notice Withdraw - /// @param account Account - /// @param amount amount - function withdrawFor(address account, uint256 amount) public onlyModule returns (bool) { - uint256 depositAmount = depositedAmount[account]; - uint256 stakedPoolTokenAmount = stakedAmount[account]; - if (amount > depositAmount) { - revert CannotWithdrawMoreThanDeposited(); - } - depositedAmount[account] -= depositAmount; - stakedAmount[account] -= stakedPoolTokenAmount; - - // Withdraw from pool - stabilityPool.withdrawFromSP(amount); - - uint256 shares = _trackWithdrawShares(account); - _totalDeposits -= depositAmount; - - // Transfer depositToken to caller - IERC20(depositToken).transfer(account, depositAmount); - emit Withdrawn(account, amount, shares); - return true; - } - - /// @notice Withdraw all for - /// @param account Account - function withdrawAllFor(address account) external returns (bool) { - return withdrawFor(account, depositedAmount[account]); - } - - /// @notice gets reward from Liquity - function getRewardLiquity() external onlyOperator returns (uint256) { - uint256 liquityBalanceBefore = lqty.balanceOf(address(this)); - uint256 ethBalanceBefore = address(this).balance; - // Withdraw minimum amount to force LQTY and ETH to be claimed - if (stabilityPool.getCompoundedLUSDDeposit(address(this)) > 0) { - stabilityPool.withdrawFromSP(0); - } - uint256 liquityBalanceAfter = lqty.balanceOf(address(this)); - uint256 ethBalanceAfter = address(this).balance; - _totalRewards = liquityBalanceAfter - liquityBalanceBefore; - emit LiquityRewardsReceived(_totalRewards); - _totalEthRewards = ethBalanceAfter - ethBalanceBefore; - return _totalRewards; - } - - /// @notice Get reward - /// @param account Account - function getReward(address account) public returns (bool) { - uint256 reward = earned(account); - if (reward > 0) { - claimedRewards[account] += reward; - IERC20(rewardToken).safeTransfer(account, reward); - emit RewardPaid(account, reward); - } - return true; - } - - /// @notice Get reward for msg.sender - function getReward() external returns (bool) { - getReward(msg.sender); - return true; - } -} +// // SPDX-License-Identifier: GPL-3.0-or-later + +// pragma solidity ^0.8.13; + +// import "@openzeppelin/contracts/utils/math/SafeMath.sol"; +// import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +// import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// import "@openzeppelin/contracts/utils/Address.sol"; +// import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +// import "../interfaces/IModuleAMO.sol"; +// import "./interfaces/IStabilityPool.sol"; +// import "forge-std/console2.sol"; + +// /// @title LiquityModuleAMO +// /// @notice Liquity Module AMO +// /// @author Ekonomia: https://github.com/Ekonomia +// contract LiquityModuleAMO is IModuleAMO, ERC20 { +// using SafeMath for uint256; +// using SafeERC20 for IERC20; + +// // Errors +// error CannotReceiveZeroMPT(); +// error ZeroAddressDetected(); +// error CannotStakeZero(); +// error CannotWithdrawMoreThanDeposited(); + +// /// State vars +// address public rewardToken; +// address public stakingToken; +// address public operator; +// address public module; +// uint256 private _totalDeposits; +// uint256 private _totalShares; +// uint256 private _totalRewards; // rewards in LQTY +// uint256 private _totalEthRewards; // rewards in ETH + +// mapping(address => uint256) public depositedAmount; // MPL deposited +// mapping(address => uint256) public stakedAmount; // MPL staked +// mapping(address => uint256) public claimedRewards; // rewards claimed +// mapping(address => uint256) private _shares; + +// // Needed for interactions w/ external contracts +// address public depositToken; +// IERC20 public lqty = IERC20(0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D); +// IStabilityPool public stabilityPool = IStabilityPool(0x66017D22b0f8556afDd19FC67041899Eb65a21bb); + +// // Events +// event RewardAdded(uint256 reward); +// event Staked(address indexed user, uint256 amount, uint256 shares); +// event Withdrawn(address indexed user, uint256 amount, uint256 shares); +// event RewardPaid(address indexed user, uint256 reward); +// event LiquityRewardsReceived(uint256 totalRewards); + +// modifier onlyOperator() { +// require(msg.sender == address(operator), "Only Operator"); +// _; +// } + +// modifier onlyModule() { +// require(msg.sender == address(module), "Only Module"); +// _; +// } + +// /// Constructor +// constructor( +// string memory _name, +// string memory _symbol, +// address _stakingToken, +// address _rewardToken, +// address _operator, +// address _module, +// address _depositToken +// ) ERC20(_name, _symbol) { +// if ( +// _stakingToken == address(0) || _rewardToken == address(0) || _operator == address(0) +// || _module == address(0) || _depositToken == address(0) +// ) { +// revert ZeroAddressDetected(); +// } +// stakingToken = _stakingToken; +// rewardToken = _rewardToken; +// operator = _operator; +// module = _module; +// depositToken = _depositToken; +// } + +// /// @notice Get total shares +// function totalShares() public view returns (uint256) { +// return _totalShares; +// } + +// /// @notice Get shares of account +// /// @param account Account +// function sharesOf(address account) public view returns (uint256) { +// return _shares[account]; +// } + +// /// @notice Get earned amount by account (total available - claimed) +// /// @param account Account +// function earned(address account) public view returns (uint256) { +// uint256 ts = totalSupply(); +// if (ts == 0) { +// return 0; +// } +// uint256 earnedRewards = (balanceOf(account) * _totalRewards) / ts - claimedRewards[account]; +// return earnedRewards; +// } + +// /// @notice Convert a deposit/withdraw amount into shares +// function _toShares(uint256 amount) private view returns (uint256) { +// if (_totalShares == 0) { +// return amount; +// } +// return (amount * _totalShares) / _totalDeposits; +// } + +// /// @notice Tracks shares for deposits +// function _trackDepositShares(address account, uint256 amount) private returns (uint256) { +// uint256 shares = _toShares(amount); +// _shares[account] += shares; +// _totalShares += shares; +// _mint(account, shares); +// return shares; +// } + +// /// @notice Tracks shares for withdrawals +// function _trackWithdrawShares(address account) private returns (uint256) { +// uint256 shares = _shares[account]; +// _shares[account] = 0; +// _totalShares -= shares; +// _burn(account, shares); +// return shares; +// } + +// /// @notice Stake for +// /// @param account For +// /// @param amount Amount +// function stakeFor(address account, uint256 amount) public onlyModule returns (bool) { +// if (amount == 0) { +// revert CannotStakeZero(); +// } + +// // Get depositToken from user +// IERC20(depositToken).safeTransferFrom(account, address(this), amount); + +// stabilityPool.provideToSP(amount, address(0)); + +// depositedAmount[account] += amount; +// stakedAmount[account] += amount; +// _totalDeposits += amount; + +// uint256 shares = _trackDepositShares(account, amount); +// emit Staked(account, amount, shares); +// return true; +// } + +// /// @notice Withdraw +// /// @param account Account +// /// @param amount amount +// function withdrawFor(address account, uint256 amount) public onlyModule returns (bool) { +// uint256 depositAmount = depositedAmount[account]; +// uint256 stakedPoolTokenAmount = stakedAmount[account]; +// if (amount > depositAmount) { +// revert CannotWithdrawMoreThanDeposited(); +// } +// depositedAmount[account] -= depositAmount; +// stakedAmount[account] -= stakedPoolTokenAmount; + +// // Withdraw from pool +// stabilityPool.withdrawFromSP(amount); + +// uint256 shares = _trackWithdrawShares(account); +// _totalDeposits -= depositAmount; + +// // Transfer depositToken to caller +// IERC20(depositToken).transfer(account, depositAmount); +// emit Withdrawn(account, amount, shares); +// return true; +// } + +// /// @notice Withdraw all for +// /// @param account Account +// function withdrawAllFor(address account) external returns (bool) { +// return withdrawFor(account, depositedAmount[account]); +// } + +// /// @notice gets reward from Liquity +// function getRewardLiquity() external onlyOperator returns (uint256) { +// uint256 liquityBalanceBefore = lqty.balanceOf(address(this)); +// uint256 ethBalanceBefore = address(this).balance; +// // Withdraw minimum amount to force LQTY and ETH to be claimed +// if (stabilityPool.getCompoundedLUSDDeposit(address(this)) > 0) { +// stabilityPool.withdrawFromSP(0); +// } +// uint256 liquityBalanceAfter = lqty.balanceOf(address(this)); +// uint256 ethBalanceAfter = address(this).balance; +// _totalRewards = liquityBalanceAfter - liquityBalanceBefore; +// emit LiquityRewardsReceived(_totalRewards); +// _totalEthRewards = ethBalanceAfter - ethBalanceBefore; +// return _totalRewards; +// } + +// /// @notice Get reward +// /// @param account Account +// function getReward(address account) public returns (bool) { +// uint256 reward = earned(account); +// if (reward > 0) { +// claimedRewards[account] += reward; +// IERC20(rewardToken).safeTransfer(account, reward); +// emit RewardPaid(account, reward); +// } +// return true; +// } + +// /// @notice Get reward for msg.sender +// function getReward() external returns (bool) { +// getReward(msg.sender); +// return true; +// } +// } diff --git a/src/modules/liquityModule/LiquityStabilityPoolModule.sol b/src/modules/liquityModule/LiquityStabilityPoolModule.sol new file mode 100644 index 0000000..26dbda4 --- /dev/null +++ b/src/modules/liquityModule/LiquityStabilityPoolModule.sol @@ -0,0 +1,159 @@ +pragma solidity ^0.8.13; + +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; +import "./dependencies/CropJoinAdapter.sol"; +import "@openzeppelin/contracts/utils/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./interfaces/IStabilityPool.sol"; + +// INSPIRED BY B.Protocol - https://etherscan.deth.net/address/0x00FF66AB8699AAfa050EE5EF5041D1503aa0849a +contract LiquityStabilityPoolModule is CropJoinAdapter { + using SafeMath for uint256; + + AggregatorV3Interface public immutable priceAggregator; + AggregatorV3Interface public immutable lusd2UsdPriceAggregator; + IERC20 public immutable LUSD; + IStabilityPool public immutable SP; + + // address payable public immutable feePool; + // uint public constant MAX_FEE = 100; // 1% + // uint public fee = 0; // fee in bps + // uint public A = 20; + // uint public constant MIN_A = 20; + // uint public constant MAX_A = 200; + + // uint public immutable maxDiscount; // max discount in bips + + address public immutable frontEndTag; + + uint256 public constant PRECISION = 1e18; + + // event ParamsSet(uint A, uint fee); + event UserDeposit(address indexed user, uint256 lusdAmount, uint256 numShares); + event UserWithdraw( + address indexed user, uint256 lusdAmount, uint256 ethAmount, uint256 numShares + ); + + constructor( + address _priceAggregator, + address _lusd2UsdPriceAggregator, + address payable _SP, + address _LUSD, + address _LQTY, + uint256 _maxDiscount, + address payable _feePool, + address _fronEndTag + ) public CropJoinAdapter(_LQTY) { + priceAggregator = AggregatorV3Interface(_priceAggregator); + lusd2UsdPriceAggregator = AggregatorV3Interface(_lusd2UsdPriceAggregator); + LUSD = IERC20(_LUSD); + SP = IStabilityPool(_SP); + + // feePool = _feePool; + // maxDiscount = _maxDiscount; + frontEndTag = _fronEndTag; // DK - might not need TODO + } + + // DK - don't think we need this + // function setParams(uint _A, uint _fee) external onlyOwner { + // require(_fee <= MAX_FEE, "setParams: fee is too big"); + // require(_A >= MIN_A, "setParams: A too small"); + // require(_A <= MAX_A, "setParams: A too big"); + + // fee = _fee; + // A = _A; + + // emit ParamsSet(_A, _fee); + // } + + function fetchPrice() public view returns (uint256) { + uint256 chainlinkDecimals; + uint256 chainlinkLatestAnswer; + uint256 chainlinkTimestamp; + + // First, try to get current decimal precision: + try priceAggregator.decimals() returns (uint8 decimals) { + // If call to Chainlink succeeds, record the current decimal precision + chainlinkDecimals = decimals; + } catch { + // If call to Chainlink aggregator reverts, return a zero response with success = false + return 0; + } + + // Secondly, try to get latest price data: + try priceAggregator.latestRoundData() returns ( + uint80, /* roundId */ + int256 answer, + uint256, /* startedAt */ + uint256 timestamp, + uint80 /* answeredInRound */ + ) { + // If call to Chainlink succeeds, return the response and success = true + chainlinkLatestAnswer = uint256(answer); + chainlinkTimestamp = timestamp; + } catch { + // If call to Chainlink aggregator reverts, return a zero response with success = false + return 0; + } + + if (chainlinkTimestamp + 1 hours < block.timestamp) return 0; // price is down + + uint256 chainlinkFactor = 10 ** chainlinkDecimals; + return chainlinkLatestAnswer.mul(PRECISION) / chainlinkFactor; + } + + // NOTE - instead of stakeFor() right now - DK.... unsure if it should be 2 separate contracts, AMO and Module + function deposit(uint256 lusdAmount) external { + // update share + uint256 lusdValue = SP.getCompoundedLUSDDeposit(address(this)); + uint256 ethValue = SP.getDepositorETHGain(address(this)).add(address(this).balance); + + uint256 price = fetchPrice(); + require(ethValue == 0 || price > 0, "deposit: chainlink is down"); + + uint256 totalValue = lusdValue.add(ethValue.mul(price) / PRECISION); + + // this is in theory not reachable. if it is, better halt deposits + // the condition is equivalent to: (totalValue = 0) ==> (total = 0) + require(totalValue > 0 || total == 0, "deposit: system is rekt"); + + uint256 newShare = PRECISION; + if (total > 0) newShare = total.mul(lusdAmount) / totalValue; + + // deposit + require( + LUSD.transferFrom(msg.sender, address(this), lusdAmount), "deposit: transferFrom failed" + ); + SP.provideToSP(lusdAmount, frontEndTag); + + // update LP token + mint(msg.sender, newShare); + + emit UserDeposit(msg.sender, lusdAmount, newShare); + } + + // NOTE - instead of withdrawFor() right now - DK.... unsure if it should be 2 separate contracts, AMO and Module + function withdraw(uint256 numShares) external { + uint256 lusdValue = SP.getCompoundedLUSDDeposit(address(this)); + uint256 ethValue = SP.getDepositorETHGain(address(this)).add(address(this).balance); + + uint256 lusdAmount = lusdValue.mul(numShares).div(total); + uint256 ethAmount = ethValue.mul(numShares).div(total); + + // this withdraws lusd, lqty, and eth. + SP.withdrawFromSP(lusdAmount); + + // update LP token + // this function has LQTY sent if you look at CropJoin.sol code + burn(msg.sender, numShares); + + // send lusd and eth + if (lusdAmount > 0) LUSD.transfer(msg.sender, lusdAmount); + if (ethAmount > 0) { + (bool success,) = msg.sender.call{value: ethAmount}(""); // re-entry is fine here + require(success, "withdraw: sending ETH failed"); + } + + emit UserWithdraw(msg.sender, lusdAmount, ethAmount, numShares); + } +} diff --git a/src/modules/liquityModule/dependencies/CropJoin.sol b/src/modules/liquityModule/dependencies/CropJoin.sol new file mode 100644 index 0000000..ecc0fef --- /dev/null +++ b/src/modules/liquityModule/dependencies/CropJoin.sol @@ -0,0 +1,173 @@ + + +// File contracts/B.Protocol/crop.sol + + +// Copyright (C) 2021 Dai Foundation +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pragma solidity >=0.6.11 <0.9.0; + +interface VatLike { + function urns(bytes32, address) external view returns (uint256, uint256); + function gem(bytes32, address) external view returns (uint256); + function slip(bytes32, address, int256) external; +} + +interface ERC20 { + function balanceOf(address owner) external view returns (uint256); + function transfer(address dst, uint256 amount) external returns (bool); + function transferFrom(address src, address dst, uint256 amount) external returns (bool); + function approve(address spender, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function decimals() external returns (uint8); +} + +// receives tokens and shares them among holders +contract CropJoin { + + VatLike public immutable vat; // cdp engine + bytes32 public immutable ilk; // collateral type + ERC20 public immutable gem; // collateral token + uint256 public immutable dec; // gem decimals + ERC20 public immutable bonus; // rewards token + + uint256 public share; // crops per gem [ray] + uint256 public total; // total gems [wad] + uint256 public stock; // crop balance [wad] + + mapping (address => uint256) public crops; // crops per user [wad] + mapping (address => uint256) public stake; // gems per user [wad] + + uint256 immutable internal to18ConversionFactor; + uint256 immutable internal toGemConversionFactor; + + // --- Events --- + event Join(uint256 val); + event Exit(uint256 val); + event Flee(); + event Tack(address indexed src, address indexed dst, uint256 wad); + + constructor(address vat_, bytes32 ilk_, address gem_, address bonus_) public { + vat = VatLike(vat_); + ilk = ilk_; + gem = ERC20(gem_); + uint256 dec_ = ERC20(gem_).decimals(); + require(dec_ <= 18); + dec = dec_; + to18ConversionFactor = 10 ** (18 - dec_); + toGemConversionFactor = 10 ** dec_; + + bonus = ERC20(bonus_); + } + + function add(uint256 x, uint256 y) public pure returns (uint256 z) { + require((z = x + y) >= x, "ds-math-add-overflow"); + } + function sub(uint256 x, uint256 y) public pure returns (uint256 z) { + require((z = x - y) <= x, "ds-math-sub-underflow"); + } + function mul(uint256 x, uint256 y) public pure returns (uint256 z) { + require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); + } + function divup(uint256 x, uint256 y) internal pure returns (uint256 z) { + z = add(x, sub(y, 1)) / y; + } + uint256 constant WAD = 10 ** 18; + function wmul(uint256 x, uint256 y) public pure returns (uint256 z) { + z = mul(x, y) / WAD; + } + function wdiv(uint256 x, uint256 y) public pure returns (uint256 z) { + z = mul(x, WAD) / y; + } + function wdivup(uint256 x, uint256 y) public pure returns (uint256 z) { + z = divup(mul(x, WAD), y); + } + uint256 constant RAY = 10 ** 27; + function rmul(uint256 x, uint256 y) public pure returns (uint256 z) { + z = mul(x, y) / RAY; + } + function rmulup(uint256 x, uint256 y) public pure returns (uint256 z) { + z = divup(mul(x, y), RAY); + } + function rdiv(uint256 x, uint256 y) public pure returns (uint256 z) { + z = mul(x, RAY) / y; + } + + // Net Asset Valuation [wad] + function nav() public virtual returns (uint256) { + uint256 _nav = gem.balanceOf(address(this)); + return mul(_nav, to18ConversionFactor); + } + + // Net Assets per Share [wad] + function nps() public returns (uint256) { + if (total == 0) return WAD; + else return wdiv(nav(), total); + } + + function crop() internal virtual returns (uint256) { + return sub(bonus.balanceOf(address(this)), stock); + } + + function harvest(address from, address to) internal { + if (total > 0) share = add(share, rdiv(crop(), total)); + + uint256 last = crops[from]; + uint256 curr = rmul(stake[from], share); + if (curr > last) require(bonus.transfer(to, curr - last)); + stock = bonus.balanceOf(address(this)); + } + + function join(address urn, uint256 val) internal virtual { + harvest(urn, urn); + if (val > 0) { + uint256 wad = wdiv(mul(val, to18ConversionFactor), nps()); + + // Overflow check for int256(wad) cast below + // Also enforces a non-zero wad + require(int256(wad) > 0); + + require(gem.transferFrom(msg.sender, address(this), val)); + vat.slip(ilk, urn, int256(wad)); + + total = add(total, wad); + stake[urn] = add(stake[urn], wad); + } + crops[urn] = rmulup(stake[urn], share); + emit Join(val); + } + + function exit(address guy, uint256 val) internal virtual { + harvest(msg.sender, guy); + if (val > 0) { + uint256 wad = wdivup(mul(val, to18ConversionFactor), nps()); + + // Overflow check for int256(wad) cast below + // Also enforces a non-zero wad + require(int256(wad) > 0); + + require(gem.transfer(guy, val)); + vat.slip(ilk, msg.sender, -int256(wad)); + + total = sub(total, wad); + stake[msg.sender] = sub(stake[msg.sender], wad); + } + crops[msg.sender] = rmulup(stake[msg.sender], share); + emit Exit(val); + } +} + + diff --git a/src/modules/liquityModule/dependencies/CropJoinAdapter.sol b/src/modules/liquityModule/dependencies/CropJoinAdapter.sol new file mode 100644 index 0000000..565f84f --- /dev/null +++ b/src/modules/liquityModule/dependencies/CropJoinAdapter.sol @@ -0,0 +1,59 @@ +// File contracts/B.Protocol/CropJoinAdapter.sol +pragma solidity >=0.6.11 <0.9.0; + +import "./CropJoin.sol"; + +// NOTE! - this is not an ERC20 token. transfer is not supported. +contract CropJoinAdapter is CropJoin { + string public constant name = "B.AMM LUSD-ETH"; + string public constant symbol = "LUSDETH"; + uint256 public constant decimals = 18; + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + + constructor(address _lqty) + public + CropJoin(address(new Dummy()), "B.AMM", address(new DummyGem()), _lqty) + {} + + // adapter to cropjoin + function nav() public override returns (uint256) { + return total; + } + + function totalSupply() public view returns (uint256) { + return total; + } + + function balanceOf(address owner) public view returns (uint256 balance) { + balance = stake[owner]; + } + + function mint(address to, uint256 value) internal virtual { + join(to, value); + emit Transfer(address(0), to, value); + } + + function burn(address owner, uint256 value) internal virtual { + exit(owner, value); + emit Transfer(owner, address(0), value); + } +} + +contract Dummy { + fallback() external {} +} + +contract DummyGem is Dummy { + function transfer(address, uint256) external pure returns (bool) { + return true; + } + + function transferFrom(address, address, uint256) external pure returns (bool) { + return true; + } + + function decimals() external pure returns (uint256) { + return 18; + } +} diff --git a/test/modules/liquityModule/LiquityDepositModule.t.sol b/test/modules/liquityModule/LiquityDepositModule.t.sol index d9c13bc..6b1b577 100644 --- a/test/modules/liquityModule/LiquityDepositModule.t.sol +++ b/test/modules/liquityModule/LiquityDepositModule.t.sol @@ -1,586 +1,609 @@ -// SPDX-License-Identifier: UNLICENSED - -pragma solidity ^0.8.13; - -import "../../BaseSetup.t.sol"; -import "@modules/liquityModule/LiquityDepositModule.sol"; -import "@modules/liquityModule/LiquityModuleAMO.sol"; -import "@modules/interfaces/IModuleAMO.sol"; - -contract LiquityDepositModuleTest is BaseSetup { - /// Errors - error ZeroAddressDetected(); - error CannotDepositZero(); - error CannotRedeemZeroTokens(); - - /// Events - event Deposited(address indexed depositor, uint256 depositAmount, uint256 phoMinted); - event Redeemed(address indexed redeemer, uint256 redeemAmount); - - // Track balance for stablecoins and PHO - struct LiquityBalance { - uint256 userStablecoinBalance; - uint256 moduleStablecoinBalance; - uint256 userPHOBalance; - uint256 userIssuedAmount; - uint256 userStakedAmount; - uint256 totalPHOSupply; - uint256 liquityPoolDeposits; - uint256 liquityPoolDepositorLQTYGain; - } - - struct RewardsVars { - uint256 rewardPerToken; - uint256 userRewardPerTokenPaid; - uint256 lastUpdateTime; - uint256 lastTimeRewardApplicable; - uint256 periodFinish; - uint256 blockTimestamp; - } - - struct SharesVars { - uint256 shares; - uint256 earned; - uint256 totalShares; - } - - // Module - LiquityDepositModule public liquityDepositModule; - - // Global - uint256 public constant mplGlobalLpCooldownPeriod = 864000; - uint256 public constant mplGlobalLpWithdrawWindow = 172800; - uint256 public moduleDelay; - address public stakingToken = 0x5f98805A4E8be255a32880FDeC7F6728C6568bA0; // LUSD - address rewardToken = 0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D; // LQTY - - function setUp() public { - // Liquity module - vm.prank(owner); - liquityDepositModule = new LiquityDepositModule( - address(moduleManager), - address(lusd), - address(pho) - ); - - // Add module to ModuleManager - vm.startPrank(PHOGovernance); - moduleManager.addModule(address(liquityDepositModule)); - vm.stopPrank(); - - // Increase PHO ceilings for modules - vm.startPrank(TONGovernance); - moduleManager.setPHOCeilingForModule(address(liquityDepositModule), ONE_MILLION_D18); - vm.stopPrank(); - - moduleDelay = moduleManager.moduleDelay(); - - vm.warp(block.timestamp + moduleDelay); - - moduleManager.executeCeilingUpdate(address(liquityDepositModule)); - - // Fund users 1 & 2 with LUSD - vm.startPrank(lusdWhale); - lusd.transfer(user1, TEN_THOUSAND_D18); - lusd.transfer(user2, TEN_THOUSAND_D18); - - // Also fund module with some LUSD - lusd.approve(liquityDepositModule.liquityModuleAMO(), TEN_THOUSAND_D18); - lusd.transfer(liquityDepositModule.liquityModuleAMO(), TEN_THOUSAND_D18); - vm.stopPrank(); - - // Mint PHO to users 1 & 2 - vm.prank(address(moduleManager)); - kernel.mintPHO(address(user1), ONE_HUNDRED_D18); - vm.prank(address(moduleManager)); - kernel.mintPHO(address(user2), ONE_HUNDRED_D18); - - // Approve sending LUSD to LiquityDeposit contract - user 1 - vm.startPrank(user1); - lusd.approve(address(liquityDepositModule), TEN_THOUSAND_D18); - - // Do same for liquity AMO - lusd.approve(address(liquityDepositModule.liquityModuleAMO()), TEN_THOUSAND_D18); - - // Allow sending PHO (redemptions) to LiquityDeposit contracts - pho.approve(address(liquityDepositModule), TEN_THOUSAND_D18); - - // Approve PHO burnFrom() via moduleManager calling kernel - pho.approve(address(kernel), ONE_MILLION_D18); - vm.stopPrank(); - - // Approve sending LUSD to LiquityDeposit contract - user 2 - vm.startPrank(user2); - lusd.approve(address(liquityDepositModule), TEN_THOUSAND_D18); - - // Do same for liquity AMO - lusd.approve(address(liquityDepositModule.liquityModuleAMO()), TEN_THOUSAND_D18); - - // Allow sending PHO (redemptions) to LiquityDeposit contracts - pho.approve(address(liquityDepositModule), TEN_THOUSAND_D18); - - // Approve PHO burnFrom() via moduleManager calling kernel - pho.approve(address(kernel), ONE_MILLION_D18); - vm.stopPrank(); - - // Allow sending PHO (redemptions) to LiquityDeposit contracts - pho.approve(address(liquityDepositModule), TEN_THOUSAND_D18); - - // Approve PHO burnFrom() via moduleManager calling kernel - pho.approve(address(kernel), ONE_MILLION_D18); - vm.stopPrank(); - } - - // Cannot set any 0 addresses for constructor - function testCannotMakeLiquityDepositModuleWithZeroAddress() public { - vm.startPrank(user1); - - vm.expectRevert(abi.encodeWithSelector(ZeroAddressDetected.selector)); - liquityDepositModule = new LiquityDepositModule( - address(0), - address(lusd), - address(pho) - ); - - vm.expectRevert(abi.encodeWithSelector(ZeroAddressDetected.selector)); - liquityDepositModule = new LiquityDepositModule( - address(moduleManager), - address(0), - address(pho) - ); - - vm.expectRevert(abi.encodeWithSelector(ZeroAddressDetected.selector)); - liquityDepositModule = new LiquityDepositModule( - address(moduleManager), - address(lusd), - address(0) - ); - - vm.stopPrank(); - } - - // Cannot deposit 0 - function testCannotDepositZero() public { - uint256 depositAmount = 0; - vm.expectRevert(abi.encodeWithSelector(CannotDepositZero.selector)); - vm.prank(user1); - liquityDepositModule.deposit(depositAmount); - } - - // Test basic deposit - function testDepositLiquityModule() public { - uint256 depositAmount = ONE_HUNDRED_D18; - _testDepositAnyModule(depositAmount, liquityDepositModule); - } - - // Helper function to test Liquity deposit from any module - function _testDepositAnyModule(uint256 _depositAmount, LiquityDepositModule _module) public { - // Convert expected issue amount based on stablecoin decimals - uint256 scaledDepositAmount = _depositAmount; - - uint256 expectedIssuedAmount = scaledDepositAmount; - - address moduleRewardPool = _module.liquityModuleAMO(); - - // LUSD and PHO balances before - LiquityBalance memory before; - before.userStablecoinBalance = lusd.balanceOf(address(user1)); - before.moduleStablecoinBalance = lusd.balanceOf(address(_module)); - before.userPHOBalance = pho.balanceOf(user1); - before.userIssuedAmount = _module.issuedAmount(user1); - before.userStakedAmount = LiquityModuleAMO(moduleRewardPool).stakedAmount(user1); - before.totalPHOSupply = pho.totalSupply(); - - // Check stability pool - before.liquityPoolDeposits = - _module.stabilityPool().getCompoundedLUSDDeposit(moduleRewardPool); - before.liquityPoolDepositorLQTYGain = - _module.stabilityPool().getDepositorLQTYGain(moduleRewardPool); - - // Deposit - vm.expectEmit(true, true, true, true); - emit Deposited(user1, _depositAmount, expectedIssuedAmount); - vm.prank(user1); - _module.deposit(_depositAmount); - - // DepositToken and PHO balances after - LiquityBalance memory aft; - aft.userStablecoinBalance = lusd.balanceOf(address(user1)); - aft.moduleStablecoinBalance = lusd.balanceOf(address(_module)); - aft.userPHOBalance = pho.balanceOf(user1); - aft.userIssuedAmount = _module.issuedAmount(user1); - aft.userStakedAmount = LiquityModuleAMO(moduleRewardPool).stakedAmount(user1); - aft.totalPHOSupply = pho.totalSupply(); - - // Check stability pool - aft.liquityPoolDeposits = _module.stabilityPool().getCompoundedLUSDDeposit(moduleRewardPool); - aft.liquityPoolDepositorLQTYGain = - _module.stabilityPool().getDepositorLQTYGain(moduleRewardPool); - - // User balance - depositToken down and PHO up - assertEq(aft.userStablecoinBalance + _depositAmount, before.userStablecoinBalance); - assertEq(aft.userPHOBalance, before.userPHOBalance + expectedIssuedAmount); - - // Deposit module balance - depositToken same (goes to Liquity pool) - assertEq(aft.moduleStablecoinBalance, before.moduleStablecoinBalance); - - // Check issued amount goes up - assertEq(aft.userIssuedAmount, before.userIssuedAmount + expectedIssuedAmount); - - // Check staked amount goes up - assertEq(aft.userStakedAmount, before.userStakedAmount + scaledDepositAmount); - - // Check PHO total supply goes up - assertEq(aft.totalPHOSupply, before.totalPHOSupply + expectedIssuedAmount); - - // Check Liquity pool balance goes up - assertEq(aft.liquityPoolDeposits, before.liquityPoolDeposits + scaledDepositAmount); - - // Check Liquity pool LQTY gain is same as before - assertEq(aft.liquityPoolDepositorLQTYGain, before.liquityPoolDepositorLQTYGain); - } - - // Cannot redeem 0 - function testCannotRedeemZero() public { - vm.expectRevert(abi.encodeWithSelector(CannotRedeemZeroTokens.selector)); - vm.prank(user1); - liquityDepositModule.redeem(); - } - - // Test Redeem - function testRedeemLiquityModule() public { - uint256 depositAmount = ONE_HUNDRED_D18; - uint256 redeemAmount = ONE_HUNDRED_D18; - uint256 withdrawTimestamp = block.timestamp + 10000; - _testDepositAnyModule(depositAmount, liquityDepositModule); - _testRedeemAnyModule(redeemAmount, liquityDepositModule, withdrawTimestamp); - } - - // Helper function to test Liquity redeem from any module - function _testRedeemAnyModule( - uint256 _redeemAmount, - LiquityDepositModule _module, - uint256 withdrawTimestamp - ) public { - // Convert expected issue amount based on stablecoin decimals - uint256 scaledRedeemAmount = _redeemAmount; - - uint256 expectedRedeemAmount = scaledRedeemAmount; - - address moduleRewardPool = _module.liquityModuleAMO(); - - // LUSD and PHO balances before - LiquityBalance memory before; - before.userStablecoinBalance = lusd.balanceOf(address(user1)); - before.moduleStablecoinBalance = lusd.balanceOf(address(_module)); - before.userPHOBalance = pho.balanceOf(user1); - before.userIssuedAmount = _module.issuedAmount(user1); - before.userStakedAmount = LiquityModuleAMO(moduleRewardPool).stakedAmount(user1); - before.totalPHOSupply = pho.totalSupply(); - - // Check stability pool - before.liquityPoolDeposits = - _module.stabilityPool().getCompoundedLUSDDeposit(moduleRewardPool); - before.liquityPoolDepositorLQTYGain = - _module.stabilityPool().getDepositorLQTYGain(moduleRewardPool); - - // Redeem - vm.warp(withdrawTimestamp); - vm.expectEmit(true, true, true, true); - emit Redeemed(user1, before.userIssuedAmount); - vm.prank(user1); - _module.redeem(); - - // DepositToken and PHO balances after - LiquityBalance memory aft; - aft.userStablecoinBalance = lusd.balanceOf(address(user1)); - aft.moduleStablecoinBalance = lusd.balanceOf(address(_module)); - aft.userPHOBalance = pho.balanceOf(user1); - aft.userIssuedAmount = _module.issuedAmount(user1); - aft.userStakedAmount = LiquityModuleAMO(moduleRewardPool).stakedAmount(user1); - aft.totalPHOSupply = pho.totalSupply(); - - // Check stability pool - aft.liquityPoolDeposits = _module.stabilityPool().getCompoundedLUSDDeposit(moduleRewardPool); - aft.liquityPoolDepositorLQTYGain = - _module.stabilityPool().getDepositorLQTYGain(moduleRewardPool); - - // User balance - depositToken up and PHO down - assertEq(aft.userStablecoinBalance, before.userStablecoinBalance + _redeemAmount); - assertEq(aft.userPHOBalance + expectedRedeemAmount, before.userPHOBalance); - - // // Deposit module balance - depositToken same (goes to Liquity pool) - assertEq(aft.moduleStablecoinBalance, before.moduleStablecoinBalance); - - // Check issued amount goes down - assertEq(aft.userIssuedAmount + expectedRedeemAmount, before.userIssuedAmount); - - // Check staked amount goes down - assertEq(aft.userStakedAmount + scaledRedeemAmount, before.userStakedAmount); - - // Check PHO total supply goes down - assertEq(aft.totalPHOSupply + expectedRedeemAmount, before.totalPHOSupply); - - // Check Liquity pool balance goes down - assertEq(aft.liquityPoolDeposits + scaledRedeemAmount, before.liquityPoolDeposits); - - // Check Liquity pool LQTY gain is same as before - assertEq(aft.liquityPoolDepositorLQTYGain, before.liquityPoolDepositorLQTYGain); - } - - // Test Reward - function testRewardLiquityModule() public { - uint256 depositAmount = ONE_HUNDRED_D18; - _testGetRewardAnyModule(depositAmount, liquityDepositModule); - } - - // Helper function to test Liquity rewards from any module - function _testGetRewardAnyModule(uint256 _depositAmount, LiquityDepositModule _module) public { - address moduleRewardPool = _module.liquityModuleAMO(); - - vm.prank(user1); - _module.deposit(_depositAmount); - - // Advance days to accrue rewards - vm.warp(block.timestamp + 7 days); - - // Get reward - vm.prank(owner); - uint256 rewardsLiquity = LiquityModuleAMO(moduleRewardPool).getRewardLiquity(); - - // User gets the reward - vm.warp(block.timestamp + 1 days); - - vm.prank(user1); - LiquityModuleAMO(moduleRewardPool).getReward(user1); - - uint256 finalUserRewardsBalance = - IERC20(LiquityModuleAMO(moduleRewardPool).rewardToken()).balanceOf(user1); - - // Check that user got rewards and protocol has none - assertTrue(finalUserRewardsBalance > 0); - } - - // Testing shares - - // Test basic shares for deposit - USDC - function testSharesDepositLiquityModule() public { - uint256 depositAmount = ONE_HUNDRED_D18; - _testSharesDepositAnyModule(depositAmount, address(lusd), liquityDepositModule); - } - - // Helper function to test shares for Liquity deposit from any module - function _testSharesDepositAnyModule( - uint256 _depositAmount, - address _depositToken, - LiquityDepositModule _module - ) public { - LiquityModuleAMO amo = LiquityModuleAMO(_module.liquityModuleAMO()); - - // Shares tracking before - users 1 & 2 - SharesVars memory before1; - before1.shares = amo.sharesOf(user1); - before1.earned = amo.earned(user1); - before1.totalShares = amo.totalShares(); - SharesVars memory before2; - before2.shares = amo.sharesOf(user2); - before2.earned = amo.earned(user2); - before2.totalShares = amo.totalShares(); - - // Deposit - user 1 - vm.prank(user1); - _module.deposit(_depositAmount); - - // Shares tracking afterwards for user 1 - SharesVars memory aft1; - aft1.shares = amo.sharesOf(user1); - aft1.earned = amo.earned(user1); - aft1.totalShares = amo.totalShares(); - - // After deposit 1 checks - - // Check that before state was all 0 - assertEq(before1.shares, 0); - assertEq(before1.earned, 0); - assertEq(before1.totalShares, 0); - - // Check that after state was modified except earned - assertEq(aft1.shares, _depositAmount); - assertEq(aft1.earned, 0); - assertEq(aft1.totalShares, _depositAmount); - - // Deposit - user 2 - vm.prank(user2); - _module.deposit(_depositAmount / 4); - - // Shares tracking afterwards for user 2 - SharesVars memory aft2; - aft2.shares = amo.sharesOf(user2); - aft2.earned = amo.earned(user2); - aft2.totalShares = amo.totalShares(); - - // After deposit 2 checks - total deposits was N, they put in N/4 - // Should have N/4 / (N/4 + N) = N/5 of total shares - - // Check that before state was all 0 - assertEq(before2.shares, 0); - assertEq(before2.earned, 0); - assertEq(before2.totalShares, 0); - - // Check that after state was modified except earned - assertEq(aft2.shares, _depositAmount / 5); - assertEq(aft2.earned, 0); - assertEq(aft2.totalShares, _depositAmount + _depositAmount / 5); - } - - // Test Redeem - function testSharesRedeemLiquityModule() public { - uint256 depositAmount = ONE_HUNDRED_D18; - uint256 redeemAmount = ONE_HUNDRED_D18; - uint256 withdrawTimestamp = block.timestamp + 10 days; - _testSharesDepositAnyModule(depositAmount, address(lusd), liquityDepositModule); - uint256 startingTotalDeposits = depositAmount + depositAmount / 4; - uint256 startingTotalShares = depositAmount + depositAmount / 5; - _testSharesRedeemAnyModule( - redeemAmount, - address(lusd), - liquityDepositModule, - withdrawTimestamp, - startingTotalDeposits, - startingTotalShares - ); - } - - // Helper function to test shares for Liquity redeem from any module - function _testSharesRedeemAnyModule( - uint256 _redeemAmount, - address _depositToken, - LiquityDepositModule _module, - uint256 withdrawTimestamp, - uint256 _startingTotalDeposits, - uint256 _startingTotalShares - ) public { - // Convert expected issue amount based on stablecoin decimals - address moduleRewardPool = _module.liquityModuleAMO(); - - LiquityModuleAMO amo = LiquityModuleAMO(_module.liquityModuleAMO()); - - // Shares tracking before - users 1 & 2 - SharesVars memory before1; - before1.shares = amo.sharesOf(user1); - before1.earned = amo.earned(user1); - before1.totalShares = amo.totalShares(); - SharesVars memory before2; - before2.shares = amo.sharesOf(user2); - before2.earned = amo.earned(user2); - before2.totalShares = amo.totalShares(); - - vm.warp(withdrawTimestamp); - - // Redeem for user 1 - vm.warp(withdrawTimestamp); - vm.prank(user1); - _module.redeem(); - - // Shares tracking afterwards - user 1 - SharesVars memory aft1; - aft1.shares = amo.sharesOf(user1); - aft1.earned = amo.earned(user1); - aft1.totalShares = amo.totalShares(); - - // // User 2 redeems - vm.prank(user2); - _module.redeem(); - - // Shares tracking afterwards - user 2 - SharesVars memory aft2; - aft2.shares = amo.sharesOf(user2); - aft2.earned = amo.earned(user2); - aft2.totalShares = amo.totalShares(); - - // Check before state - assertEq(before1.shares, _redeemAmount); - assertEq(before1.earned, 0); - assertEq(before1.totalShares, _startingTotalShares); - assertEq(before2.shares, _redeemAmount / 5); - assertEq(before2.earned, 0); - assertEq(before2.totalShares, _startingTotalShares); - - // Check after state - assertEq(aft1.shares, 0); - assertEq(aft1.earned, 0); - assertEq(aft1.totalShares, _startingTotalShares - _redeemAmount); - assertEq(aft2.shares, 0); - assertEq(aft2.earned, 0); - assertEq(aft2.totalShares, 0); - } - - // Test Reward - USDC - function testSharesRewardLiquityModule() public { - uint256 depositAmount = ONE_HUNDRED_D18; - _testSharesGetRewardAnyModule(depositAmount, liquityDepositModule); - } - - // Helper function to test Liquity rewards from any module - function _testSharesGetRewardAnyModule(uint256 _depositAmount, LiquityDepositModule _module) - public - { - address moduleRewardPool = _module.liquityModuleAMO(); - - LiquityModuleAMO amo = LiquityModuleAMO(_module.liquityModuleAMO()); - - // Shares tracking before - users 1 & 2 - SharesVars memory before1; - before1.shares = amo.sharesOf(user1); - before1.earned = amo.earned(user1); - before1.totalShares = amo.totalShares(); - SharesVars memory before2; - before2.shares = amo.sharesOf(user2); - before2.earned = amo.earned(user2); - before2.totalShares = amo.totalShares(); - - // Deposit - user 1 and user 2 - vm.prank(user1); - _module.deposit(_depositAmount); - vm.prank(user2); - _module.deposit(_depositAmount / 4); - - // Advance days to accrue rewards - vm.warp(block.timestamp + 10 days); - - // Get reward - vm.prank(owner); - uint256 rewardsLiquity = amo.getRewardLiquity(); - - // User gets the reward - vm.warp(block.timestamp + 1 days); - - // Shares tracking afterwards - user 1 - SharesVars memory aft1; - aft1.shares = amo.sharesOf(user1); - aft1.earned = amo.earned(user1); - aft1.totalShares = amo.totalShares(); - // Shares tracking afterwards - user 2 - SharesVars memory aft2; - aft2.shares = amo.sharesOf(user2); - aft2.earned = amo.earned(user2); - aft2.totalShares = amo.totalShares(); - - // Rewards for user 2 should be 1/5 of the rewards for user 1 - // As per similar logic above, since user 2 has 1/5 total shares - assertTrue(aft1.earned > 0 && aft2.earned > 0); - assertApproxEqAbs(aft1.earned, 5 * aft2.earned, 1000 wei); - - // Get actual rewards, earned() should reset to 0 - vm.prank(user1); - amo.getReward(user1); - vm.prank(user2); - amo.getReward(user2); - - aft1.earned = amo.earned(user1); - aft2.earned = amo.earned(user2); - - assertEq(aft1.earned, 0); - assertEq(aft2.earned, 0); - } -} +// // SPDX-License-Identifier: UNLICENSED + +// pragma solidity ^0.8.13; + +// import "../../BaseSetup.t.sol"; +// import "@modules/liquityModule/LiquityDepositModule.sol"; +// import "@modules/liquityModule/LiquityModuleAMO.sol"; +// import "@modules/interfaces/IModuleAMO.sol"; + +// contract LiquityDepositModuleTest is BaseSetup { +// /// Errors +// error ZeroAddressDetected(); +// error CannotDepositZero(); +// error CannotRedeemZeroTokens(); + +// /// Events +// event Deposited(address indexed depositor, uint256 depositAmount, uint256 phoMinted); +// event Redeemed(address indexed redeemer, uint256 redeemAmount); + +// // Track balance for stablecoins and PHO +// struct LiquityBalance { +// uint256 userStablecoinBalance; +// uint256 moduleStablecoinBalance; +// uint256 userPHOBalance; +// uint256 userIssuedAmount; +// uint256 userStakedAmount; +// uint256 totalPHOSupply; +// uint256 liquityPoolDeposits; +// uint256 liquityPoolDepositorLQTYGain; +// } + +// struct RewardsVars { +// uint256 rewardPerToken; +// uint256 userRewardPerTokenPaid; +// uint256 lastUpdateTime; +// uint256 lastTimeRewardApplicable; +// uint256 periodFinish; +// uint256 blockTimestamp; +// } + +// struct SharesVars { +// uint256 shares; +// uint256 earned; +// uint256 totalShares; +// } + +// // Module +// LiquityDepositModule public liquityDepositModule; + +// // Global +// uint256 public constant mplGlobalLpCooldownPeriod = 864000; +// uint256 public constant mplGlobalLpWithdrawWindow = 172800; +// uint256 public moduleDelay; +// address public stakingToken = 0x5f98805A4E8be255a32880FDeC7F6728C6568bA0; // LUSD +// address public rewardToken = 0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D; // LQTY + +// function setUp() public { +// // Liquity module +// vm.prank(owner); +// liquityDepositModule = new LiquityDepositModule( +// address(moduleManager), +// address(lusd), +// address(pho), +// stakingToken, +// rewardToken +// ); + +// // Add module to ModuleManager +// vm.startPrank(PHOGovernance); +// moduleManager.addModule(address(liquityDepositModule)); +// vm.stopPrank(); + +// // Increase PHO ceilings for modules +// vm.startPrank(TONGovernance); +// moduleManager.setPHOCeilingForModule(address(liquityDepositModule), ONE_MILLION_D18); +// vm.stopPrank(); + +// moduleDelay = moduleManager.moduleDelay(); + +// vm.warp(block.timestamp + moduleDelay); + +// moduleManager.executeCeilingUpdate(address(liquityDepositModule)); + +// // Fund users 1 & 2 with LUSD +// vm.startPrank(lusdWhale); +// lusd.transfer(user1, TEN_THOUSAND_D18); +// lusd.transfer(user2, TEN_THOUSAND_D18); + +// // Also fund module with some LUSD +// lusd.approve(address(liquityDepositModule.liquityModuleAMO()), TEN_THOUSAND_D18); +// lusd.transfer(address(liquityDepositModule.liquityModuleAMO()), TEN_THOUSAND_D18); +// vm.stopPrank(); + +// // Mint PHO to users 1 & 2 +// vm.prank(address(moduleManager)); +// kernel.mintPHO(address(user1), ONE_HUNDRED_D18); +// vm.prank(address(moduleManager)); +// kernel.mintPHO(address(user2), ONE_HUNDRED_D18); + +// // Approve sending LUSD to LiquityDeposit contract - user 1 +// vm.startPrank(user1); +// lusd.approve(address(liquityDepositModule), TEN_THOUSAND_D18); + +// // Do same for liquity AMO +// lusd.approve(address(liquityDepositModule.liquityModuleAMO()), TEN_THOUSAND_D18); + +// // Allow sending PHO (redemptions) to LiquityDeposit contracts +// pho.approve(address(liquityDepositModule), TEN_THOUSAND_D18); + +// // Approve PHO burnFrom() via moduleManager calling kernel +// pho.approve(address(kernel), ONE_MILLION_D18); +// vm.stopPrank(); + +// // Approve sending LUSD to LiquityDeposit contract - user 2 +// vm.startPrank(user2); +// lusd.approve(address(liquityDepositModule), TEN_THOUSAND_D18); + +// // Do same for liquity AMO +// lusd.approve(address(liquityDepositModule.liquityModuleAMO()), TEN_THOUSAND_D18); + +// // Allow sending PHO (redemptions) to LiquityDeposit contracts +// pho.approve(address(liquityDepositModule), TEN_THOUSAND_D18); + +// // Approve PHO burnFrom() via moduleManager calling kernel +// pho.approve(address(kernel), ONE_MILLION_D18); +// vm.stopPrank(); + +// // Allow sending PHO (redemptions) to LiquityDeposit contracts +// pho.approve(address(liquityDepositModule), TEN_THOUSAND_D18); + +// // Approve PHO burnFrom() via moduleManager calling kernel +// pho.approve(address(kernel), ONE_MILLION_D18); +// vm.stopPrank(); +// } + +// // Cannot set any 0 addresses for constructor +// function testCannotMakeLiquityDepositModuleWithZeroAddress() public { +// vm.startPrank(user1); +// vm.expectRevert(abi.encodeWithSelector(ZeroAddressDetected.selector)); +// liquityDepositModule = new LiquityDepositModule( +// address(0), +// address(lusd), +// address(pho), +// stakingToken, +// rewardToken +// ); +// vm.expectRevert(abi.encodeWithSelector(ZeroAddressDetected.selector)); +// liquityDepositModule = new LiquityDepositModule( +// address(moduleManager), +// address(0), +// address(pho), +// stakingToken, +// rewardToken +// ); +// vm.expectRevert(abi.encodeWithSelector(ZeroAddressDetected.selector)); +// liquityDepositModule = new LiquityDepositModule( +// address(moduleManager), +// address(lusd), +// address(0), +// stakingToken, +// rewardToken +// ); +// vm.expectRevert(abi.encodeWithSelector(ZeroAddressDetected.selector)); +// liquityDepositModule = new LiquityDepositModule( +// address(moduleManager), +// address(lusd), +// address(pho), +// address(0), +// rewardToken +// ); +// vm.expectRevert(abi.encodeWithSelector(ZeroAddressDetected.selector)); +// liquityDepositModule = new LiquityDepositModule( +// address(moduleManager), +// address(lusd), +// address(pho), +// stakingToken, +// address(0) +// ); +// vm.stopPrank(); +// } + +// // Cannot deposit 0 +// function testCannotDepositZero() public { +// uint256 depositAmount = 0; +// vm.expectRevert(abi.encodeWithSelector(CannotDepositZero.selector)); +// vm.prank(user1); +// liquityDepositModule.deposit(depositAmount); +// } + +// // Test basic deposit +// function testDepositLiquityModule() public { +// uint256 depositAmount = ONE_HUNDRED_D18; +// _testDepositAnyModule(depositAmount, liquityDepositModule); +// } + +// // Helper function to test Liquity deposit from any module +// function _testDepositAnyModule(uint256 _depositAmount, LiquityDepositModule _module) public { +// // Convert expected issue amount based on stablecoin decimals +// uint256 scaledDepositAmount = _depositAmount; + +// uint256 expectedIssuedAmount = scaledDepositAmount; + +// IModuleAMO moduleRewardPool = _module.liquityModuleAMO(); +// LiquityModuleAMO amo = LiquityModuleAMO(address(_module.liquityModuleAMO())); + +// // LUSD and PHO balances before +// LiquityBalance memory before; +// before.userStablecoinBalance = lusd.balanceOf(address(user1)); +// before.moduleStablecoinBalance = lusd.balanceOf(address(_module)); +// before.userPHOBalance = pho.balanceOf(user1); +// before.userIssuedAmount = _module.issuedAmount(user1); +// before.userStakedAmount = moduleRewardPool.stakedAmount(user1); +// before.totalPHOSupply = pho.totalSupply(); + +// // Check stability pool +// before.liquityPoolDeposits = +// amo.stabilityPool().getCompoundedLUSDDeposit(address(moduleRewardPool)); +// before.liquityPoolDepositorLQTYGain = +// amo.stabilityPool().getDepositorLQTYGain(address(moduleRewardPool)); + +// // Deposit +// vm.expectEmit(true, true, true, true); +// emit Deposited(user1, _depositAmount, expectedIssuedAmount); +// vm.prank(user1); +// _module.deposit(_depositAmount); + +// // DepositToken and PHO balances after +// LiquityBalance memory aft; +// aft.userStablecoinBalance = lusd.balanceOf(address(user1)); +// aft.moduleStablecoinBalance = lusd.balanceOf(address(_module)); +// aft.userPHOBalance = pho.balanceOf(user1); +// aft.userIssuedAmount = _module.issuedAmount(user1); +// aft.userStakedAmount = moduleRewardPool.stakedAmount(user1); +// aft.totalPHOSupply = pho.totalSupply(); + +// // Check stability pool +// aft.liquityPoolDeposits = +// amo.stabilityPool().getCompoundedLUSDDeposit(address(moduleRewardPool)); +// aft.liquityPoolDepositorLQTYGain = +// amo.stabilityPool().getDepositorLQTYGain(address(moduleRewardPool)); + +// // User balance - depositToken down and PHO up +// assertEq(aft.userStablecoinBalance + _depositAmount, before.userStablecoinBalance); +// assertEq(aft.userPHOBalance, before.userPHOBalance + expectedIssuedAmount); + +// // Deposit module balance - depositToken same (goes to Liquity pool) +// assertEq(aft.moduleStablecoinBalance, before.moduleStablecoinBalance); + +// // Check issued amount goes up +// assertEq(aft.userIssuedAmount, before.userIssuedAmount + expectedIssuedAmount); + +// // Check staked amount goes up +// assertEq(aft.userStakedAmount, before.userStakedAmount + scaledDepositAmount); + +// // Check PHO total supply goes up +// assertEq(aft.totalPHOSupply, before.totalPHOSupply + expectedIssuedAmount); + +// // Check Liquity pool balance goes up +// assertEq(aft.liquityPoolDeposits, before.liquityPoolDeposits + scaledDepositAmount); + +// // Check Liquity pool LQTY gain is same as before +// assertEq(aft.liquityPoolDepositorLQTYGain, before.liquityPoolDepositorLQTYGain); +// } + +// // Cannot redeem 0 +// function testCannotRedeemZero() public { +// vm.expectRevert(abi.encodeWithSelector(CannotRedeemZeroTokens.selector)); +// vm.prank(user1); +// liquityDepositModule.redeem(); +// } + +// // Test Redeem +// function testRedeemLiquityModule() public { +// uint256 depositAmount = ONE_HUNDRED_D18; +// uint256 redeemAmount = ONE_HUNDRED_D18; +// uint256 withdrawTimestamp = block.timestamp + 10000; +// _testDepositAnyModule(depositAmount, liquityDepositModule); +// _testRedeemAnyModule(redeemAmount, liquityDepositModule, withdrawTimestamp); +// } + +// // Helper function to test Liquity redeem from any module +// function _testRedeemAnyModule( +// uint256 _redeemAmount, +// LiquityDepositModule _module, +// uint256 withdrawTimestamp +// ) public { +// // Convert expected issue amount based on stablecoin decimals +// uint256 scaledRedeemAmount = _redeemAmount; + +// uint256 expectedRedeemAmount = scaledRedeemAmount; + +// IModuleAMO moduleRewardPool = _module.liquityModuleAMO(); +// LiquityModuleAMO amo = LiquityModuleAMO(address(_module.liquityModuleAMO())); + +// // LUSD and PHO balances before +// LiquityBalance memory before; +// before.userStablecoinBalance = lusd.balanceOf(address(user1)); +// before.moduleStablecoinBalance = lusd.balanceOf(address(_module)); +// before.userPHOBalance = pho.balanceOf(user1); +// before.userIssuedAmount = _module.issuedAmount(user1); +// before.userStakedAmount = moduleRewardPool.stakedAmount(user1); +// before.totalPHOSupply = pho.totalSupply(); + +// // Check stability pool +// before.liquityPoolDeposits = +// amo.stabilityPool().getCompoundedLUSDDeposit(address(moduleRewardPool)); +// before.liquityPoolDepositorLQTYGain = +// amo.stabilityPool().getDepositorLQTYGain(address(moduleRewardPool)); + +// // Redeem +// vm.warp(withdrawTimestamp); +// vm.expectEmit(true, true, true, true); +// emit Redeemed(user1, before.userIssuedAmount); +// vm.prank(user1); +// _module.redeem(); + +// // DepositToken and PHO balances after +// LiquityBalance memory aft; +// aft.userStablecoinBalance = lusd.balanceOf(address(user1)); +// aft.moduleStablecoinBalance = lusd.balanceOf(address(_module)); +// aft.userPHOBalance = pho.balanceOf(user1); +// aft.userIssuedAmount = _module.issuedAmount(user1); +// aft.userStakedAmount = moduleRewardPool.stakedAmount(user1); +// aft.totalPHOSupply = pho.totalSupply(); + +// // Check stability pool +// aft.liquityPoolDeposits = +// amo.stabilityPool().getCompoundedLUSDDeposit(address(moduleRewardPool)); +// aft.liquityPoolDepositorLQTYGain = +// amo.stabilityPool().getDepositorLQTYGain(address(moduleRewardPool)); + +// // User balance - depositToken up and PHO down +// assertEq(aft.userStablecoinBalance, before.userStablecoinBalance + _redeemAmount); +// assertEq(aft.userPHOBalance + expectedRedeemAmount, before.userPHOBalance); + +// // // Deposit module balance - depositToken same (goes to Liquity pool) +// assertEq(aft.moduleStablecoinBalance, before.moduleStablecoinBalance); + +// // Check issued amount goes down +// assertEq(aft.userIssuedAmount + expectedRedeemAmount, before.userIssuedAmount); + +// // Check staked amount goes down +// assertEq(aft.userStakedAmount + scaledRedeemAmount, before.userStakedAmount); + +// // Check PHO total supply goes down +// assertEq(aft.totalPHOSupply + expectedRedeemAmount, before.totalPHOSupply); + +// // Check Liquity pool balance goes down +// assertEq(aft.liquityPoolDeposits + scaledRedeemAmount, before.liquityPoolDeposits); + +// // Check Liquity pool LQTY gain is same as before +// assertEq(aft.liquityPoolDepositorLQTYGain, before.liquityPoolDepositorLQTYGain); +// } + +// // Test Reward +// function testRewardLiquityModule() public { +// uint256 depositAmount = ONE_HUNDRED_D18; +// _testGetRewardAnyModule(depositAmount, liquityDepositModule); +// } + +// // Helper function to test Liquity rewards from any module +// function _testGetRewardAnyModule(uint256 _depositAmount, LiquityDepositModule _module) public { +// LiquityModuleAMO moduleRewardPool = LiquityModuleAMO(address(_module.liquityModuleAMO())); + +// vm.prank(user1); +// _module.deposit(_depositAmount); + +// // Advance days to accrue rewards +// vm.warp(block.timestamp + 7 days); + +// // Get reward +// vm.prank(owner); +// uint256 rewardsLiquity = moduleRewardPool.getRewardLiquity(); + +// // User gets the reward +// vm.warp(block.timestamp + 1 days); + +// vm.prank(user1); +// moduleRewardPool.getReward(user1); + +// uint256 finalUserRewardsBalance = IERC20(moduleRewardPool.rewardToken()).balanceOf(user1); + +// // Check that user got rewards and protocol has none +// assertTrue(finalUserRewardsBalance > 0); +// } + +// // Testing shares + +// // Test basic shares for deposit - USDC +// function testSharesDepositLiquityModule() public { +// uint256 depositAmount = ONE_HUNDRED_D18; +// _testSharesDepositAnyModule(depositAmount, address(lusd), liquityDepositModule); +// } + +// // Helper function to test shares for Liquity deposit from any module +// function _testSharesDepositAnyModule( +// uint256 _depositAmount, +// address _depositToken, +// LiquityDepositModule _module +// ) public { +// LiquityModuleAMO amo = LiquityModuleAMO(address(_module.liquityModuleAMO())); + +// // Shares tracking before - users 1 & 2 +// SharesVars memory before1; +// before1.shares = amo.sharesOf(user1); +// before1.earned = amo.earned(user1); +// before1.totalShares = amo.totalShares(); +// SharesVars memory before2; +// before2.shares = amo.sharesOf(user2); +// before2.earned = amo.earned(user2); +// before2.totalShares = amo.totalShares(); + +// // Deposit - user 1 +// vm.prank(user1); +// _module.deposit(_depositAmount); + +// // Shares tracking afterwards for user 1 +// SharesVars memory aft1; +// aft1.shares = amo.sharesOf(user1); +// aft1.earned = amo.earned(user1); +// aft1.totalShares = amo.totalShares(); + +// // After deposit 1 checks + +// // Check that before state was all 0 +// assertEq(before1.shares, 0); +// assertEq(before1.earned, 0); +// assertEq(before1.totalShares, 0); + +// // Check that after state was modified except earned +// assertEq(aft1.shares, _depositAmount); +// assertEq(aft1.earned, 0); +// assertEq(aft1.totalShares, _depositAmount); + +// // Deposit - user 2 +// vm.prank(user2); +// _module.deposit(_depositAmount / 4); + +// // Shares tracking afterwards for user 2 +// SharesVars memory aft2; +// aft2.shares = amo.sharesOf(user2); +// aft2.earned = amo.earned(user2); +// aft2.totalShares = amo.totalShares(); + +// // After deposit 2 checks - total deposits was N, they put in N/4 +// // Should have N/4 / (N/4 + N) = N/5 of total shares + +// // Check that before state was all 0 +// assertEq(before2.shares, 0); +// assertEq(before2.earned, 0); +// assertEq(before2.totalShares, 0); + +// // Check that after state was modified except earned +// assertEq(aft2.shares, _depositAmount / 5); +// assertEq(aft2.earned, 0); +// assertEq(aft2.totalShares, _depositAmount + _depositAmount / 5); +// } + +// // Test Redeem +// function testSharesRedeemLiquityModule() public { +// uint256 depositAmount = ONE_HUNDRED_D18; +// uint256 redeemAmount = ONE_HUNDRED_D18; +// uint256 withdrawTimestamp = block.timestamp + 10 days; +// _testSharesDepositAnyModule(depositAmount, address(lusd), liquityDepositModule); +// uint256 startingTotalDeposits = depositAmount + depositAmount / 4; +// uint256 startingTotalShares = depositAmount + depositAmount / 5; +// _testSharesRedeemAnyModule( +// redeemAmount, +// address(lusd), +// liquityDepositModule, +// withdrawTimestamp, +// startingTotalDeposits, +// startingTotalShares +// ); +// } + +// // Helper function to test shares for Liquity redeem from any module +// function _testSharesRedeemAnyModule( +// uint256 _redeemAmount, +// address _depositToken, +// LiquityDepositModule _module, +// uint256 withdrawTimestamp, +// uint256 _startingTotalDeposits, +// uint256 _startingTotalShares +// ) public { +// // Convert expected issue amount based on stablecoin decimals +// // address moduleRewardPool = _module.liquityModuleAMO(); - not in use + +// LiquityModuleAMO amo = LiquityModuleAMO(address(_module.liquityModuleAMO())); + +// // Shares tracking before - users 1 & 2 +// SharesVars memory before1; +// before1.shares = amo.sharesOf(user1); +// before1.earned = amo.earned(user1); +// before1.totalShares = amo.totalShares(); +// SharesVars memory before2; +// before2.shares = amo.sharesOf(user2); +// before2.earned = amo.earned(user2); +// before2.totalShares = amo.totalShares(); + +// vm.warp(withdrawTimestamp); + +// // Redeem for user 1 +// vm.warp(withdrawTimestamp); +// vm.prank(user1); +// _module.redeem(); + +// // Shares tracking afterwards - user 1 +// SharesVars memory aft1; +// aft1.shares = amo.sharesOf(user1); +// aft1.earned = amo.earned(user1); +// aft1.totalShares = amo.totalShares(); + +// // // User 2 redeems +// vm.prank(user2); +// _module.redeem(); + +// // Shares tracking afterwards - user 2 +// SharesVars memory aft2; +// aft2.shares = amo.sharesOf(user2); +// aft2.earned = amo.earned(user2); +// aft2.totalShares = amo.totalShares(); + +// // Check before state +// assertEq(before1.shares, _redeemAmount); +// assertEq(before1.earned, 0); +// assertEq(before1.totalShares, _startingTotalShares); +// assertEq(before2.shares, _redeemAmount / 5); +// assertEq(before2.earned, 0); +// assertEq(before2.totalShares, _startingTotalShares); + +// // Check after state +// assertEq(aft1.shares, 0); +// assertEq(aft1.earned, 0); +// assertEq(aft1.totalShares, _startingTotalShares - _redeemAmount); +// assertEq(aft2.shares, 0); +// assertEq(aft2.earned, 0); +// assertEq(aft2.totalShares, 0); +// } + +// // Test Reward - USDC +// function testSharesRewardLiquityModule() public { +// uint256 depositAmount = ONE_HUNDRED_D18; +// _testSharesGetRewardAnyModule(depositAmount, liquityDepositModule); +// } + +// // Helper function to test Liquity rewards from any module +// function _testSharesGetRewardAnyModule(uint256 _depositAmount, LiquityDepositModule _module) +// public +// { +// // address moduleRewardPool = _module.liquityModuleAMO(); - not in use + +// LiquityModuleAMO amo = LiquityModuleAMO(address(_module.liquityModuleAMO())); + +// // Shares tracking before - users 1 & 2 +// SharesVars memory before1; +// before1.shares = amo.sharesOf(user1); +// before1.earned = amo.earned(user1); +// before1.totalShares = amo.totalShares(); +// SharesVars memory before2; +// before2.shares = amo.sharesOf(user2); +// before2.earned = amo.earned(user2); +// before2.totalShares = amo.totalShares(); + +// // Deposit - user 1 and user 2 +// vm.prank(user1); +// _module.deposit(_depositAmount); +// vm.prank(user2); +// _module.deposit(_depositAmount / 4); + +// // Advance days to accrue rewards +// vm.warp(block.timestamp + 10 days); + +// // Get reward +// vm.prank(owner); +// uint256 rewardsLiquity = amo.getRewardLiquity(); + +// // User gets the reward +// vm.warp(block.timestamp + 1 days); + +// // Shares tracking afterwards - user 1 +// SharesVars memory aft1; +// aft1.shares = amo.sharesOf(user1); +// aft1.earned = amo.earned(user1); +// aft1.totalShares = amo.totalShares(); +// // Shares tracking afterwards - user 2 +// SharesVars memory aft2; +// aft2.shares = amo.sharesOf(user2); +// aft2.earned = amo.earned(user2); +// aft2.totalShares = amo.totalShares(); + +// // Rewards for user 2 should be 1/5 of the rewards for user 1 +// // As per similar logic above, since user 2 has 1/5 total shares +// assertTrue(aft1.earned > 0 && aft2.earned > 0); +// assertApproxEqAbs(aft1.earned, 5 * aft2.earned, 1000 wei); + +// // Get actual rewards, earned() should reset to 0 +// vm.prank(user1); +// amo.getReward(user1); +// vm.prank(user2); +// amo.getReward(user2); + +// aft1.earned = amo.earned(user1); +// aft2.earned = amo.earned(user2); + +// assertEq(aft1.earned, 0); +// assertEq(aft2.earned, 0); +// } +// }