From 639b35671324b6d4d3e79465dfeebd0deedfbe3d Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Fri, 24 Nov 2023 16:56:42 +0530 Subject: [PATCH 01/64] prettier formatting --- test/oracle/ChainlinkOracle.t.sol | 4 +--- test/oracle/SPAOracle.t.sol | 8 +------- test/utils/DeploymentSetup.sol | 4 +--- test/utils/UpgradeUtil.sol | 6 +----- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/test/oracle/ChainlinkOracle.t.sol b/test/oracle/ChainlinkOracle.t.sol index dfcc70a7..6249b93b 100644 --- a/test/oracle/ChainlinkOracle.t.sol +++ b/test/oracle/ChainlinkOracle.t.sol @@ -13,9 +13,7 @@ contract ChainlinkOracleTest is BaseTest { super.setUp(); setArbitrumFork(); vm.prank(USDS_OWNER); - chainlinkOracle = new ChainlinkOracle( - new ChainlinkOracle.SetupTokenData[](0) - ); + chainlinkOracle = new ChainlinkOracle(new ChainlinkOracle.SetupTokenData[](0)); } function _getTokenData() internal pure returns (ChainlinkOracle.SetupTokenData[] memory) { diff --git a/test/oracle/SPAOracle.t.sol b/test/oracle/SPAOracle.t.sol index 228766bd..02e56ed1 100644 --- a/test/oracle/SPAOracle.t.sol +++ b/test/oracle/SPAOracle.t.sol @@ -69,13 +69,7 @@ contract SPAOracleTest is BaseUniOracleTest { function setUp() public override { super.setUp(); vm.startPrank(USDS_OWNER); - spaOracle = new SPAOracle( - masterOracle, - USDCe, - FEE_TIER, - MA_PERIOD, - WEIGHT_DIA - ); + spaOracle = new SPAOracle(masterOracle, USDCe, FEE_TIER, MA_PERIOD, WEIGHT_DIA); spaOracle.updateDIAParams(WEIGHT_DIA, type(uint128).max); vm.stopPrank(); } diff --git a/test/utils/DeploymentSetup.sol b/test/utils/DeploymentSetup.sol index f2ee98f1..ee7b2c8d 100644 --- a/test/utils/DeploymentSetup.sol +++ b/test/utils/DeploymentSetup.sol @@ -75,9 +75,7 @@ abstract contract PreMigrationSetup is Setup { CollateralManager collateralManager = new CollateralManager(VAULT); ORACLE = address(new MasterPriceOracle()); - FeeCalculator feeCalculator = new FeeCalculator( - address(collateralManager) - ); + FeeCalculator feeCalculator = new FeeCalculator(address(collateralManager)); FEE_CALCULATOR = address(feeCalculator); COLLATERAL_MANAGER = address(collateralManager); diff --git a/test/utils/UpgradeUtil.sol b/test/utils/UpgradeUtil.sol index 8718d4c7..87e6445c 100644 --- a/test/utils/UpgradeUtil.sol +++ b/test/utils/UpgradeUtil.sol @@ -12,11 +12,7 @@ contract UpgradeUtil { } function deployErc1967Proxy(address impl) public returns (address) { - TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( - impl, - address(proxyAdmin), - "" - ); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(impl, address(proxyAdmin), ""); return address(proxy); } } From bc7b37a9f446c76415b6906f0d3f72bfe634a0c5 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Fri, 24 Nov 2023 17:00:02 +0530 Subject: [PATCH 02/64] fix(emergency-withdraw): allocatedAmt - amtRcv --- contracts/strategies/stargate/StargateStrategy.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 4db90a9a..c323fe0c 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -139,6 +139,7 @@ contract StargateStrategy is InitializableAbstractStrategy { ILPStaking(farm).emergencyWithdraw(assetInfo[_asset].rewardPID); uint256 amtRecv = IStargateRouter(router).instantRedeemLocal(assetInfo[_asset].pid, lpTokenAmt, vault) * IStargatePool(assetToPToken[_asset]).convertRate(); + assetInfo[_asset].allocatedAmt -= amtRecv; emit Withdrawal(_asset, amtRecv); } From 73167a738272dd5b3f037569077205b44c355d09 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Fri, 24 Nov 2023 17:09:38 +0530 Subject: [PATCH 03/64] guarded deposit function with onlyVault (SPE-12) --- contracts/strategies/aave/AaveStrategy.sol | 2 +- contracts/strategies/compound/CompoundStrategy.sol | 2 +- contracts/strategies/stargate/StargateStrategy.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/strategies/aave/AaveStrategy.sol b/contracts/strategies/aave/AaveStrategy.sol index bf73ad24..4d65437e 100755 --- a/contracts/strategies/aave/AaveStrategy.sol +++ b/contracts/strategies/aave/AaveStrategy.sol @@ -52,7 +52,7 @@ contract AaveStrategy is InitializableAbstractStrategy { } /// @inheritdoc InitializableAbstractStrategy - function deposit(address _asset, uint256 _amount) external override nonReentrant { + function deposit(address _asset, uint256 _amount) external override onlyVault nonReentrant { if (!supportsCollateral(_asset)) revert CollateralNotSupported(_asset); Helpers._isNonZeroAmt(_amount); // Following line also doubles as a check that we are depositing diff --git a/contracts/strategies/compound/CompoundStrategy.sol b/contracts/strategies/compound/CompoundStrategy.sol index 80cac18a..cb536bbb 100644 --- a/contracts/strategies/compound/CompoundStrategy.sol +++ b/contracts/strategies/compound/CompoundStrategy.sol @@ -49,7 +49,7 @@ contract CompoundStrategy is InitializableAbstractStrategy { } /// @inheritdoc InitializableAbstractStrategy - function deposit(address _asset, uint256 _amount) external override nonReentrant { + function deposit(address _asset, uint256 _amount) external override onlyVault nonReentrant { Helpers._isNonZeroAmt(_amount); address lpToken = _getPTokenFor(_asset); diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index c323fe0c..cd308967 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -82,7 +82,7 @@ contract StargateStrategy is InitializableAbstractStrategy { } /// @inheritdoc InitializableAbstractStrategy - function deposit(address _asset, uint256 _amount) external override nonReentrant { + function deposit(address _asset, uint256 _amount) external override onlyVault nonReentrant { Helpers._isNonZeroAmt(_amount); if (!supportsCollateral(_asset)) revert CollateralNotSupported(_asset); address lpToken = assetToPToken[_asset]; From 2dd4f811931debe85f815a1974b7790a73894e5b Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Mon, 27 Nov 2023 16:22:08 +0530 Subject: [PATCH 04/64] Added recover erc20 function (SPE-15) --- contracts/strategies/InitializableAbstractStrategy.sol | 10 ++++++++++ test/strategy/StargateStrategy.t.sol | 3 +++ 2 files changed, 13 insertions(+) diff --git a/contracts/strategies/InitializableAbstractStrategy.sol b/contracts/strategies/InitializableAbstractStrategy.sol index 3d6d35ed..76e8de06 100755 --- a/contracts/strategies/InitializableAbstractStrategy.sol +++ b/contracts/strategies/InitializableAbstractStrategy.sol @@ -79,6 +79,16 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade emit HarvestIncentiveRateUpdated(_newRate); } + /// @notice A function to recover any erc20 token sent to this strategy mistakenly + /// @dev Only callable by owner + /// @param token Address of the token + /// @param receiver Receiver of the token + /// @param amount Amount to be recovered + /// @dev reverts if amount > balance + function recoverERC20(address token, address receiver, uint256 amount) external onlyOwner { + IERC20(token).safeTransfer(receiver, amount); + } + /// @dev Deposit an amount of asset into the platform /// @param _asset Address for the asset /// @param _amount Units of asset to deposit diff --git a/test/strategy/StargateStrategy.t.sol b/test/strategy/StargateStrategy.t.sol index 8cb68781..8dfe8262 100644 --- a/test/strategy/StargateStrategy.t.sol +++ b/test/strategy/StargateStrategy.t.sol @@ -337,6 +337,9 @@ contract Deposit is StargateStrategyTest { } } assertEq(strategy.checkBalance(assetData[i].asset), amt); + uint256 _bal = ERC20(assetData[i].asset).balanceOf(address(strategy)); + emit log_named_uint("Strategy Balance", _bal); + assertApproxEqAbs(ERC20(assetData[i].asset).balanceOf(address(strategy)), 0, 1); } } From 6e8a0f2dc7d1589e5a52b002e7a5a8c3fa8e7edf Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Mon, 27 Nov 2023 19:14:39 +0530 Subject: [PATCH 05/64] removed recovererc20 function --- contracts/strategies/InitializableAbstractStrategy.sol | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/contracts/strategies/InitializableAbstractStrategy.sol b/contracts/strategies/InitializableAbstractStrategy.sol index 76e8de06..3d6d35ed 100755 --- a/contracts/strategies/InitializableAbstractStrategy.sol +++ b/contracts/strategies/InitializableAbstractStrategy.sol @@ -79,16 +79,6 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade emit HarvestIncentiveRateUpdated(_newRate); } - /// @notice A function to recover any erc20 token sent to this strategy mistakenly - /// @dev Only callable by owner - /// @param token Address of the token - /// @param receiver Receiver of the token - /// @param amount Amount to be recovered - /// @dev reverts if amount > balance - function recoverERC20(address token, address receiver, uint256 amount) external onlyOwner { - IERC20(token).safeTransfer(receiver, amount); - } - /// @dev Deposit an amount of asset into the platform /// @param _asset Address for the asset /// @param _amount Units of asset to deposit From 99ca91e73e442fa8b04852be6cd1c4790fb4ba28 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Tue, 28 Nov 2023 11:50:11 +0530 Subject: [PATCH 06/64] Added recover erc20 function (SPE-15) --- contracts/strategies/InitializableAbstractStrategy.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/contracts/strategies/InitializableAbstractStrategy.sol b/contracts/strategies/InitializableAbstractStrategy.sol index 3d6d35ed..76e8de06 100755 --- a/contracts/strategies/InitializableAbstractStrategy.sol +++ b/contracts/strategies/InitializableAbstractStrategy.sol @@ -79,6 +79,16 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade emit HarvestIncentiveRateUpdated(_newRate); } + /// @notice A function to recover any erc20 token sent to this strategy mistakenly + /// @dev Only callable by owner + /// @param token Address of the token + /// @param receiver Receiver of the token + /// @param amount Amount to be recovered + /// @dev reverts if amount > balance + function recoverERC20(address token, address receiver, uint256 amount) external onlyOwner { + IERC20(token).safeTransfer(receiver, amount); + } + /// @dev Deposit an amount of asset into the platform /// @param _asset Address for the asset /// @param _amount Units of asset to deposit From dc0b609634b5819255dec1de114249994937caa9 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Tue, 28 Nov 2023 14:17:19 +0530 Subject: [PATCH 07/64] using safeIncreaseAllowance (SPE-17) --- contracts/strategies/aave/AaveStrategy.sol | 2 +- contracts/strategies/compound/CompoundStrategy.sol | 2 +- contracts/strategies/stargate/StargateStrategy.sol | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/strategies/aave/AaveStrategy.sol b/contracts/strategies/aave/AaveStrategy.sol index 4d65437e..019d84d6 100755 --- a/contracts/strategies/aave/AaveStrategy.sol +++ b/contracts/strategies/aave/AaveStrategy.sol @@ -60,7 +60,7 @@ contract AaveStrategy is InitializableAbstractStrategy { allocatedAmount[_asset] += _amount; IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount); - IERC20(_asset).safeApprove(address(aavePool), _amount); + IERC20(_asset).safeIncreaseAllowance(address(aavePool), _amount); aavePool.supply(_asset, _amount, address(this), REFERRAL_CODE); emit Deposit(_asset, _amount); diff --git a/contracts/strategies/compound/CompoundStrategy.sol b/contracts/strategies/compound/CompoundStrategy.sol index cb536bbb..8220c8e7 100644 --- a/contracts/strategies/compound/CompoundStrategy.sol +++ b/contracts/strategies/compound/CompoundStrategy.sol @@ -58,7 +58,7 @@ contract CompoundStrategy is InitializableAbstractStrategy { allocatedAmount[_asset] += _amount; IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount); - IERC20(_asset).safeApprove(lpToken, _amount); + IERC20(_asset).safeIncreaseAllowance(lpToken, _amount); // Supply Compound Strategy. IComet(lpToken).supply(_asset, _amount); diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index cd308967..03443761 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -87,7 +87,7 @@ contract StargateStrategy is InitializableAbstractStrategy { if (!supportsCollateral(_asset)) revert CollateralNotSupported(_asset); address lpToken = assetToPToken[_asset]; IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount); - IERC20(_asset).safeApprove(router, _amount); + IERC20(_asset).safeIncreaseAllowance(router, _amount); // Add liquidity in the stargate pool. IStargateRouter(router).addLiquidity(assetInfo[_asset].pid, _amount, address(this)); @@ -103,7 +103,7 @@ contract StargateStrategy is InitializableAbstractStrategy { // Update the allocated amount in the strategy assetInfo[_asset].allocatedAmt += depositAmt; - IERC20(lpToken).safeApprove(farm, lpTokenBal); + IERC20(lpToken).safeIncreaseAllowance(farm, lpTokenBal); ILPStaking(farm).deposit(assetInfo[_asset].rewardPID, lpTokenBal); emit Deposit(_asset, depositAmt); } From 010105b74b335fb962c789262fea6eaec92c5097 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Tue, 28 Nov 2023 18:39:24 +0530 Subject: [PATCH 08/64] updated to forceApprove --- contracts/strategies/aave/AaveStrategy.sol | 2 +- contracts/strategies/compound/CompoundStrategy.sol | 2 +- contracts/strategies/stargate/StargateStrategy.sol | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/strategies/aave/AaveStrategy.sol b/contracts/strategies/aave/AaveStrategy.sol index 019d84d6..1a2724c9 100755 --- a/contracts/strategies/aave/AaveStrategy.sol +++ b/contracts/strategies/aave/AaveStrategy.sol @@ -60,7 +60,7 @@ contract AaveStrategy is InitializableAbstractStrategy { allocatedAmount[_asset] += _amount; IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount); - IERC20(_asset).safeIncreaseAllowance(address(aavePool), _amount); + IERC20(_asset).forceApprove(address(aavePool), _amount); aavePool.supply(_asset, _amount, address(this), REFERRAL_CODE); emit Deposit(_asset, _amount); diff --git a/contracts/strategies/compound/CompoundStrategy.sol b/contracts/strategies/compound/CompoundStrategy.sol index 8220c8e7..410e663f 100644 --- a/contracts/strategies/compound/CompoundStrategy.sol +++ b/contracts/strategies/compound/CompoundStrategy.sol @@ -58,7 +58,7 @@ contract CompoundStrategy is InitializableAbstractStrategy { allocatedAmount[_asset] += _amount; IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount); - IERC20(_asset).safeIncreaseAllowance(lpToken, _amount); + IERC20(_asset).forceApprove(lpToken, _amount); // Supply Compound Strategy. IComet(lpToken).supply(_asset, _amount); diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 03443761..0109114c 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -87,7 +87,7 @@ contract StargateStrategy is InitializableAbstractStrategy { if (!supportsCollateral(_asset)) revert CollateralNotSupported(_asset); address lpToken = assetToPToken[_asset]; IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount); - IERC20(_asset).safeIncreaseAllowance(router, _amount); + IERC20(_asset).forceApprove(router, _amount); // Add liquidity in the stargate pool. IStargateRouter(router).addLiquidity(assetInfo[_asset].pid, _amount, address(this)); @@ -103,7 +103,7 @@ contract StargateStrategy is InitializableAbstractStrategy { // Update the allocated amount in the strategy assetInfo[_asset].allocatedAmt += depositAmt; - IERC20(lpToken).safeIncreaseAllowance(farm, lpTokenBal); + IERC20(lpToken).forceApprove(farm, lpTokenBal); ILPStaking(farm).deposit(assetInfo[_asset].rewardPID, lpTokenBal); emit Deposit(_asset, depositAmt); } From 9175d360396158b89b4b82ac1c41e3ee5d2553f3 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Wed, 29 Nov 2023 16:21:00 +0530 Subject: [PATCH 09/64] Updated Stargate strategy to update farm --- contracts/strategies/stargate/StargateStrategy.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 0109114c..85515fb1 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -26,6 +26,8 @@ contract StargateStrategy is InitializableAbstractStrategy { address public farm; mapping(address => AssetInfo) public assetInfo; + event FarmUpdated(address newFarm); + error IncorrectPoolId(address asset, uint16 pid); error IncorrectRewardPoolId(address asset, uint256 rewardPid); @@ -174,6 +176,18 @@ contract StargateStrategy is InitializableAbstractStrategy { emit RewardTokenCollected(rewardToken, yieldReceiver, harvestAmt); } + /// @notice A function to withdraw from old farm, update farm and deposit in new farm + /// @param _newFarm Address of the new farm + /// @param _asset Address of asset of which lp token is to be withdrawn and deposited + /// @dev Only callable by owner + function updateFarm(address _newFarm, address _asset) external onlyOwner { + uint256 lpTokenAmt = checkLPTokenBalance(_asset); + ILPStaking(farm).withdraw(assetInfo[_asset].rewardPID, lpTokenAmt); + farm = _newFarm; + ILPStaking(_newFarm).deposit(assetInfo[_asset].rewardPID, lpTokenAmt); // Gas savings + emit FarmUpdated(_newFarm); + } + /// @inheritdoc InitializableAbstractStrategy function supportsCollateral(address _asset) public view override returns (bool) { return assetToPToken[_asset] != address(0); From f9289049869aef5f4b55680becb1711915dd289d Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Wed, 29 Nov 2023 18:49:58 +0530 Subject: [PATCH 10/64] dripRate updation reconfigured SPE-27 --- contracts/rebase/Dripper.sol | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/contracts/rebase/Dripper.sol b/contracts/rebase/Dripper.sol index e51b7e9a..1229b288 100644 --- a/contracts/rebase/Dripper.sol +++ b/contracts/rebase/Dripper.sol @@ -52,10 +52,21 @@ contract Dripper is IDripper, Ownable { IERC20(Helpers.USDS).safeTransfer(vault, collectableAmt); emit Collected(collectableAmt); } - dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; + if (IERC20(Helpers.USDS).balanceOf(address(this)) == 0) dripRate = 0; return collectableAmt; } + /// @notice Function to be used to send USDs to dripper and update `dripRate` + /// @param _amount Amount of USDs to be sent form caller to this contract + function addUSDs(uint256 _amount) external { + if (_amount != 0) { + IERC20(Helpers.USDS).safeTransferFrom(msg.sender, address(this), _amount); + dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; + } else { + revert Helpers.InvalidAmount(); + } + } + /// @notice Update the vault address /// @param _vault Address of the new vault function updateVault(address _vault) public onlyOwner { From 9a92c49c935d1c256e50537180a30991af6c39d3 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Thu, 30 Nov 2023 19:32:28 +0530 Subject: [PATCH 11/64] Updated Yield reserve to call addUSDs --- contracts/buyback/YieldReserve.sol | 4 +++- contracts/interfaces/IDripper.sol | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/contracts/buyback/YieldReserve.sol b/contracts/buyback/YieldReserve.sol index 6b4555c4..67a39a18 100644 --- a/contracts/buyback/YieldReserve.sol +++ b/contracts/buyback/YieldReserve.sol @@ -7,6 +7,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IVault} from "../interfaces/IVault.sol"; import {IOracle} from "../interfaces/IOracle.sol"; import {Helpers} from "../libraries/Helpers.sol"; +import {IDripper} from "../interfaces/IDripper.sol"; /// @title YieldReserve of USDs protocol /// @notice The contract allows users to swap the supported stable coins against yield earned by USDs protocol @@ -231,6 +232,7 @@ contract YieldReserve is ReentrancyGuard, Ownable { emit USDsSent(toBuyback, toDripper); IERC20(Helpers.USDS).safeTransfer(buyback, toBuyback); - IERC20(Helpers.USDS).safeTransfer(dripper, toDripper); + IERC20(Helpers.USDS).forceApprove(dripper, toDripper); + IDripper(dripper).addUSDs(toDripper); } } diff --git a/contracts/interfaces/IDripper.sol b/contracts/interfaces/IDripper.sol index 45387e32..7845d518 100644 --- a/contracts/interfaces/IDripper.sol +++ b/contracts/interfaces/IDripper.sol @@ -4,5 +4,7 @@ pragma solidity 0.8.19; interface IDripper { function collect() external returns (uint256); + function addUSDs(uint256 _amount) external; + function getCollectableAmt() external view returns (uint256); } From cf11a11653ec7d552d8bafa0b41134e9282e4af0 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Thu, 30 Nov 2023 19:56:38 +0530 Subject: [PATCH 12/64] Added checks for toDripper and skipped revert --- contracts/buyback/YieldReserve.sol | 6 ++++-- contracts/rebase/Dripper.sol | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/buyback/YieldReserve.sol b/contracts/buyback/YieldReserve.sol index 67a39a18..a151261c 100644 --- a/contracts/buyback/YieldReserve.sol +++ b/contracts/buyback/YieldReserve.sol @@ -232,7 +232,9 @@ contract YieldReserve is ReentrancyGuard, Ownable { emit USDsSent(toBuyback, toDripper); IERC20(Helpers.USDS).safeTransfer(buyback, toBuyback); - IERC20(Helpers.USDS).forceApprove(dripper, toDripper); - IDripper(dripper).addUSDs(toDripper); + if (toDripper != 0) { + IERC20(Helpers.USDS).forceApprove(dripper, toDripper); + IDripper(dripper).addUSDs(toDripper); + } } } diff --git a/contracts/rebase/Dripper.sol b/contracts/rebase/Dripper.sol index 1229b288..383712f2 100644 --- a/contracts/rebase/Dripper.sol +++ b/contracts/rebase/Dripper.sol @@ -62,8 +62,6 @@ contract Dripper is IDripper, Ownable { if (_amount != 0) { IERC20(Helpers.USDS).safeTransferFrom(msg.sender, address(this), _amount); dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; - } else { - revert Helpers.InvalidAmount(); } } From 98fba0b2bffe11de37b0a4857621dec598a070f3 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Thu, 30 Nov 2023 20:28:04 +0530 Subject: [PATCH 13/64] Fixed test cases --- test/rebase/Dripper.t.sol | 6 ++---- test/vault/VaultCore.t.sol | 6 +++++- test/vault/VaultIntegration.t.sol | 6 +++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/test/rebase/Dripper.t.sol b/test/rebase/Dripper.t.sol index d73f1d2a..6ed178d6 100644 --- a/test/rebase/Dripper.t.sol +++ b/test/rebase/Dripper.t.sol @@ -110,11 +110,9 @@ contract Collect is DripperTest { } function test_CollectDripper() external useKnownActor(WHALE_USDS) { - IERC20(USDS).approve(WHALE_USDS, 100000 * 10 ** 18); - IERC20(USDS).transfer(address(dripper), 10000 * 10 ** 18); + IERC20(USDS).approve(address(dripper), 100000 * 10 ** 18); + dripper.addUSDs(10000 * 10 ** 18); // deal(USDS, address(dripper), 1, true); - skip(86400); - dripper.collect(); skip(86400 * 14); vm.expectEmit(true, true, false, true); emit Collected(10000 * 10 ** 18); diff --git a/test/vault/VaultCore.t.sol b/test/vault/VaultCore.t.sol index 4f7f9abe..52ec4a9b 100644 --- a/test/vault/VaultCore.t.sol +++ b/test/vault/VaultCore.t.sol @@ -472,8 +472,12 @@ contract TestRebase is VaultCoreTest { function test_Rebase() public { vm.startPrank(VAULT); IRebaseManager(REBASE_MANAGER).fetchRebaseAmt(); + IUSDs(USDS).mint(actors[1], 1e22); + changePrank(actors[1]); + ERC20(USDS).approve(DRIPPER, 1e22); + IDripper(DRIPPER).addUSDs(1e22); + changePrank(VAULT); skip(1 days); - IUSDs(USDS).mint(DRIPPER, 1e22); IDripper(DRIPPER).collect(); skip(1 days); (uint256 min, uint256 max) = IRebaseManager(REBASE_MANAGER).getMinAndMaxRebaseAmt(); diff --git a/test/vault/VaultIntegration.t.sol b/test/vault/VaultIntegration.t.sol index 993d970c..2398df7f 100644 --- a/test/vault/VaultIntegration.t.sol +++ b/test/vault/VaultIntegration.t.sol @@ -204,8 +204,12 @@ contract TestRebase is VaultCoreTest { function test_Rebase() public useKnownActor(VAULT) { IRebaseManager(REBASE_MANAGER).fetchRebaseAmt(); + IUSDs(USDS).mint(actors[1], 1e22); + changePrank(actors[1]); + ERC20(USDS).approve(DRIPPER, 1e22); + IDripper(DRIPPER).addUSDs(1e22); + changePrank(VAULT); skip(1 days); - IUSDs(USDS).mint(DRIPPER, 1e22); IDripper(DRIPPER).collect(); skip(1 days); (uint256 min, uint256 max) = IRebaseManager(REBASE_MANAGER).getMinAndMaxRebaseAmt(); From 9f3db4ea63be8d9d086a3f75f315dc46848ca6a4 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Fri, 1 Dec 2023 01:50:39 +0530 Subject: [PATCH 14/64] Added recoverERC20 test cases --- test/strategy/StargateStrategy.t.sol | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/strategy/StargateStrategy.t.sol b/test/strategy/StargateStrategy.t.sol index 8dfe8262..1cb14a1f 100644 --- a/test/strategy/StargateStrategy.t.sol +++ b/test/strategy/StargateStrategy.t.sol @@ -613,3 +613,38 @@ contract EdgeCases is StargateStrategyTest { assertTrue(strategy.checkBalance(data.asset) < initialBal); } } + +contract TestRecoverERC20 is StargateStrategyTest { + address token; + address receiver; + uint256 amount; + + function setUp() public override { + super.setUp(); + vm.startPrank(USDS_OWNER); + _initializeStrategy(); + _createDeposits(); + vm.stopPrank(); + token = DAI; + receiver = actors[1]; + amount = 1e22; + } + + function test_RevertsWhen_CallerIsNotOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + strategy.recoverERC20(token, receiver, amount); + } + + function test_RevertsWhen_AmountMoreThanBalance() public useKnownActor(USDS_OWNER) { + vm.expectRevert(); + strategy.recoverERC20(token, receiver, amount); + } + + function test_RecoverERC20() public useKnownActor(USDS_OWNER) { + deal(token, address(strategy), amount); + uint256 balBefore = ERC20(token).balanceOf(receiver); + strategy.recoverERC20(token, receiver, amount); + uint256 balAfter = ERC20(token).balanceOf(receiver); + assertEq(balAfter - balBefore, amount); + } +} From 51259ed52dc7f98a8f1346068886b582c36551e7 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Fri, 1 Dec 2023 02:01:40 +0530 Subject: [PATCH 15/64] Cached rewardPID in updateFarm() --- contracts/strategies/stargate/StargateStrategy.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 85515fb1..24b45e6a 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -181,10 +181,11 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @param _asset Address of asset of which lp token is to be withdrawn and deposited /// @dev Only callable by owner function updateFarm(address _newFarm, address _asset) external onlyOwner { + uint256 _rewardPID = assetInfo[_asset].rewardPID; uint256 lpTokenAmt = checkLPTokenBalance(_asset); - ILPStaking(farm).withdraw(assetInfo[_asset].rewardPID, lpTokenAmt); + ILPStaking(farm).withdraw(_rewardPID, lpTokenAmt); farm = _newFarm; - ILPStaking(_newFarm).deposit(assetInfo[_asset].rewardPID, lpTokenAmt); // Gas savings + ILPStaking(_newFarm).deposit(_rewardPID, lpTokenAmt); // Gas savings emit FarmUpdated(_newFarm); } From 8230f6e8331b6630b9663a6e9ea04cf83b850a58 Mon Sep 17 00:00:00 2001 From: Parv3213 Date: Fri, 1 Dec 2023 13:42:20 +0530 Subject: [PATCH 16/64] Gas Optimization --- .../strategies/stargate/StargateStrategy.sol | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 24b45e6a..c7512923 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -91,8 +91,9 @@ contract StargateStrategy is InitializableAbstractStrategy { IERC20(_asset).safeTransferFrom(msg.sender, address(this), _amount); IERC20(_asset).forceApprove(router, _amount); + AssetInfo storage asset = assetInfo[_asset]; // Add liquidity in the stargate pool. - IStargateRouter(router).addLiquidity(assetInfo[_asset].pid, _amount, address(this)); + IStargateRouter(router).addLiquidity(asset.pid, _amount, address(this)); // Deposit the generated lpToken in the farm. // @dev We are assuming that the 100% of lpToken is deposited in the farm. uint256 lpTokenBal = IERC20(lpToken).balanceOf(address(this)); @@ -103,10 +104,10 @@ contract StargateStrategy is InitializableAbstractStrategy { } // Update the allocated amount in the strategy - assetInfo[_asset].allocatedAmt += depositAmt; + asset.allocatedAmt += depositAmt; IERC20(lpToken).forceApprove(farm, lpTokenBal); - ILPStaking(farm).deposit(assetInfo[_asset].rewardPID, lpTokenBal); + ILPStaking(farm).deposit(asset.rewardPID, lpTokenBal); emit Deposit(_asset, depositAmt); } @@ -137,11 +138,12 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @param _asset Asset to withdraw function emergencyWithdrawToVault(address _asset) external onlyOwner nonReentrant { uint256 lpTokenAmt = checkLPTokenBalance(_asset); + AssetInfo storage asset = assetInfo[_asset]; // Withdraw from LPStaking without caring for rewards - ILPStaking(farm).emergencyWithdraw(assetInfo[_asset].rewardPID); - uint256 amtRecv = IStargateRouter(router).instantRedeemLocal(assetInfo[_asset].pid, lpTokenAmt, vault) + ILPStaking(farm).emergencyWithdraw(asset.rewardPID); + uint256 amtRecv = IStargateRouter(router).instantRedeemLocal(asset.pid, lpTokenAmt, vault) * IStargatePool(assetToPToken[_asset]).convertRate(); - assetInfo[_asset].allocatedAmt -= amtRecv; + asset.allocatedAmt -= amtRecv; emit Withdrawal(_asset, amtRecv); } @@ -221,18 +223,20 @@ contract StargateStrategy is InitializableAbstractStrategy { uint256 lpTokenBal = checkLPTokenBalance(_asset); uint256 collateralBal = _convertToCollateral(_asset, lpTokenBal); - if (collateralBal <= assetInfo[_asset].allocatedAmt) { + uint256 allocatedAmt = assetInfo[_asset].allocatedAmt; + if (collateralBal <= allocatedAmt) { return 0; } - return collateralBal - assetInfo[_asset].allocatedAmt; + return collateralBal - allocatedAmt; } /// @inheritdoc InitializableAbstractStrategy function checkBalance(address _asset) public view override returns (uint256) { uint256 lpTokenBal = checkLPTokenBalance(_asset); uint256 calcCollateralBal = _convertToCollateral(_asset, lpTokenBal); - if (assetInfo[_asset].allocatedAmt <= calcCollateralBal) { - return assetInfo[_asset].allocatedAmt; + uint256 allocatedAmt = assetInfo[_asset].allocatedAmt; + if (allocatedAmt <= calcCollateralBal) { + return allocatedAmt; } return calcCollateralBal; } @@ -241,10 +245,11 @@ contract StargateStrategy is InitializableAbstractStrategy { function checkAvailableBalance(address _asset) public view override returns (uint256) { IStargatePool pool = IStargatePool(assetToPToken[_asset]); uint256 availableFunds = _convertToCollateral(_asset, pool.deltaCredit()); - if (availableFunds <= assetInfo[_asset].allocatedAmt) { + uint256 allocatedAmt = assetInfo[_asset].allocatedAmt; + if (availableFunds <= allocatedAmt) { return availableFunds; } - return assetInfo[_asset].allocatedAmt; + return allocatedAmt; } /// @inheritdoc InitializableAbstractStrategy @@ -290,16 +295,17 @@ contract StargateStrategy is InitializableAbstractStrategy { Helpers._isNonZeroAddr(_recipient); Helpers._isNonZeroAmt(_amount, "Must withdraw something"); uint256 lpTokenAmt = _convertToPToken(_asset, _amount); - ILPStaking(farm).withdraw(assetInfo[_asset].rewardPID, lpTokenAmt); + AssetInfo storage asset = assetInfo[_asset]; + ILPStaking(farm).withdraw(asset.rewardPID, lpTokenAmt); uint256 minRecvAmt = (_amount * (Helpers.MAX_PERCENTAGE - withdrawSlippage)) / Helpers.MAX_PERCENTAGE; - uint256 amtRecv = IStargateRouter(router).instantRedeemLocal(assetInfo[_asset].pid, lpTokenAmt, _recipient) + uint256 amtRecv = IStargateRouter(router).instantRedeemLocal(asset.pid, lpTokenAmt, _recipient) * IStargatePool(assetToPToken[_asset]).convertRate(); if (amtRecv < minRecvAmt) { revert Helpers.MinSlippageError(amtRecv, minRecvAmt); } if (!_withdrawInterest) { - assetInfo[_asset].allocatedAmt -= amtRecv; + asset.allocatedAmt -= amtRecv; emit Withdrawal(_asset, amtRecv); } From 8c98c51bd42b27a7feb72c1e177aa01134e07df1 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 1 Dec 2023 14:09:44 +0530 Subject: [PATCH 17/64] Revert "Cached rewardPID in updateFarm()" This reverts commit 51259ed52dc7f98a8f1346068886b582c36551e7. --- contracts/strategies/stargate/StargateStrategy.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index c7512923..9bc51bc8 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -183,11 +183,10 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @param _asset Address of asset of which lp token is to be withdrawn and deposited /// @dev Only callable by owner function updateFarm(address _newFarm, address _asset) external onlyOwner { - uint256 _rewardPID = assetInfo[_asset].rewardPID; uint256 lpTokenAmt = checkLPTokenBalance(_asset); - ILPStaking(farm).withdraw(_rewardPID, lpTokenAmt); + ILPStaking(farm).withdraw(assetInfo[_asset].rewardPID, lpTokenAmt); farm = _newFarm; - ILPStaking(_newFarm).deposit(_rewardPID, lpTokenAmt); // Gas savings + ILPStaking(_newFarm).deposit(assetInfo[_asset].rewardPID, lpTokenAmt); // Gas savings emit FarmUpdated(_newFarm); } From 1816accbd01fb29ac6e12087e1c920b782c9808c Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 1 Dec 2023 14:10:10 +0530 Subject: [PATCH 18/64] Revert "Updated Stargate strategy to update farm" This reverts commit 9175d360396158b89b4b82ac1c41e3ee5d2553f3. --- contracts/strategies/stargate/StargateStrategy.sol | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 9bc51bc8..c55fce2f 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -26,8 +26,6 @@ contract StargateStrategy is InitializableAbstractStrategy { address public farm; mapping(address => AssetInfo) public assetInfo; - event FarmUpdated(address newFarm); - error IncorrectPoolId(address asset, uint16 pid); error IncorrectRewardPoolId(address asset, uint256 rewardPid); @@ -178,18 +176,6 @@ contract StargateStrategy is InitializableAbstractStrategy { emit RewardTokenCollected(rewardToken, yieldReceiver, harvestAmt); } - /// @notice A function to withdraw from old farm, update farm and deposit in new farm - /// @param _newFarm Address of the new farm - /// @param _asset Address of asset of which lp token is to be withdrawn and deposited - /// @dev Only callable by owner - function updateFarm(address _newFarm, address _asset) external onlyOwner { - uint256 lpTokenAmt = checkLPTokenBalance(_asset); - ILPStaking(farm).withdraw(assetInfo[_asset].rewardPID, lpTokenAmt); - farm = _newFarm; - ILPStaking(_newFarm).deposit(assetInfo[_asset].rewardPID, lpTokenAmt); // Gas savings - emit FarmUpdated(_newFarm); - } - /// @inheritdoc InitializableAbstractStrategy function supportsCollateral(address _asset) public view override returns (bool) { return assetToPToken[_asset] != address(0); From 27b1ee599bee6754b45349d78818346c659e2ce1 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Mon, 4 Dec 2023 19:38:59 +0530 Subject: [PATCH 19/64] Added invalid amount check --- contracts/rebase/Dripper.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/rebase/Dripper.sol b/contracts/rebase/Dripper.sol index 383712f2..9569ec1c 100644 --- a/contracts/rebase/Dripper.sol +++ b/contracts/rebase/Dripper.sol @@ -59,10 +59,9 @@ contract Dripper is IDripper, Ownable { /// @notice Function to be used to send USDs to dripper and update `dripRate` /// @param _amount Amount of USDs to be sent form caller to this contract function addUSDs(uint256 _amount) external { - if (_amount != 0) { - IERC20(Helpers.USDS).safeTransferFrom(msg.sender, address(this), _amount); - dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; - } + Helpers._isNonZeroAmt(_amount); + IERC20(Helpers.USDS).safeTransferFrom(msg.sender, address(this), _amount); + dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; } /// @notice Update the vault address From 0cc65e37e96c33f74fe41b02e73791ecd00523a6 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Mon, 4 Dec 2023 19:52:31 +0530 Subject: [PATCH 20/64] Removed extra check --- contracts/rebase/Dripper.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/rebase/Dripper.sol b/contracts/rebase/Dripper.sol index 11016f76..9569ec1c 100644 --- a/contracts/rebase/Dripper.sol +++ b/contracts/rebase/Dripper.sol @@ -53,7 +53,6 @@ contract Dripper is IDripper, Ownable { emit Collected(collectableAmt); } if (IERC20(Helpers.USDS).balanceOf(address(this)) == 0) dripRate = 0; - if (IERC20(Helpers.USDS).balanceOf(address(this)) == 0) dripRate = 0; return collectableAmt; } From 7b8198d759afe88a053914379cf413159352a546 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Tue, 5 Dec 2023 16:59:30 +0530 Subject: [PATCH 21/64] Review comments to fix number units --- test/buyback/SPABuyback.t.sol | 32 +++++++++++++++---------------- test/buyback/YieldReserve.t.sol | 6 +++--- test/oracle/ChainlinkOracle.t.sol | 2 +- test/rebase/Dripper.t.sol | 21 +++++++++----------- test/rebase/RebaseManager.t.sol | 14 +++++++------- test/vault/VaultCore.t.sol | 6 +++--- test/vault/VaultIntegration.t.sol | 7 ++++--- 7 files changed, 43 insertions(+), 45 deletions(-) diff --git a/test/buyback/SPABuyback.t.sol b/test/buyback/SPABuyback.t.sol index 20d846f7..b1de3fdf 100644 --- a/test/buyback/SPABuyback.t.sol +++ b/test/buyback/SPABuyback.t.sol @@ -25,10 +25,10 @@ contract SPABuybackTestSetup is BaseTest { modifier mockOracle() { vm.mockCall( - address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDS), abi.encode(995263234350000000, 1e18) + address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDS), abi.encode(995263234350000000, 1 ether) ); vm.mockCall( - address(ORACLE), abi.encodeWithSignature("getPrice(address)", SPA), abi.encode(4729390000000000, 1e18) + address(ORACLE), abi.encodeWithSignature("getPrice(address)", SPA), abi.encode(4729390000000000, 1 ether) ); _; vm.clearMockedCalls(); @@ -102,8 +102,8 @@ contract TestGetters is SPABuybackTestSetup { function setUp() public override { super.setUp(); - usdsAmount = 100e18; - spaIn = 100000e18; + usdsAmount = 100 ether; + spaIn = 1e5 ether; } function testGetSpaReqdForUSDs() public mockOracle { @@ -195,7 +195,7 @@ contract TestWithdraw is SPABuybackTestSetup { function setUp() public override { super.setUp(); token = USDS; - amount = 100e18; + amount = 100 ether; vm.prank(VAULT); IUSDs(USDS).mint(address(spaBuyback), amount); @@ -214,7 +214,7 @@ contract TestWithdraw is SPABuybackTestSetup { function testCannotWithdrawMoreThanBalance() public useKnownActor(USDS_OWNER) { amount = IERC20(USDS).balanceOf(address(spaBuyback)); - amount = amount + 100e18; + amount = amount + 100 ether; vm.expectRevert("Transfer greater than balance"); spaBuyback.withdraw(token, user, amount); } @@ -251,7 +251,7 @@ contract TestBuyUSDs is SPABuybackTestSetup { function setUp() public override { super.setUp(); - spaIn = 100000e18; + spaIn = 1e5 ether; minUSDsOut = 1; } @@ -262,8 +262,8 @@ contract TestBuyUSDs is SPABuybackTestSetup { } function testCannotIfSlippageMoreThanExpected() public mockOracle { - minUSDsOut = spaBuyback.getUsdsOutForSpa(spaIn) + 100e18; - vm.expectRevert(abi.encodeWithSelector(Helpers.MinSlippageError.selector, minUSDsOut - 100e18, minUSDsOut)); + minUSDsOut = spaBuyback.getUsdsOutForSpa(spaIn) + 100 ether; + vm.expectRevert(abi.encodeWithSelector(Helpers.MinSlippageError.selector, minUSDsOut - 100 ether, minUSDsOut)); spaBuyback.buyUSDs(spaIn, minUSDsOut); } @@ -276,7 +276,7 @@ contract TestBuyUSDs is SPABuybackTestSetup { function testBuyUSDs() public mockOracle { minUSDsOut = _calculateUSDsForSpaIn(spaIn); vm.prank(VAULT); - IUSDs(USDS).mint(address(spaBuyback), minUSDsOut + 10e18); + IUSDs(USDS).mint(address(spaBuyback), minUSDsOut + 10 ether); spaTotalSupply.balBefore = IERC20(SPA).totalSupply(); spaBal.balBefore = IERC20(SPA).balanceOf(VESPA_REWARDER); deal(SPA, user, spaIn); @@ -300,13 +300,13 @@ contract TestBuyUSDs is SPABuybackTestSetup { // Testing with fuzzing function testBuyUSDs(uint256 spaIn, uint256 spaPrice, uint256 usdsPrice) public { usdsPrice = bound(usdsPrice, 7e17, 13e17); - spaPrice = bound(spaPrice, 1e15, 1e20); - spaIn = bound(spaIn, 1e18, 1e27); - uint256 swapValue = (spaIn * spaPrice) / 1e18; - vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDS), abi.encode(usdsPrice, 1e18)); - vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", SPA), abi.encode(spaPrice, 1e18)); + spaPrice = bound(spaPrice, 1e15, 100 ether); + spaIn = bound(spaIn, 1 ether, 1e9 ether); + uint256 swapValue = (spaIn * spaPrice) / 1 ether; + vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDS), abi.encode(usdsPrice, 1 ether)); + vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", SPA), abi.encode(spaPrice, 1 ether)); minUSDsOut = _calculateUSDsForSpaIn(spaIn); - if (swapValue > 1e18 && minUSDsOut > 1e18) { + if (swapValue > 1 ether && minUSDsOut > 1 ether) { vm.prank(VAULT); IUSDs(USDS).mint(address(spaBuyback), minUSDsOut); deal(SPA, user, spaIn); diff --git a/test/buyback/YieldReserve.t.sol b/test/buyback/YieldReserve.t.sol index 3fd53014..f4dad687 100644 --- a/test/buyback/YieldReserve.t.sol +++ b/test/buyback/YieldReserve.t.sol @@ -227,8 +227,8 @@ contract YieldReserveTest is YieldReserveSetup { } function test_getTokenBForTokenA_inputs() public useKnownActor(USDS_OWNER) { - mockPrice(USDCe, 10e8, PRICE_PRECISION); - mockPrice(USDS, 10e8, PRICE_PRECISION); + mockPrice(USDCe, 1 gwei, PRICE_PRECISION); + mockPrice(USDS, 1 gwei, PRICE_PRECISION); vm.expectRevert(abi.encodeWithSelector(YieldReserve.InvalidSourceToken.selector)); yieldReserve.getTokenBForTokenA(USDS, USDCe, 10000); @@ -250,7 +250,7 @@ contract YieldReserveTest is YieldReserveSetup { yieldReserve.toggleSrcTokenPermission(USDS, true); yieldReserve.toggleDstTokenPermission(USDCe, true); - assertEq(getTokenData(USDCe).conversionFactor, 1e12); + assertEq(getTokenData(USDCe).conversionFactor, 1e3 gwei); assertEq(getTokenData(USDS).conversionFactor, 1); uint256 amount = yieldReserve.getTokenBForTokenA(USDS, USDCe, amountIn * USDsPrecision); diff --git a/test/oracle/ChainlinkOracle.t.sol b/test/oracle/ChainlinkOracle.t.sol index f17e877f..c900f5b0 100644 --- a/test/oracle/ChainlinkOracle.t.sol +++ b/test/oracle/ChainlinkOracle.t.sol @@ -40,7 +40,7 @@ contract Test_SetTokenData is ChainlinkOracleTest { function test_revertsWhen_InvalidPrecision() public useKnownActor(USDS_OWNER) { ChainlinkOracle.SetupTokenData memory tokenData = _getTokenData()[0]; - tokenData.data.pricePrecision = 1e9; + tokenData.data.pricePrecision = 1 gwei; vm.expectRevert(abi.encodeWithSelector(ChainlinkOracle.InvalidPricePrecision.selector)); chainlinkOracle.setTokenData(tokenData.token, tokenData.data); } diff --git a/test/rebase/Dripper.t.sol b/test/rebase/Dripper.t.sol index 6ed178d6..2d911c82 100644 --- a/test/rebase/Dripper.t.sol +++ b/test/rebase/Dripper.t.sol @@ -2,16 +2,11 @@ pragma solidity 0.8.19; import {BaseTest} from ".././utils/BaseTest.sol"; import {Dripper, Helpers} from "../../contracts/rebase/Dripper.sol"; +import {IUSDs} from "../../contracts/interfaces/IUSDs.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; address constant WHALE_USDS = 0x50450351517117Cb58189edBa6bbaD6284D45902; -interface IUSDS { - function approve(address _spender, uint256 _value) external returns (bool); - - function transferFrom(address _from, address _to, uint256 _value) external returns (bool); -} - contract DripperTest is BaseTest { // Init Variables. Dripper public dripper; @@ -27,7 +22,7 @@ contract DripperTest is BaseTest { function setUp() public override { super.setUp(); setArbitrumFork(); - dripper = new Dripper(VAULT, (86400 * 7)); + dripper = new Dripper(VAULT, (7 days)); dripper.transferOwnership(USDS_OWNER); } } @@ -110,12 +105,14 @@ contract Collect is DripperTest { } function test_CollectDripper() external useKnownActor(WHALE_USDS) { - IERC20(USDS).approve(address(dripper), 100000 * 10 ** 18); - dripper.addUSDs(10000 * 10 ** 18); - // deal(USDS, address(dripper), 1, true); - skip(86400 * 14); + changePrank(VAULT); + IUSDs(USDS).mint(WHALE_USDS, 1e6 ether); + changePrank(WHALE_USDS); + IERC20(USDS).approve(address(dripper), 1e6 ether); + dripper.addUSDs(1e6 ether); + skip(14 days); vm.expectEmit(true, true, false, true); - emit Collected(10000 * 10 ** 18); + emit Collected(1e6 ether); dripper.collect(); } } diff --git a/test/rebase/RebaseManager.t.sol b/test/rebase/RebaseManager.t.sol index 273daf98..425ba502 100644 --- a/test/rebase/RebaseManager.t.sol +++ b/test/rebase/RebaseManager.t.sol @@ -101,7 +101,7 @@ contract UpdateGap is RebaseManagerTest { function test_RevertWhen_CallerIsNotOwner() external useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); - rebaseManager.updateGap(86400 * 7); + rebaseManager.updateGap(7 days); } function test_UpdateGap(uint256 gap) external useKnownActor(USDS_OWNER) { @@ -147,25 +147,25 @@ contract FetchRebaseAmt is RebaseManagerTest { IOracle.PriceData memory usdceData = IOracle(ORACLE).getPrice(USDS); uint256 usdcePrice = usdceData.price; uint256 usdcePrecision = usdceData.precision; - skip(86400 * 10); + skip(10 days); // Using mock call to set price feed as we are skipping 10 days into the future and oracle will not have the data for that day. vm.mockCall( address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDCe), abi.encode(usdcePrice, usdcePrecision) ); // Minting USDs - mintUSDs(1e11); + mintUSDs(100 gwei); vm.prank(USDS_OWNER); - IERC20(USDS).transfer(address(dripper), 1e22); + IERC20(USDS).transfer(address(dripper), 10000 ether); vm.startPrank(VAULT); dripper.collect(); console.log("Day 1 After Minting USDs"); - skip(86400 * 1); + skip(1 days); (uint256 min, uint256 max) = rebaseManager.getMinAndMaxRebaseAmt(); console.log("Min Rebase Amt", min / (10 ** 18), "max Rebase Amt", max / (10 ** 18)); uint256 collectable0 = dripper.getCollectableAmt(); console.log("collectable0", collectable0 / 10 ** 18); - skip(86400 * 1); + skip(1 days); console.log("Day 2 After Minting USDs"); (uint256 min2, uint256 max2) = rebaseManager.getMinAndMaxRebaseAmt(); console.log("Min Rebase Amt", min2 / (10 ** 18), "max Rebase Amt", max2 / (10 ** 18)); @@ -175,7 +175,7 @@ contract FetchRebaseAmt is RebaseManagerTest { console.log("Rebase Amount", rebaseAmt1 / 10 ** 18); // Trying to collect from dripper after rebase dripper.collect(); - skip(86400 * 1); + skip(1 days); console.log("Day 3 After Minting USDs"); (uint256 min3, uint256 max3) = rebaseManager.getMinAndMaxRebaseAmt(); console.log("Min Rebase Amt", min3 / (10 ** 18), "max Rebase Amt", max3 / (10 ** 18)); diff --git a/test/vault/VaultCore.t.sol b/test/vault/VaultCore.t.sol index d4bbc5b7..43fcc5f3 100644 --- a/test/vault/VaultCore.t.sol +++ b/test/vault/VaultCore.t.sol @@ -474,10 +474,10 @@ contract TestRebase is VaultCoreTest { function test_Rebase() public { vm.startPrank(VAULT); IRebaseManager(REBASE_MANAGER).fetchRebaseAmt(); - IUSDs(USDS).mint(actors[1], 1e22); + IUSDs(USDS).mint(actors[1], 1e5 ether); changePrank(actors[1]); - ERC20(USDS).approve(DRIPPER, 1e22); - IDripper(DRIPPER).addUSDs(1e22); + ERC20(USDS).approve(DRIPPER, 1e5 ether); + IDripper(DRIPPER).addUSDs(1e5 ether); changePrank(VAULT); skip(1 days); IDripper(DRIPPER).collect(); diff --git a/test/vault/VaultIntegration.t.sol b/test/vault/VaultIntegration.t.sol index 2398df7f..50c01f3d 100644 --- a/test/vault/VaultIntegration.t.sol +++ b/test/vault/VaultIntegration.t.sol @@ -203,11 +203,12 @@ contract TestRebase is VaultCoreTest { event RebasedUSDs(uint256 rebaseAmt); function test_Rebase() public useKnownActor(VAULT) { + uint256 _amount = 1e5 ether; IRebaseManager(REBASE_MANAGER).fetchRebaseAmt(); - IUSDs(USDS).mint(actors[1], 1e22); + IUSDs(USDS).mint(actors[1], _amount); changePrank(actors[1]); - ERC20(USDS).approve(DRIPPER, 1e22); - IDripper(DRIPPER).addUSDs(1e22); + ERC20(USDS).approve(DRIPPER, _amount); + IDripper(DRIPPER).addUSDs(_amount); changePrank(VAULT); skip(1 days); IDripper(DRIPPER).collect(); From 5ba139d81a713d168a3ab7a794763866d8749124 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Wed, 29 Nov 2023 16:21:00 +0530 Subject: [PATCH 22/64] Updated Stargate strategy to update farm --- contracts/strategies/stargate/StargateStrategy.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index c55fce2f..9bc51bc8 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -26,6 +26,8 @@ contract StargateStrategy is InitializableAbstractStrategy { address public farm; mapping(address => AssetInfo) public assetInfo; + event FarmUpdated(address newFarm); + error IncorrectPoolId(address asset, uint16 pid); error IncorrectRewardPoolId(address asset, uint256 rewardPid); @@ -176,6 +178,18 @@ contract StargateStrategy is InitializableAbstractStrategy { emit RewardTokenCollected(rewardToken, yieldReceiver, harvestAmt); } + /// @notice A function to withdraw from old farm, update farm and deposit in new farm + /// @param _newFarm Address of the new farm + /// @param _asset Address of asset of which lp token is to be withdrawn and deposited + /// @dev Only callable by owner + function updateFarm(address _newFarm, address _asset) external onlyOwner { + uint256 lpTokenAmt = checkLPTokenBalance(_asset); + ILPStaking(farm).withdraw(assetInfo[_asset].rewardPID, lpTokenAmt); + farm = _newFarm; + ILPStaking(_newFarm).deposit(assetInfo[_asset].rewardPID, lpTokenAmt); // Gas savings + emit FarmUpdated(_newFarm); + } + /// @inheritdoc InitializableAbstractStrategy function supportsCollateral(address _asset) public view override returns (bool) { return assetToPToken[_asset] != address(0); From 0d7fdac60c9bb198a1decefe4ba9023cf84628de Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Fri, 1 Dec 2023 02:01:40 +0530 Subject: [PATCH 23/64] Cached rewardPID in updateFarm() --- contracts/strategies/stargate/StargateStrategy.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 9bc51bc8..c7512923 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -183,10 +183,11 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @param _asset Address of asset of which lp token is to be withdrawn and deposited /// @dev Only callable by owner function updateFarm(address _newFarm, address _asset) external onlyOwner { + uint256 _rewardPID = assetInfo[_asset].rewardPID; uint256 lpTokenAmt = checkLPTokenBalance(_asset); - ILPStaking(farm).withdraw(assetInfo[_asset].rewardPID, lpTokenAmt); + ILPStaking(farm).withdraw(_rewardPID, lpTokenAmt); farm = _newFarm; - ILPStaking(_newFarm).deposit(assetInfo[_asset].rewardPID, lpTokenAmt); // Gas savings + ILPStaking(_newFarm).deposit(_rewardPID, lpTokenAmt); // Gas savings emit FarmUpdated(_newFarm); } From 0f33ebb5e4c5fa2d64b6e01691a286fad7a53626 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Tue, 5 Dec 2023 20:02:14 +0530 Subject: [PATCH 24/64] Updated updateFarm function --- .../strategies/stargate/StargateStrategy.sol | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index c7512923..9d5439fc 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -180,14 +180,25 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @notice A function to withdraw from old farm, update farm and deposit in new farm /// @param _newFarm Address of the new farm - /// @param _asset Address of asset of which lp token is to be withdrawn and deposited /// @dev Only callable by owner - function updateFarm(address _newFarm, address _asset) external onlyOwner { - uint256 _rewardPID = assetInfo[_asset].rewardPID; - uint256 lpTokenAmt = checkLPTokenBalance(_asset); - ILPStaking(farm).withdraw(_rewardPID, lpTokenAmt); + function updateFarm(address _newFarm) external onlyOwner { + address _oldFarm = farm; + uint256 _numAssets = assetsMapped.length; + address _asset; + uint256 _rewardPID; + uint256 _lpTokenAmt; + for (uint8 i = 0; i < _numAssets;) { + _asset = assetsMapped[i]; + _rewardPID = assetInfo[_asset].rewardPID; + _lpTokenAmt = checkLPTokenBalance(_asset); + ILPStaking(_oldFarm).withdraw(_rewardPID, _lpTokenAmt); + IERC20(assetToPToken[_asset]).forceApprove(_newFarm, _lpTokenAmt); + ILPStaking(_newFarm).deposit(_rewardPID, _lpTokenAmt); + unchecked { + ++i; + } + } farm = _newFarm; - ILPStaking(_newFarm).deposit(_rewardPID, lpTokenAmt); // Gas savings emit FarmUpdated(_newFarm); } From e080048d99aa3966e7b2a25c4f5d824985d009ea Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Wed, 6 Dec 2023 18:41:32 +0530 Subject: [PATCH 25/64] Edited updateFarm to withdraw & deposit all tokens --- test/strategy/StargateStrategy.t.sol | 38 +++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/test/strategy/StargateStrategy.t.sol b/test/strategy/StargateStrategy.t.sol index 1cb14a1f..9065e054 100644 --- a/test/strategy/StargateStrategy.t.sol +++ b/test/strategy/StargateStrategy.t.sol @@ -38,6 +38,8 @@ contract StargateStrategyTest is BaseStrategy, BaseTest { StargateStrategy internal strategy; address internal proxyAddress; + event FarmUpdated(address _newFarm); + // Test errors error IncorrectPoolId(address asset, uint16 pid); error IncorrectRewardPoolId(address asset, uint256 rewardPid); @@ -627,7 +629,7 @@ contract TestRecoverERC20 is StargateStrategyTest { vm.stopPrank(); token = DAI; receiver = actors[1]; - amount = 1e22; + amount = 1000 * 10 ** ERC20(token).decimals(); } function test_RevertsWhen_CallerIsNotOwner() public { @@ -648,3 +650,37 @@ contract TestRecoverERC20 is StargateStrategyTest { assertEq(balAfter - balBefore, amount); } } + +contract TestUpdateFarm is StargateStrategyTest { + address _newFarm; + + function setUp() public override { + super.setUp(); + vm.startPrank(USDS_OWNER); + _initializeStrategy(); + _createDeposits(); + vm.stopPrank(); + _newFarm = 0xeA8DfEE1898a7e0a59f7527F076106d7e44c2176; + } + + function test_revertsWhen_CallerIsNotOwner() public { + vm.expectRevert("Ownable: caller is not the owner"); + strategy.updateFarm(_newFarm); + } + + function test_UpdateFarm() public useKnownActor(USDS_OWNER) { + uint256 _length = assetData.length; + uint256[] memory oldFarmBalances = new uint256[](_length); + uint256[] memory newFarmBalances = new uint256[](_length); + for (uint8 i = 0; i < _length; ++i) { + (oldFarmBalances[i],) = ILPStaking(STARGATE_FARM).userInfo(assetData[i].rewardPid, address(strategy)); + } + vm.expectEmit(true, true, true, true); + emit FarmUpdated(_newFarm); + strategy.updateFarm(_newFarm); + for (uint8 i = 0; i < assetData.length; ++i) { + (newFarmBalances[i],) = ILPStaking(_newFarm).userInfo(assetData[i].rewardPid, address(strategy)); + assertEq(oldFarmBalances[i], newFarmBalances[i], "Mismatch in balance"); + } + } +} From be8deab592813969c4895b48ccfa0006b3c79958 Mon Sep 17 00:00:00 2001 From: arcantheon <140178288+arcantheon@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:46:19 +0530 Subject: [PATCH 26/64] Update contracts/strategies/stargate/StargateStrategy.sol Co-authored-by: Parv Garg --- contracts/strategies/stargate/StargateStrategy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 54c2e6c2..5079bbfa 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -181,7 +181,7 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @notice A function to withdraw from old farm, update farm and deposit in new farm /// @param _newFarm Address of the new farm /// @dev Only callable by owner - function updateFarm(address _newFarm) external onlyOwner { + function updateFarm(address _newFarm) external nonReentrant onlyOwner { address _oldFarm = farm; uint256 _numAssets = assetsMapped.length; address _asset; From 5231a44fea560c052633fa8b1594682a358df178 Mon Sep 17 00:00:00 2001 From: arcantheon <140178288+arcantheon@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:46:29 +0530 Subject: [PATCH 27/64] Update contracts/strategies/stargate/StargateStrategy.sol Co-authored-by: Parv Garg --- contracts/strategies/stargate/StargateStrategy.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 5079bbfa..909b642a 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -187,7 +187,7 @@ contract StargateStrategy is InitializableAbstractStrategy { address _asset; uint256 _rewardPID; uint256 _lpTokenAmt; - for (uint8 i = 0; i < _numAssets;) { + for (uint8 i; i < _numAssets;) { _asset = assetsMapped[i]; _rewardPID = assetInfo[_asset].rewardPID; _lpTokenAmt = checkLPTokenBalance(_asset); From 02c62ef5308a4fb8c59a69b904ccac2fd42475bb Mon Sep 17 00:00:00 2001 From: YashP16 Date: Tue, 12 Dec 2023 15:48:37 +0530 Subject: [PATCH 28/64] refactor(contracts/strategies): Update `checkRewardEarned()` response. Issue: Each strategy has its own reward harvesting method, returning inconsistent response while querying the reward earned. Fix: Add a consistent response struct for the function. Changes: * Update the strategy contracts. * Update the related test files. --- .../InitializableAbstractStrategy.sol | 7 ++++- contracts/strategies/aave/AaveStrategy.sol | 10 +++---- .../strategies/compound/CompoundStrategy.sol | 7 +++-- .../strategies/stargate/StargateStrategy.sol | 30 ++++++++++--------- test/strategy/AaveStrategy.t.sol | 4 +-- test/strategy/CompoundStrategy.t.sol | 6 ++-- test/strategy/StargateStrategy.t.sol | 15 +++++----- 7 files changed, 44 insertions(+), 35 deletions(-) diff --git a/contracts/strategies/InitializableAbstractStrategy.sol b/contracts/strategies/InitializableAbstractStrategy.sol index 76e8de06..86037243 100755 --- a/contracts/strategies/InitializableAbstractStrategy.sol +++ b/contracts/strategies/InitializableAbstractStrategy.sol @@ -15,6 +15,11 @@ interface IStrategyVault { abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; + struct RewardData { + address token; // Reward token + uint256 amount; // Collectible amount + } + address public vault; uint16 public withdrawSlippage; uint16 public depositSlippage; @@ -137,7 +142,7 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade function checkInterestEarned(address _asset) external view virtual returns (uint256); /// @notice Get the amount of claimable reward - function checkRewardEarned() external view virtual returns (uint256); + function checkRewardEarned() external view virtual returns (RewardData[] memory); /// @notice Get the total LP token balance for a asset. /// @param _asset Address of the asset. diff --git a/contracts/strategies/aave/AaveStrategy.sol b/contracts/strategies/aave/AaveStrategy.sol index 433c64a6..16fb7d6c 100755 --- a/contracts/strategies/aave/AaveStrategy.sol +++ b/contracts/strategies/aave/AaveStrategy.sol @@ -107,6 +107,11 @@ contract AaveStrategy is InitializableAbstractStrategy { revert NoRewardIncentive(); } + /// @inheritdoc InitializableAbstractStrategy + function checkRewardEarned() external pure override returns (RewardData[] memory) { + return (new RewardData[](0)); + } + /// @inheritdoc InitializableAbstractStrategy function checkInterestEarned(address _asset) public view override returns (uint256) { uint256 balance = checkLPTokenBalance(_asset); @@ -147,11 +152,6 @@ contract AaveStrategy is InitializableAbstractStrategy { return assetToPToken[_asset] != address(0); } - /// @inheritdoc InitializableAbstractStrategy - function checkRewardEarned() public pure override returns (uint256) { - return 0; - } - /// @notice Withdraw asset from Aave lending Pool /// @param _recipient Address to receive withdrawn asset /// @param _asset Address of asset to withdraw diff --git a/contracts/strategies/compound/CompoundStrategy.sol b/contracts/strategies/compound/CompoundStrategy.sol index 410e663f..d13a7f2d 100644 --- a/contracts/strategies/compound/CompoundStrategy.sol +++ b/contracts/strategies/compound/CompoundStrategy.sol @@ -118,8 +118,9 @@ contract CompoundStrategy is InitializableAbstractStrategy { } /// @inheritdoc InitializableAbstractStrategy - function checkRewardEarned() external view override returns (uint256 total) { + function checkRewardEarned() external view override returns (RewardData[] memory) { uint256 numAssets = assetsMapped.length; + RewardData[] memory rewardData = new RewardData[](numAssets); for (uint256 i; i < numAssets;) { address lpToken = assetToPToken[assetsMapped[i]]; uint256 accrued = uint256(IComet(lpToken).baseTrackingAccrued(address(this))); @@ -131,12 +132,12 @@ contract CompoundStrategy is InitializableAbstractStrategy { } accrued = ((accrued * config.multiplier) / FACTOR_SCALE); - // assuming homogeneous reward tokens - total += accrued - rewardPool.rewardsClaimed(lpToken, address(this)); + rewardData[i] = RewardData(config.token, accrued - rewardPool.rewardsClaimed(lpToken, address(this))); unchecked { ++i; } } + return rewardData; } /// @inheritdoc InitializableAbstractStrategy diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index f473e1f0..4bde9760 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -177,19 +177,7 @@ contract StargateStrategy is InitializableAbstractStrategy { } /// @inheritdoc InitializableAbstractStrategy - function supportsCollateral(address _asset) public view override returns (bool) { - return assetToPToken[_asset] != address(0); - } - - /// @notice Get the amount STG pending to be collected. - /// @param _asset Address for the asset - function checkPendingRewards(address _asset) public view returns (uint256) { - if (!supportsCollateral(_asset)) revert CollateralNotSupported(_asset); - return ILPStaking(farm).pendingEmissionToken(assetInfo[_asset].rewardPID, address(this)); - } - - /// @inheritdoc InitializableAbstractStrategy - function checkRewardEarned() public view override returns (uint256) { + function checkRewardEarned() external view override returns (RewardData[] memory) { uint256 pendingRewards = 0; uint256 numAssets = assetsMapped.length; for (uint256 i; i < numAssets;) { @@ -200,7 +188,21 @@ contract StargateStrategy is InitializableAbstractStrategy { } } uint256 claimedRewards = IERC20(rewardTokenAddress[0]).balanceOf(address(this)); - return claimedRewards + pendingRewards; + RewardData[] memory rewardData = new RewardData[](1); + rewardData[0] = RewardData(rewardTokenAddress[0], claimedRewards + pendingRewards); + return rewardData; + } + + /// @inheritdoc InitializableAbstractStrategy + function supportsCollateral(address _asset) public view override returns (bool) { + return assetToPToken[_asset] != address(0); + } + + /// @notice Get the amount STG pending to be collected. + /// @param _asset Address for the asset + function checkPendingRewards(address _asset) public view returns (uint256) { + if (!supportsCollateral(_asset)) revert CollateralNotSupported(_asset); + return ILPStaking(farm).pendingEmissionToken(assetInfo[_asset].rewardPID, address(this)); } /// @inheritdoc InitializableAbstractStrategy diff --git a/test/strategy/AaveStrategy.t.sol b/test/strategy/AaveStrategy.t.sol index abcca591..e30dca2e 100644 --- a/test/strategy/AaveStrategy.t.sol +++ b/test/strategy/AaveStrategy.t.sol @@ -349,8 +349,8 @@ contract MiscellaneousTest is AaveStrategyTest { } function test_CheckRewardEarned() public { - uint256 reward = strategy.checkRewardEarned(); - assertEq(reward, 0); + InitializableAbstractStrategy.RewardData[] memory rewardData = strategy.checkRewardEarned(); + assertEq(rewardData.length, 0); } function test_CheckBalance() public { diff --git a/test/strategy/CompoundStrategy.t.sol b/test/strategy/CompoundStrategy.t.sol index d7f0f042..e9d9dff7 100644 --- a/test/strategy/CompoundStrategy.t.sol +++ b/test/strategy/CompoundStrategy.t.sol @@ -340,8 +340,8 @@ contract CheckRewardEarnedTest is CompoundStrategyTest { vm.warp(block.timestamp + 10 days); vm.mockCall(P_TOKEN, abi.encodeWithSignature("baseTrackingAccrued(address)"), abi.encode(10000000)); IComet(P_TOKEN).accrueAccount(address(strategy)); - uint256 reward = strategy.checkRewardEarned(); - assertNotEq(reward, 0); + CompoundStrategy.RewardData[] memory rewardData = strategy.checkRewardEarned(); + assert(rewardData.length > 0); } } @@ -354,7 +354,7 @@ contract CheckAvailableBalanceTest is CompoundStrategyTest { _deposit(); } - function test_checkAvilableBalance_LTAllocatedAmount() public { + function test_checkAvailableBalance_LTAllocatedAmount() public { vm.mockCall(ASSET, abi.encodeWithSignature("balanceOf(address)"), abi.encode(depositAmount - 100)); uint256 availableBalance = strategy.checkAvailableBalance(ASSET); assertTrue(availableBalance < depositAmount); diff --git a/test/strategy/StargateStrategy.t.sol b/test/strategy/StargateStrategy.t.sol index 3f7f6373..c30cc2dd 100644 --- a/test/strategy/StargateStrategy.t.sol +++ b/test/strategy/StargateStrategy.t.sol @@ -423,18 +423,19 @@ contract CollectReward is HarvestTest { _harvestIncentiveRate = uint16(bound(_harvestIncentiveRate, 0, 10000)); vm.prank(USDS_OWNER); strategy.updateHarvestIncentiveRate(_harvestIncentiveRate); - uint256 initialRewards = strategy.checkRewardEarned(); + StargateStrategy.RewardData[] memory initialRewards = strategy.checkRewardEarned(); - assert(initialRewards == 0); + assert(initialRewards[0].token == strategy.rewardTokenAddress(0)); + assert(initialRewards[0].amount == 0); // Do a time travel & mine dummy blocks for accumulating some rewards vm.warp(block.timestamp + 10 days); vm.roll(block.number + 1000); - uint256 currentRewards = strategy.checkRewardEarned(); - assert(currentRewards > 0); - uint256 incentiveAmt = (currentRewards * strategy.harvestIncentiveRate()) / Helpers.MAX_PERCENTAGE; - uint256 harvestAmt = currentRewards - incentiveAmt; + StargateStrategy.RewardData[] memory currentRewards = strategy.checkRewardEarned(); + assert(currentRewards[0].amount > 0); + uint256 incentiveAmt = (currentRewards[0].amount * strategy.harvestIncentiveRate()) / Helpers.MAX_PERCENTAGE; + uint256 harvestAmt = currentRewards[0].amount - incentiveAmt; address caller = actors[1]; if (incentiveAmt > 0) { @@ -450,7 +451,7 @@ contract CollectReward is HarvestTest { assertEq(ERC20(E_TOKEN).balanceOf(caller), incentiveAmt); currentRewards = strategy.checkRewardEarned(); - assert(currentRewards == 0); + assert(currentRewards[0].amount == 0); } } From 2b16742829f73adb072756310dcc88f69504e1ea Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Fri, 15 Dec 2023 15:23:27 +0530 Subject: [PATCH 29/64] Resolved review comments and updated ether units --- test/buyback/SPABuyback.t.sol | 36 ++++++++++++++----------------- test/buyback/YieldReserve.t.sol | 10 ++++----- test/rebase/Dripper.t.sol | 8 +++---- test/rebase/RebaseManager.t.sol | 2 +- test/vault/VaultCore.t.sol | 7 +++--- test/vault/VaultIntegration.t.sol | 2 +- 6 files changed, 31 insertions(+), 34 deletions(-) diff --git a/test/buyback/SPABuyback.t.sol b/test/buyback/SPABuyback.t.sol index b1de3fdf..5dd7c96f 100644 --- a/test/buyback/SPABuyback.t.sol +++ b/test/buyback/SPABuyback.t.sol @@ -24,12 +24,8 @@ contract SPABuybackTestSetup is BaseTest { uint256 internal spaIn; modifier mockOracle() { - vm.mockCall( - address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDS), abi.encode(995263234350000000, 1 ether) - ); - vm.mockCall( - address(ORACLE), abi.encodeWithSignature("getPrice(address)", SPA), abi.encode(4729390000000000, 1 ether) - ); + vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDS), abi.encode(99526323, 1e8)); + vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", SPA), abi.encode(472939, 1e8)); _; vm.clearMockedCalls(); } @@ -102,8 +98,8 @@ contract TestGetters is SPABuybackTestSetup { function setUp() public override { super.setUp(); - usdsAmount = 100 ether; - spaIn = 1e5 ether; + usdsAmount = 1e20; + spaIn = 1e23; } function testGetSpaReqdForUSDs() public mockOracle { @@ -195,7 +191,7 @@ contract TestWithdraw is SPABuybackTestSetup { function setUp() public override { super.setUp(); token = USDS; - amount = 100 ether; + amount = 1e20; vm.prank(VAULT); IUSDs(USDS).mint(address(spaBuyback), amount); @@ -214,7 +210,7 @@ contract TestWithdraw is SPABuybackTestSetup { function testCannotWithdrawMoreThanBalance() public useKnownActor(USDS_OWNER) { amount = IERC20(USDS).balanceOf(address(spaBuyback)); - amount = amount + 100 ether; + amount = amount + 1e20; vm.expectRevert("Transfer greater than balance"); spaBuyback.withdraw(token, user, amount); } @@ -251,7 +247,7 @@ contract TestBuyUSDs is SPABuybackTestSetup { function setUp() public override { super.setUp(); - spaIn = 1e5 ether; + spaIn = 1e23; minUSDsOut = 1; } @@ -262,8 +258,8 @@ contract TestBuyUSDs is SPABuybackTestSetup { } function testCannotIfSlippageMoreThanExpected() public mockOracle { - minUSDsOut = spaBuyback.getUsdsOutForSpa(spaIn) + 100 ether; - vm.expectRevert(abi.encodeWithSelector(Helpers.MinSlippageError.selector, minUSDsOut - 100 ether, minUSDsOut)); + minUSDsOut = spaBuyback.getUsdsOutForSpa(spaIn) + 1e20; + vm.expectRevert(abi.encodeWithSelector(Helpers.MinSlippageError.selector, minUSDsOut - 1e20, minUSDsOut)); spaBuyback.buyUSDs(spaIn, minUSDsOut); } @@ -276,7 +272,7 @@ contract TestBuyUSDs is SPABuybackTestSetup { function testBuyUSDs() public mockOracle { minUSDsOut = _calculateUSDsForSpaIn(spaIn); vm.prank(VAULT); - IUSDs(USDS).mint(address(spaBuyback), minUSDsOut + 10 ether); + IUSDs(USDS).mint(address(spaBuyback), minUSDsOut + 1e19); spaTotalSupply.balBefore = IERC20(SPA).totalSupply(); spaBal.balBefore = IERC20(SPA).balanceOf(VESPA_REWARDER); deal(SPA, user, spaIn); @@ -300,13 +296,13 @@ contract TestBuyUSDs is SPABuybackTestSetup { // Testing with fuzzing function testBuyUSDs(uint256 spaIn, uint256 spaPrice, uint256 usdsPrice) public { usdsPrice = bound(usdsPrice, 7e17, 13e17); - spaPrice = bound(spaPrice, 1e15, 100 ether); - spaIn = bound(spaIn, 1 ether, 1e9 ether); - uint256 swapValue = (spaIn * spaPrice) / 1 ether; - vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDS), abi.encode(usdsPrice, 1 ether)); - vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", SPA), abi.encode(spaPrice, 1 ether)); + spaPrice = bound(spaPrice, 1e15, 1e20); + spaIn = bound(spaIn, 1e18, 1e27); + uint256 swapValue = (spaIn * spaPrice) / 1e18; + vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDS), abi.encode(usdsPrice, 1e18)); + vm.mockCall(address(ORACLE), abi.encodeWithSignature("getPrice(address)", SPA), abi.encode(spaPrice, 1e18)); minUSDsOut = _calculateUSDsForSpaIn(spaIn); - if (swapValue > 1 ether && minUSDsOut > 1 ether) { + if (swapValue > 1e18 && minUSDsOut > 1e18) { vm.prank(VAULT); IUSDs(USDS).mint(address(spaBuyback), minUSDsOut); deal(SPA, user, spaIn); diff --git a/test/buyback/YieldReserve.t.sol b/test/buyback/YieldReserve.t.sol index f4dad687..8684a7ed 100644 --- a/test/buyback/YieldReserve.t.sol +++ b/test/buyback/YieldReserve.t.sol @@ -227,8 +227,8 @@ contract YieldReserveTest is YieldReserveSetup { } function test_getTokenBForTokenA_inputs() public useKnownActor(USDS_OWNER) { - mockPrice(USDCe, 1 gwei, PRICE_PRECISION); - mockPrice(USDS, 1 gwei, PRICE_PRECISION); + mockPrice(USDCe, 1e8, PRICE_PRECISION); + mockPrice(USDS, 1e8, PRICE_PRECISION); vm.expectRevert(abi.encodeWithSelector(YieldReserve.InvalidSourceToken.selector)); yieldReserve.getTokenBForTokenA(USDS, USDCe, 10000); @@ -279,9 +279,9 @@ contract SwapTest is YieldReserveSetup { function setUp() public override { super.setUp(); vm.startPrank(USDS_OWNER); - deal(address(USDCe), USDS_OWNER, 1 ether); - deal(address(USDCe), address(yieldReserve), 100 ether); - deal(address(DAI), address(yieldReserve), 100 ether); + deal(address(USDCe), USDS_OWNER, 1e18); + deal(address(USDCe), address(yieldReserve), 1e20); + deal(address(DAI), address(yieldReserve), 1e20); mockPrice(USDCe, 1e8, PRICE_PRECISION); mockPrice(USDS, 1e8, PRICE_PRECISION); diff --git a/test/rebase/Dripper.t.sol b/test/rebase/Dripper.t.sol index 2d911c82..cdd02699 100644 --- a/test/rebase/Dripper.t.sol +++ b/test/rebase/Dripper.t.sol @@ -106,13 +106,13 @@ contract Collect is DripperTest { function test_CollectDripper() external useKnownActor(WHALE_USDS) { changePrank(VAULT); - IUSDs(USDS).mint(WHALE_USDS, 1e6 ether); + IUSDs(USDS).mint(WHALE_USDS, 1e24); changePrank(WHALE_USDS); - IERC20(USDS).approve(address(dripper), 1e6 ether); - dripper.addUSDs(1e6 ether); + IERC20(USDS).approve(address(dripper), 1e24); + dripper.addUSDs(1e24); skip(14 days); vm.expectEmit(true, true, false, true); - emit Collected(1e6 ether); + emit Collected(1e24); dripper.collect(); } } diff --git a/test/rebase/RebaseManager.t.sol b/test/rebase/RebaseManager.t.sol index 425ba502..8dd52488 100644 --- a/test/rebase/RebaseManager.t.sol +++ b/test/rebase/RebaseManager.t.sol @@ -155,7 +155,7 @@ contract FetchRebaseAmt is RebaseManagerTest { // Minting USDs mintUSDs(100 gwei); vm.prank(USDS_OWNER); - IERC20(USDS).transfer(address(dripper), 10000 ether); + IERC20(USDS).transfer(address(dripper), 1e22); vm.startPrank(VAULT); dripper.collect(); diff --git a/test/vault/VaultCore.t.sol b/test/vault/VaultCore.t.sol index 43fcc5f3..b2fe65c8 100644 --- a/test/vault/VaultCore.t.sol +++ b/test/vault/VaultCore.t.sol @@ -472,12 +472,13 @@ contract TestRebase is VaultCoreTest { event RebasedUSDs(uint256 rebaseAmt); function test_Rebase() public { + uint256 _amount = 1e22; vm.startPrank(VAULT); IRebaseManager(REBASE_MANAGER).fetchRebaseAmt(); - IUSDs(USDS).mint(actors[1], 1e5 ether); + IUSDs(USDS).mint(actors[1], _amount); changePrank(actors[1]); - ERC20(USDS).approve(DRIPPER, 1e5 ether); - IDripper(DRIPPER).addUSDs(1e5 ether); + ERC20(USDS).approve(DRIPPER, _amount); + IDripper(DRIPPER).addUSDs(_amount); changePrank(VAULT); skip(1 days); IDripper(DRIPPER).collect(); diff --git a/test/vault/VaultIntegration.t.sol b/test/vault/VaultIntegration.t.sol index 50c01f3d..ed0146e2 100644 --- a/test/vault/VaultIntegration.t.sol +++ b/test/vault/VaultIntegration.t.sol @@ -203,7 +203,7 @@ contract TestRebase is VaultCoreTest { event RebasedUSDs(uint256 rebaseAmt); function test_Rebase() public useKnownActor(VAULT) { - uint256 _amount = 1e5 ether; + uint256 _amount = 1e23; IRebaseManager(REBASE_MANAGER).fetchRebaseAmt(); IUSDs(USDS).mint(actors[1], _amount); changePrank(actors[1]); From f49ba0a88695c5ed73ffae6055771d77dddfc734 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Mon, 18 Dec 2023 18:55:16 +0530 Subject: [PATCH 30/64] Fixing units of gwei --- test/buyback/YieldReserve.t.sol | 2 +- test/oracle/ChainlinkOracle.t.sol | 2 +- test/rebase/RebaseManager.t.sol | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/buyback/YieldReserve.t.sol b/test/buyback/YieldReserve.t.sol index 8684a7ed..5a78ca72 100644 --- a/test/buyback/YieldReserve.t.sol +++ b/test/buyback/YieldReserve.t.sol @@ -250,7 +250,7 @@ contract YieldReserveTest is YieldReserveSetup { yieldReserve.toggleSrcTokenPermission(USDS, true); yieldReserve.toggleDstTokenPermission(USDCe, true); - assertEq(getTokenData(USDCe).conversionFactor, 1e3 gwei); + assertEq(getTokenData(USDCe).conversionFactor, 1e12); assertEq(getTokenData(USDS).conversionFactor, 1); uint256 amount = yieldReserve.getTokenBForTokenA(USDS, USDCe, amountIn * USDsPrecision); diff --git a/test/oracle/ChainlinkOracle.t.sol b/test/oracle/ChainlinkOracle.t.sol index c900f5b0..166b6bb8 100644 --- a/test/oracle/ChainlinkOracle.t.sol +++ b/test/oracle/ChainlinkOracle.t.sol @@ -40,7 +40,7 @@ contract Test_SetTokenData is ChainlinkOracleTest { function test_revertsWhen_InvalidPrecision() public useKnownActor(USDS_OWNER) { ChainlinkOracle.SetupTokenData memory tokenData = _getTokenData()[0]; - tokenData.data.pricePrecision = 1 gwei; + tokenData.data.pricePrecision = 1e8; vm.expectRevert(abi.encodeWithSelector(ChainlinkOracle.InvalidPricePrecision.selector)); chainlinkOracle.setTokenData(tokenData.token, tokenData.data); } diff --git a/test/rebase/RebaseManager.t.sol b/test/rebase/RebaseManager.t.sol index 8dd52488..b7015e25 100644 --- a/test/rebase/RebaseManager.t.sol +++ b/test/rebase/RebaseManager.t.sol @@ -153,7 +153,7 @@ contract FetchRebaseAmt is RebaseManagerTest { address(ORACLE), abi.encodeWithSignature("getPrice(address)", USDCe), abi.encode(usdcePrice, usdcePrecision) ); // Minting USDs - mintUSDs(100 gwei); + mintUSDs(1e11); vm.prank(USDS_OWNER); IERC20(USDS).transfer(address(dripper), 1e22); From ebde8500fec2dace8adf2f175ad784d69a0c9b60 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Mon, 18 Dec 2023 19:05:29 +0530 Subject: [PATCH 31/64] Fixed test case --- test/oracle/ChainlinkOracle.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/oracle/ChainlinkOracle.t.sol b/test/oracle/ChainlinkOracle.t.sol index 166b6bb8..f17e877f 100644 --- a/test/oracle/ChainlinkOracle.t.sol +++ b/test/oracle/ChainlinkOracle.t.sol @@ -40,7 +40,7 @@ contract Test_SetTokenData is ChainlinkOracleTest { function test_revertsWhen_InvalidPrecision() public useKnownActor(USDS_OWNER) { ChainlinkOracle.SetupTokenData memory tokenData = _getTokenData()[0]; - tokenData.data.pricePrecision = 1e8; + tokenData.data.pricePrecision = 1e9; vm.expectRevert(abi.encodeWithSelector(ChainlinkOracle.InvalidPricePrecision.selector)); chainlinkOracle.setTokenData(tokenData.token, tokenData.data); } From d6b5aea345f31e0f49c2b3d5e567d033fd0a7c7d Mon Sep 17 00:00:00 2001 From: Parv3213 Date: Thu, 21 Dec 2023 19:57:14 +0530 Subject: [PATCH 32/64] docs: Sequential flow diagrams --- docs/arch/sequentialDiagrams/USDsMintFlow.pu | 38 +++++++++++++++++ .../arch/sequentialDiagrams/USDsRebaseFlow.pu | 23 ++++++++++ .../arch/sequentialDiagrams/USDsRedeemFlow.pu | 40 ++++++++++++++++++ .../USDsYeildGenerationFlow.pu | 42 +++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 docs/arch/sequentialDiagrams/USDsMintFlow.pu create mode 100644 docs/arch/sequentialDiagrams/USDsRebaseFlow.pu create mode 100644 docs/arch/sequentialDiagrams/USDsRedeemFlow.pu create mode 100644 docs/arch/sequentialDiagrams/USDsYeildGenerationFlow.pu diff --git a/docs/arch/sequentialDiagrams/USDsMintFlow.pu b/docs/arch/sequentialDiagrams/USDsMintFlow.pu new file mode 100644 index 00000000..87e1acfb --- /dev/null +++ b/docs/arch/sequentialDiagrams/USDsMintFlow.pu @@ -0,0 +1,38 @@ +@startuml USDs Mint Flow + +title USDs Mint Flow +actor User +participant Vault +participant CollateralManager +participant Oracle +participant FeeCalculator +participant USDs +participant FeeVault +box "External Contracts" #LightBlue + participant Collateral +end box + +User -> Collateral: approve(vault, amount) +User -> Vault: mint(collateral, amount, minAmtToRecv, deadline) +note over Vault: Validate the transaction \nis within deadline +Vault -> CollateralManager: getMintParams +note over CollateralManager: Validates if token \ncollateral is registered +CollateralManager --> Vault: Collateral's mint config +Vault -> Oracle: getPrice(collateral) +note over Oracle: Validate the price \nfeed for the collateral +Oracle --> Vault: Collateral Price +note over Vault: Validate conditions \n1. Mint is allowed \n2. Price of collateral is \nabove a `downsidePeg` +Vault -> FeeCalculator: getMintFee(collateral) +note over FeeCalculator: Calculates fee based \non the composition of \ncollateral in USDs +FeeCalculator --> Vault: Fee amount +note over Vault: Perform a slippage check +Vault -> Vault: Rebase() +note over Vault: Perform a rebase if \npreset conditions match +Vault -> Collateral: safeTransferFrom(user, vault, amount) +Collateral --> Vault: Collateral +Vault -> USDs: mint(user, toMinterAmt) +USDs --> User: Mint USDs for User +Vault -> USDs: mint(feeVault, feeAmt) +USDs --> FeeVault: Mint USDs as fee to feeVault + +@enduml diff --git a/docs/arch/sequentialDiagrams/USDsRebaseFlow.pu b/docs/arch/sequentialDiagrams/USDsRebaseFlow.pu new file mode 100644 index 00000000..7e9483b7 --- /dev/null +++ b/docs/arch/sequentialDiagrams/USDsRebaseFlow.pu @@ -0,0 +1,23 @@ +@startuml USDs Rebase Flow + +title USDs Rebase Flow +actor User +participant Vault +participant RebaseManager +participant Dripper +participant USDs + +User -> Vault : rebase() +Vault -> RebaseManager : fetchRebaseAmt() +RebaseManager -> Dripper : getCollectableAmt() +Dripper --> RebaseManager : Dripped USDs +note over RebaseManager : Calculate rebase amount based on\n1. Available USDs\n2. Min/Max rebase amount +note over RebaseManager : Validate time gap since the last rebase +RebaseManager -> Dripper : collect() +Dripper -> USDs : safeTransfer(vault, collectableAmt) +note over Dripper, USDs : collectableAmt always >= rebaseAmt +RebaseManager --> Vault : rebaseAmt +Vault -> USDs : rebase(rebaseAmt) +USDs -> USDs : _burn(vault, rebaseAmt) +note over USDs : Update rebasingCreditsPerToken\nresulting in an updated rebasing account balance +@enduml diff --git a/docs/arch/sequentialDiagrams/USDsRedeemFlow.pu b/docs/arch/sequentialDiagrams/USDsRedeemFlow.pu new file mode 100644 index 00000000..86d2f7ea --- /dev/null +++ b/docs/arch/sequentialDiagrams/USDsRedeemFlow.pu @@ -0,0 +1,40 @@ +@startuml USDs Redeem Flow + +title USDs Redeem Flow +actor User +participant Vault +participant CollateralManager +participant Oracle +participant FeeCalculator +participant USDs +participant FeeVault +box "External Contracts" #LightBlue + participant Collateral +end box + +User -> USDs: approve(vault, usdsAmt) +User -> Vault: redeem(collateral, usdsAmt, minCollAmt, deadline) +note over Vault: Validate the transaction\nis within the deadline +Vault -> CollateralManager: getRedeemParams(collateral) +note over CollateralManager: Validates if token\ncollateral is registered +CollateralManager --> Vault: Collateral's redeem params +note over Vault: Validate if redemption\nis not paused for the collateral +Vault -> Oracle: getPrice(collateral) +note over Oracle: Validate the price\nfeed for the collateral +Oracle --> Vault: Collateral Price +Vault -> FeeCalculator: getRedeemFee(collateral) +note over FeeCalculator: Calculates fee based\non the composition of\ncollateral in USDs +FeeCalculator --> Vault: Fee amount +note over Vault: Verify enough collateral in\nVault; if absent, withdraw from\nthe strategy +note over Vault: Perform a slippage check +Vault -> USDs: safeTransferFrom(user, vault, usdsAmt) +USDs --> Vault: usdsAmt to burn +Vault -> USDs: burn(vault, usdsAmt) +Vault -> USDs: safeTransfer(feeVault, feeAmt) +USDs --> FeeVault: USDs feeAmt +Vault -> Collateral: safeTransfer(user, collateralAmt) +Collateral --> User: collateralAmt +Vault -> Vault: Rebase() +note over Vault: Perform a rebase if\npreset-conditions match + +@enduml diff --git a/docs/arch/sequentialDiagrams/USDsYeildGenerationFlow.pu b/docs/arch/sequentialDiagrams/USDsYeildGenerationFlow.pu new file mode 100644 index 00000000..979e098e --- /dev/null +++ b/docs/arch/sequentialDiagrams/USDsYeildGenerationFlow.pu @@ -0,0 +1,42 @@ +@startuml USDs Yield Generation Flow + +title USDs Yield Generation Flow +actor User +participant Vault +participant CollateralManager +participant Strategy +participant YieldReserve +participant Dripper +box "External Contracts" #LightBlue + participant Collateral + participant "Yield Earning Strategies" as YieldEarningStrategies +end box + +group Allocate + User -> Vault: allocate(collateral, strategy, amount) + group validate allocation + Vault -> CollateralManager: validateAllocation(collateral, strategy, amount) + CollateralManager --> Vault: Boolean (true/false) + end + Vault -> Collateral: forceApprove(strategy, amount) + Vault -> Strategy: deposit(collateral, amount) + note over Strategy: Validate support for collateral + Strategy -> Collateral: safeTransferFrom(vault, strategy, amount) + Collateral --> Strategy: Collateral amount + Strategy -> YieldEarningStrategies: Deposit collateral for earning yield +end + +group Harvest + User -> Strategy: collectReward() / checkRewardEarned() + Strategy --> User: Harvest Incentive + Strategy --> YieldReserve: Harvest Amount +end + +group Yield purchase using USDs + User -> YieldReserve: swap(srcToken, dstToken, amountIn, minAmountOut) + YieldReserve --> User: dstToken + YieldReserve -> Dripper: addUSDs(rebaseAmt) + note over YieldReserve, Dripper: USDs for rebase +end + +@enduml From d23aa6590dc84dc30b524337d3c910d3a7ab07df Mon Sep 17 00:00:00 2001 From: Parv3213 Date: Fri, 22 Dec 2023 16:23:46 +0530 Subject: [PATCH 33/64] Improve sequential diagrams --- docs/arch/sequentialDiagrams/USDsMintFlow.pu | 2 +- docs/arch/sequentialDiagrams/USDsRebaseFlow.pu | 3 ++- docs/arch/sequentialDiagrams/USDsRedeemFlow.pu | 6 +++--- .../sequentialDiagrams/USDsYeildGenerationFlow.pu | 15 +++++++++------ 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/arch/sequentialDiagrams/USDsMintFlow.pu b/docs/arch/sequentialDiagrams/USDsMintFlow.pu index 87e1acfb..dc5b225d 100644 --- a/docs/arch/sequentialDiagrams/USDsMintFlow.pu +++ b/docs/arch/sequentialDiagrams/USDsMintFlow.pu @@ -29,7 +29,7 @@ note over Vault: Perform a slippage check Vault -> Vault: Rebase() note over Vault: Perform a rebase if \npreset conditions match Vault -> Collateral: safeTransferFrom(user, vault, amount) -Collateral --> Vault: Collateral +User --> Vault: Collateral Vault -> USDs: mint(user, toMinterAmt) USDs --> User: Mint USDs for User Vault -> USDs: mint(feeVault, feeAmt) diff --git a/docs/arch/sequentialDiagrams/USDsRebaseFlow.pu b/docs/arch/sequentialDiagrams/USDsRebaseFlow.pu index 7e9483b7..5209ec0c 100644 --- a/docs/arch/sequentialDiagrams/USDsRebaseFlow.pu +++ b/docs/arch/sequentialDiagrams/USDsRebaseFlow.pu @@ -15,7 +15,8 @@ note over RebaseManager : Calculate rebase amount based on\n1. Available USDs\n2 note over RebaseManager : Validate time gap since the last rebase RebaseManager -> Dripper : collect() Dripper -> USDs : safeTransfer(vault, collectableAmt) -note over Dripper, USDs : collectableAmt always >= rebaseAmt +USDs --> Vault : collectableAmt USDs +note over Dripper, USDs : collectableAmt is always >= rebaseAmt RebaseManager --> Vault : rebaseAmt Vault -> USDs : rebase(rebaseAmt) USDs -> USDs : _burn(vault, rebaseAmt) diff --git a/docs/arch/sequentialDiagrams/USDsRedeemFlow.pu b/docs/arch/sequentialDiagrams/USDsRedeemFlow.pu index 86d2f7ea..8121517e 100644 --- a/docs/arch/sequentialDiagrams/USDsRedeemFlow.pu +++ b/docs/arch/sequentialDiagrams/USDsRedeemFlow.pu @@ -28,12 +28,12 @@ FeeCalculator --> Vault: Fee amount note over Vault: Verify enough collateral in\nVault; if absent, withdraw from\nthe strategy note over Vault: Perform a slippage check Vault -> USDs: safeTransferFrom(user, vault, usdsAmt) -USDs --> Vault: usdsAmt to burn +User --> Vault: USDs (usdsAmt) to burn Vault -> USDs: burn(vault, usdsAmt) Vault -> USDs: safeTransfer(feeVault, feeAmt) -USDs --> FeeVault: USDs feeAmt +Vault --> FeeVault: USDs (feeAmt) Vault -> Collateral: safeTransfer(user, collateralAmt) -Collateral --> User: collateralAmt +Vault --> User: Collateral (collateralAmt) Vault -> Vault: Rebase() note over Vault: Perform a rebase if\npreset-conditions match diff --git a/docs/arch/sequentialDiagrams/USDsYeildGenerationFlow.pu b/docs/arch/sequentialDiagrams/USDsYeildGenerationFlow.pu index 979e098e..1fb5737a 100644 --- a/docs/arch/sequentialDiagrams/USDsYeildGenerationFlow.pu +++ b/docs/arch/sequentialDiagrams/USDsYeildGenerationFlow.pu @@ -14,28 +14,31 @@ end box group Allocate User -> Vault: allocate(collateral, strategy, amount) - group validate allocation - Vault -> CollateralManager: validateAllocation(collateral, strategy, amount) - CollateralManager --> Vault: Boolean (true/false) - end + Vault -> CollateralManager: validateAllocation(collateral, strategy, amount) + CollateralManager -> Vault: Boolean (true/false) + note over Vault: Validate allocation Vault -> Collateral: forceApprove(strategy, amount) Vault -> Strategy: deposit(collateral, amount) note over Strategy: Validate support for collateral Strategy -> Collateral: safeTransferFrom(vault, strategy, amount) - Collateral --> Strategy: Collateral amount - Strategy -> YieldEarningStrategies: Deposit collateral for earning yield + Vault --> Strategy: Collateral (amount) + Strategy -> YieldEarningStrategies: Deposit collateral in yeild earning strategies + Strategy --> YieldEarningStrategies: Collateral (amount) end group Harvest User -> Strategy: collectReward() / checkRewardEarned() Strategy --> User: Harvest Incentive + YieldEarningStrategies --> Strategy: Harvest Amount Strategy --> YieldReserve: Harvest Amount end group Yield purchase using USDs User -> YieldReserve: swap(srcToken, dstToken, amountIn, minAmountOut) + User --> YieldReserve: srcToken (amountIn) YieldReserve --> User: dstToken YieldReserve -> Dripper: addUSDs(rebaseAmt) + YieldReserve --> Dripper: USDs (rebaseAmt) note over YieldReserve, Dripper: USDs for rebase end From 57da144bb3b0f3128600372a85161971a604fc04 Mon Sep 17 00:00:00 2001 From: Parv3213 Date: Fri, 22 Dec 2023 18:57:45 +0530 Subject: [PATCH 34/64] Add sequence diagram ref in Readme --- README.md | 1 + docs/arch/sequentialDiagrams/README.md | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 docs/arch/sequentialDiagrams/README.md diff --git a/README.md b/README.md index 81bbf2f6..6b4f9a5f 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ The project uses foundry framework for compiling, developing and testing contrac ## Project Summary: * [Summary](/docs/src/SUMMARY.md) +* [Sequence Diagrams](/docs/arch/sequentialDiagrams/readme.md) ## Project Setup: * [Install Foundry](https://book.getfoundry.sh/getting-started/installation) diff --git a/docs/arch/sequentialDiagrams/README.md b/docs/arch/sequentialDiagrams/README.md new file mode 100644 index 00000000..fb8e9715 --- /dev/null +++ b/docs/arch/sequentialDiagrams/README.md @@ -0,0 +1,11 @@ +### Mint Flow +![MintFlow](https://www.plantuml.com/plantuml/png/VLDDRzim3BthLn3TOGcG5jYEEGpjkkMqOa7NxhH36PjsXCgI8T59V_uesQdin6WkIVZntkCZTHiOFNUDKQpP43p_-H7WGLk6ZN57XGKx3y-1l6gbJXUwHSdznSxm97BlZ44cZ-O1BTPdzRyz5eOceGtHFPgYaoutBOuIpajxmfrxWwkVRq9ZqGYbPS7bS0MVVkbwpzzDHmhaCozCaIr189IAyy3rjr5k3TYstXreSOXCAy36TPQNfz9-uZKquioYU6yxrQrYugxX9_T8nM459M5fjAMbieu91DSF2F9bT8aIv3s1wBT1Hj3EmZ-h0nmrxxLzxrUfIVWl_Lr3JHmNjKMFJSX4bwMPD82kWDqhHRey1WYpfre7-KUbkW20w-n0JduEoGuedArqdQNsoqxwjbuNDF9hxCTGDZMaZGsYhI8geN8-1NE_wflczK1Y-y1n4Lhgw7KGXYyt_Pdh06ZarAcKuDUR7WHS1HDx98Sx0GM4bz8TRT0bRQb-oLEFxpalHwBpuv_NdtvJW8e8TXYI86S71vhMXQGzIflkplOlPW8uTaWImvbUUhCbBoutW12CRbkiXMjFnMkUQoXyf2XfiVm8mgSQq55WwoaGZorla8lzt46l8M15J_4fLEGttZMBBf1VmUnJdDjyHerE3A-qns0Nrq3-hea8aaq84MhTxp_5P80KcmMgY59u5JSpnTaCgJ4MzXja1wT6TKksx1hp7m00) + +### Redeem Flow +![RedeemFlow](https://www.plantuml.com/plantuml/png/VLFBRjmm3BphAuZqK0VO_40A56dJvjIYGThaj1UkJNk5o9H1qddyVMKvMQ-zHcw2D3CSZiWhDHJhsm3tVtyet555rC9DYC_EcRT0f_TOMXIuLnBN9QulVOTiy81ziDdDTGm1ZGJ3RsHi5lW_WcMWsTKDqJM6ia-iE0Dd4qleM70NN-3ivukHC8PKaYtfcfx1fr--sTltq9C305WtveWhI1BE3Vt0nTTSwHAmwoG-KV4qrDX0hvL-Q-tyWCgrBq5oB4Lvq3j0Dz1w7khaSqLO1SzqxZWQGLAMTua73BvAPB0zWGco9li-yfQzmhEtlUVXwI3XCYzvE8tt4XgoyQTkKR3L8sD7bK-9ami5NuF5Hqhr9p9u1Q76gv5GvKxuS355CZr-rhTyeCjkFkZTrndQTeVE4pRHUgKAwYWvWSdFbC4uHBdnM_4bhNTyWCt2xWR2bcjQhp5orbk3N6ooCHlSesz8z-kEbeptCodKHB13fMhBaTzCjLrKFsG3iPv_38-hCPCxzfmU0DlOiwt4J-BhLo2EVRE7cUgMC-RBy3EuKsBRv66i19-XbjXkUN2c9edIl9vgtvAaJ5j0qE2x3Xk2Sa_buvZPj6YADVqRfhuckKcwHQya6rZTkwcjJ8RY_HaimguNNeZdk-KAVsIWg8aUHdWw7QF7FvuLdoDc-xXG7LkQybq83xtDXgjOXKxDtz4m7yNvHwbBne1Fuz89ATb56RdACwJGefLxvww8gxuDxZy0) + +### Rebase Flow +![RebaseFlow](https://www.plantuml.com/plantuml/svg/VP51xzem3CNl-HG-WWHCst7I40Yq6zAq0IUaoKrDYPOwLUB2_j_-dxPfrOB4BR9_Vk_5tWH1BtLfuVHd7-0tPHW8VbhtK4gCM7gjenRduHJ8gohE6cqgP84prbR6bJHqGCRYYTrxKrNJMkkZL2CBotMd1j_1jngpkUeAiJDHZSILHD-wuhQKI4w1E97S8bkGx9orf0Kpc_2-ktpLJfDvbusT4BWx-HTiXrRN5eLINC3IrIpGl7DmVE6lAzZUqLZCqaOl_6q51yDV3lX_ElN6vupMv8sDc9AWm0g2OKqWDmABGPBGkmtexllZVw_RGB4Ny4f7Znokv6VtPjyBq9DrpLMBBaS7cWBFcqecYsJnJ9i0Q1_u4M3z0tn_msd-iTk8wIf3-DIP3K0VTG3-PhNd_biZR0ZRWwSg7qvfkDXvoes4N-IFxX_5MteAKIDsm30WGzto-J00g7KwlqNMf3R4ULrQzGa0) + +### Yield Generation Flow +![YieldGenerationFlow](https://www.plantuml.com/plantuml/png/XLJBRjim4BphAnRfHG5SvAv34ATEsW0j2kGHeCSDjRQ9K2H1hkJaxqkPabbATdqnP74UCqkADvxHSTieU7bQUVWZITNmdJGvP6aqt2kpAmgMhEX_21Hi7BnuSeKDUb98YvhX5Ll5sPjlHYdam5M_KEDcWd_Y84gRp-pbtl6HWdP7sSBAIMk3mflvW8kxZw2gKGK77LG4-mluybDkjdohMYe008xdc2rTn8ftwBJKcp6Jf228G_zXBIqLf6i8EOfYuqnhOQcK4SZHjnyBVBsEqwW0XxLI738im0_D5u2DQJLVzip82DJvs2heKCaQcGOhQVHvmQbEbklM64MeeMJNqjKQbQT8qeO9J4TkHBuEre07xrDnArWR9sXfhGiAvPcQu_vNK9CrNd9MQQHacH8dnVAjjSPnRmqYt-WHFKtfSKtF3hLVaxjtfYcxFjEPVSaI9pKe4pPtF7vuAbZ5jl-41QdXawIgWI8S_04_aJsjUpGJwNeyfJ_GTUGv7TAiK1-613_I3brTNi8LY2s9z_Y_DwFmTfwb5wf6QNZGWZJBZdhWYPpP81DrkGyyCyZkWnawjOiuiAqJM_G4hG-c-piiwvkh-HtQqZlnRDv9Bw3sF3p5wJs4fqRgwFMxvSiaDKysoa0valVm39KcTl0wWhaUhxeAiAxx2gMZD_IqRFYuPYBqQ4ZmoTUJ4HSJLl_vH68SwqtuRHjL_0K0) \ No newline at end of file From 627ee0446b37238f7a4bc525b92bb51e5061a9e2 Mon Sep 17 00:00:00 2001 From: Parv3213 Date: Fri, 22 Dec 2023 18:58:49 +0530 Subject: [PATCH 35/64] Update sequence diagram link in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b4f9a5f..3e1fa104 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The project uses foundry framework for compiling, developing and testing contrac ## Project Summary: * [Summary](/docs/src/SUMMARY.md) -* [Sequence Diagrams](/docs/arch/sequentialDiagrams/readme.md) +* [Sequence Diagrams](/docs/arch/sequentialDiagrams/README.md) ## Project Setup: * [Install Foundry](https://book.getfoundry.sh/getting-started/installation) From f9c9f544bbbd7ccc16e6c386b5706a2ae36d2e4a Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 22 Dec 2023 20:43:31 +0530 Subject: [PATCH 36/64] refactor(contracts/SPABuyback): Configure SPABuyback to transfer rewards to new veSPARewarder. --- contracts/buyback/SPABuyback.sol | 4 +++- contracts/interfaces/IveSPARewarder.sol | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 contracts/interfaces/IveSPARewarder.sol diff --git a/contracts/buyback/SPABuyback.sol b/contracts/buyback/SPABuyback.sol index 938de81d..165b76e5 100644 --- a/contracts/buyback/SPABuyback.sol +++ b/contracts/buyback/SPABuyback.sol @@ -7,6 +7,7 @@ import {ERC20BurnableUpgradeable} from import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {IveSPARewarder} from "../interfaces/IveSPARewarder.sol"; import {IOracle} from "../interfaces/IOracle.sol"; import {Helpers} from "../libraries/Helpers.sol"; @@ -155,7 +156,8 @@ contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade uint256 toReward = (balance * rewardPercentage) / Helpers.MAX_PERCENTAGE; // Transferring SPA tokens - ERC20BurnableUpgradeable(Helpers.SPA).safeTransfer(veSpaRewarder, toReward); + ERC20BurnableUpgradeable(Helpers.SPA).forceApprove(veSpaRewarder, toReward); + IveSPARewarder(veSpaRewarder).addRewards(Helpers.SPA, toReward, 1); emit SPARewarded(toReward); // Remaining balance will be burned diff --git a/contracts/interfaces/IveSPARewarder.sol b/contracts/interfaces/IveSPARewarder.sol new file mode 100644 index 00000000..92142328 --- /dev/null +++ b/contracts/interfaces/IveSPARewarder.sol @@ -0,0 +1,16 @@ +pragma solidity 0.8.19; + +interface IveSPARewarder { + /// @notice Add rewards tokens for future epochs in uniform manner. + /// @dev Any rewards tokens directly transferred to the contract are not considered for distribution. + /// They have to be recovered from the contract using `recoverERC20` function. + /// @param _token Address of the reward token. + /// @param _amount The total amount to be funded. + /// @param _numEpochs The number of epochs the amount should be split. (2 = 2 epochs). + function addRewards(address _token, uint256 _amount, uint256 _numEpochs) external; + + /// @notice Gets the scheduled rewards for a particular `_weekCursor`. + /// @param _weekCursor Timestamp of the week. (THU 00:00 UTC) + /// @param _rewardToken Address of the reward token. + function rewardsPerWeek(uint256 _weekCursor, address _rewardToken) external view returns (uint256); +} From 35d27bb05820049861aa56bfc126ce5b6de5bdbe Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 22 Dec 2023 20:44:19 +0530 Subject: [PATCH 37/64] test(SPABuyback): Add assertions for new veSPARewarder --- test/buyback/SPABuyback.t.sol | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/buyback/SPABuyback.t.sol b/test/buyback/SPABuyback.t.sol index 5dd7c96f..22c6d6cc 100644 --- a/test/buyback/SPABuyback.t.sol +++ b/test/buyback/SPABuyback.t.sol @@ -7,6 +7,7 @@ import {UpgradeUtil} from "../utils/UpgradeUtil.sol"; import {MasterPriceOracle} from "../../contracts/oracle/MasterPriceOracle.sol"; import {IOracle} from "../../contracts/interfaces/IOracle.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {IveSPARewarder} from "../../contracts/interfaces/IveSPARewarder.sol"; import {IUSDs} from "../../contracts/interfaces/IUSDs.sol"; contract SPABuybackTestSetup is BaseTest { @@ -17,7 +18,7 @@ contract SPABuybackTestSetup is BaseTest { IOracle.PriceData internal spaData; address internal user; - address internal constant VESPA_REWARDER = 0x2CaB3abfC1670D1a452dF502e216a66883cDf079; + address internal constant VESPA_REWARDER = 0x5eD5C72D24fF0931E5a38C2969160dFE259E7C05; uint256 internal constant MAX_PERCENTAGE = 10000; uint256 internal rewardPercentage; uint256 internal minUSDsOut; @@ -59,6 +60,12 @@ contract SPABuybackTestSetup is BaseTest { spaData = IOracle(ORACLE).getPrice(SPA); return (_usdsAmount * usdsData.price * spaData.precision) / (spaData.price * usdsData.precision); } + + function _getWeek(uint256 _n) internal view returns (uint256) { + uint256 week = 7 days; + uint256 thisWeek = (block.timestamp / week) * week; + return thisWeek + (_n * week); + } } contract TestInit is SPABuybackTestSetup { @@ -279,6 +286,7 @@ contract TestBuyUSDs is SPABuybackTestSetup { vm.startPrank(user); IERC20(SPA).approve(address(spaBuyback), spaIn); spaData = IOracle(ORACLE).getPrice(SPA); + uint256 initialRewards = IveSPARewarder(VESPA_REWARDER).rewardsPerWeek(_getWeek(1), SPA); vm.expectEmit(true, true, false, true, address(spaBuyback)); emit BoughtBack(user, user, spaData.price, spaIn, minUSDsOut); vm.expectEmit(true, true, true, true, address(spaBuyback)); @@ -287,9 +295,11 @@ contract TestBuyUSDs is SPABuybackTestSetup { emit SPABurned(spaIn / 2); spaBuyback.buyUSDs(spaIn, minUSDsOut); vm.stopPrank(); + uint256 rewardsAfter = IveSPARewarder(VESPA_REWARDER).rewardsPerWeek(_getWeek(1), SPA); spaTotalSupply.balAfter = IERC20(SPA).totalSupply(); spaBal.balAfter = IERC20(SPA).balanceOf(VESPA_REWARDER); assertEq(spaBal.balAfter - spaBal.balBefore, spaIn / 2); + assertEq(initialRewards + (spaIn / 2), rewardsAfter); assertEq(spaTotalSupply.balBefore - spaTotalSupply.balAfter, spaIn / 2); } @@ -308,6 +318,7 @@ contract TestBuyUSDs is SPABuybackTestSetup { deal(SPA, user, spaIn); vm.startPrank(user); IERC20(SPA).approve(address(spaBuyback), spaIn); + uint256 initialRewards = IveSPARewarder(VESPA_REWARDER).rewardsPerWeek(_getWeek(1), SPA); vm.expectEmit(true, true, true, true, address(spaBuyback)); emit BoughtBack(user, user, spaData.price, spaIn, minUSDsOut); vm.expectEmit(true, true, true, false, address(spaBuyback)); @@ -316,6 +327,8 @@ contract TestBuyUSDs is SPABuybackTestSetup { emit SPABurned(spaIn / 2); spaBuyback.buyUSDs(spaIn, minUSDsOut); vm.stopPrank(); + uint256 rewardsAfter = IveSPARewarder(VESPA_REWARDER).rewardsPerWeek(_getWeek(1), SPA); + assertEq(initialRewards + (spaIn / 2), rewardsAfter); vm.clearMockedCalls(); emit log_named_uint("SPA spent", spaIn); } From e4cd3fdaa0c03bc2d70ed341ffab1d82be67458b Mon Sep 17 00:00:00 2001 From: Parv Garg Date: Mon, 8 Jan 2024 17:28:53 +0530 Subject: [PATCH 38/64] Fix Natspec and overall documentation (#24) * Remove unnecessary initializations - removing only from for loops * Gas optimisation and simplification * Standardization - use `getCollateralInAStrategy` instead of `IStrategy(_strategy).checkBalance(_collateral)` directly * Gas optimisation and simplification * No need to pre-calculate `_minUSDSAmt` before `mint` - `mintView` called from YieldReverse will result in the same value as `mintView` called from VaultCore. Hence, `_minUSDSAmt` can just be passed a 0. * Replace USDS constant variable with Helpers.USDS * Check for `block.timestamp < lastRebaseTS + gap` instead of `block.timestamp <= lastRebaseTS + gap` - Gas optimisation * Use local variable instead of repeated calculation * Add interface as inheritance in its contract * Standardize function param name * Add PR CI * Fix PR CI * Fix PR CI * Fix PR CI * Fix PR CI * Unnecessary addition * fixed a mistake in the comment for buybackPercentage in YieldReserve.sol * optimized greater than zero checks for unsigned integers. saves gas * Fix PR CI * Fix PR CI * Final fix PR CI * Fix gitignore - Remove interfaces folder from gitignore * Remove `mythril` from `pip-requirements` - as mythril install throwing error on `npm i` * improved test coverage for spaBuyback and yieldReserve contracts. * Improve coverage - negative tests for USDs and VaultCore * Fix test - typo * Complete coverage for USDs * Revert parts of "Gas optimisation and simplification" This reverts parts of commit c29c96d79aa01d15ef72cc79c0d391ebd44c731e. * added ++i unchecked block in for loop as no possibility of overflow. saves gas * Update CI * Update solhint * Fix Natspec and overall documentation * Standardize documentation comments * Update contracts/buyback/SPABuyback.sol * Apply suggestions from code review * Fixes * Format fmt * Fix package-lock * docs(NatSpec): Improve natspec for MasterPriceOracle and IOracle * refactor(contracts/buyback): Improve documentation * refactor(contracts/vault): Improve CollateralManager documentation * improved ChainlinkOracle documentation * docs(Natspec): Improve USDs Token Docs * refactor(contracts/token): Add missing natspec params * improved VaultCore documentation * improved StargateStrategy documentation * improved documentation for ChainlinkOracle, StargateStrategy, FeeCalculator and Vaultcore * improved natspec for ChainlinkOracle and FeeCalculator * docs(Natspec): Improve USDs, Dripper, RebaseManager NatSpec * refactor(contracts): Make nat-spec docs consistent * Updated natspec doc * format fix * docs(Natspec): Dripper RebaseManager NatSpec * fix(MasterPriceOracle): added events and errors natspec comments --------- Co-authored-by: TechnoGeek01 Co-authored-by: YashP16 Co-authored-by: mehultuteja Co-authored-by: Dayaa --- contracts/buyback/SPABuyback.sol | 25 ++- contracts/buyback/YieldReserve.sol | 128 ++++++----- contracts/interfaces/IOracle.sol | 4 +- contracts/libraries/Helpers.sol | 33 +-- contracts/oracle/BaseUniOracle.sol | 58 ++--- contracts/oracle/ChainlinkOracle.sol | 35 ++- contracts/oracle/MasterPriceOracle.sol | 12 +- contracts/oracle/SPAOracle.sol | 31 ++- contracts/oracle/USDsOracle.sol | 6 +- contracts/rebase/Dripper.sol | 44 ++-- contracts/rebase/RebaseManager.sol | 57 +++-- .../InitializableAbstractStrategy.sol | 119 +++++----- contracts/strategies/aave/AaveStrategy.sol | 36 +-- .../strategies/compound/CompoundStrategy.sol | 1 + .../strategies/stargate/StargateStrategy.sol | 11 +- contracts/token/USDs.sol | 209 +++++++++-------- contracts/vault/CollateralManager.sol | 97 +++++--- contracts/vault/FeeCalculator.sol | 5 + contracts/vault/VaultCore.sol | 212 ++++++++++-------- package-lock.json | 80 +++---- 20 files changed, 664 insertions(+), 539 deletions(-) diff --git a/contracts/buyback/SPABuyback.sol b/contracts/buyback/SPABuyback.sol index 165b76e5..6e129257 100644 --- a/contracts/buyback/SPABuyback.sol +++ b/contracts/buyback/SPABuyback.sol @@ -11,17 +11,19 @@ import {IveSPARewarder} from "../interfaces/IveSPARewarder.sol"; import {IOracle} from "../interfaces/IOracle.sol"; import {Helpers} from "../libraries/Helpers.sol"; -/// @title Buyback contract of the USDs Buyback protocol. -/// @notice Users can give their SPA and get USDs in return. -/// @notice The SPA in the contract is distributed as rewards based on the rewardPercentage and the rest is burned. +/// @title SPABuyback of USDs Protocol. /// @author Sperax Foundation +/// @notice This contract allows users to exchange SPA tokens for USDs tokens. +/// @dev Users can provide SPA tokens and receive USDs tokens in return based on the current exchange rate. +/// @dev A percentage of the provided SPA tokens are distributed as rewards, and the rest are burned. contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20Upgradeable for ERC20BurnableUpgradeable; - address public veSpaRewarder; - uint256 public rewardPercentage; - address public oracle; + address public veSpaRewarder; // Rewarder contract. + uint256 public rewardPercentage; // Share of rewards to be distributed with veSPA holders. + address public oracle; // Price oracle for SPA and USDs + // Events event BoughtBack( address indexed receiverOfUSDs, address indexed senderOfSPA, @@ -36,6 +38,7 @@ contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade event VeSpaRewarderUpdated(address newVeSpaRewarder); event OracleUpdated(address newOracle); + // Custom error messages error CannotWithdrawSPA(); error InsufficientUSDsBalance(uint256 toSend, uint256 bal); @@ -127,6 +130,7 @@ contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade (uint256 usdsToSend, uint256 spaPrice) = _getUsdsOutForSpa(_spaIn); Helpers._isNonZeroAmt(usdsToSend, "SPA Amount too low"); + // Slippage check if (usdsToSend < _minUSDsOut) { revert Helpers.MinSlippageError(usdsToSend, _minUSDsOut); } @@ -155,7 +159,7 @@ contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade // Calculating the amount to reward based on rewardPercentage uint256 toReward = (balance * rewardPercentage) / Helpers.MAX_PERCENTAGE; - // Transferring SPA tokens + // Transferring SPA tokens to rewarder ERC20BurnableUpgradeable(Helpers.SPA).forceApprove(veSpaRewarder, toReward); IveSPARewarder(veSpaRewarder).addRewards(Helpers.SPA, toReward, 1); emit SPARewarded(toReward); @@ -190,14 +194,19 @@ contract SPABuyback is Initializable, OwnableUpgradeable, ReentrancyGuardUpgrade return (usdsOut, spaPrice); } + /// @dev Retrieves price data from the oracle contract for SPA and USDS tokens. + /// @return The price of USDS in SPA, the price of SPA in USDS, and their respective precisions. function _getOracleData() private view returns (uint256, uint256, uint256, uint256) { - // Fetches the price for SPA and USDS from oracle + // Fetches the price for SPA and USDS from the oracle contract IOracle.PriceData memory usdsData = IOracle(oracle).getPrice(Helpers.USDS); IOracle.PriceData memory spaData = IOracle(oracle).getPrice(Helpers.SPA); return (usdsData.price, spaData.price, usdsData.precision, spaData.precision); } + /// @dev Checks if the provided reward percentage is valid. + /// @param _rewardPercentage The reward percentage to validate. + /// @dev The reward percentage must be a non-zero value and should not exceed the maximum percentage value. function _isValidRewardPercentage(uint256 _rewardPercentage) private pure { Helpers._isNonZeroAmt(_rewardPercentage); Helpers._isLTEMaxPercentage(_rewardPercentage); diff --git a/contracts/buyback/YieldReserve.sol b/contracts/buyback/YieldReserve.sol index 5a00a17e..4166b469 100644 --- a/contracts/buyback/YieldReserve.sol +++ b/contracts/buyback/YieldReserve.sol @@ -10,10 +10,10 @@ import {IOracle} from "../interfaces/IOracle.sol"; import {Helpers} from "../libraries/Helpers.sol"; import {IDripper} from "../interfaces/IDripper.sol"; -/// @title YieldReserve of USDs protocol -/// @notice The contract allows users to swap the supported stable coins against yield earned by USDs protocol -/// It sends USDs to dripper for rebase, and to Buyback Contract for buyback. +/// @title YieldReserve of USDs Protocol. /// @author Sperax Foundation +/// @notice This contract allows users to swap supported stable-coins for yield earned by the USDs protocol. +/// It sends USDs to the Dripper contract for rebase and to the Buyback Contract for buyback. contract YieldReserve is ReentrancyGuard, Ownable { using SafeERC20 for IERC20; @@ -23,15 +23,18 @@ contract YieldReserve is ReentrancyGuard, Ownable { uint160 conversionFactor; } - address public vault; - address public oracle; - address public buyback; - address public dripper; - // Percentage of USDs to be sent to Buyback 5000 means 50% + // Addresses of key contracts + address public vault; // Address of the Vault contract + address public oracle; // Address of the Oracle contract + address public buyback; // Address of the Buyback contract + address public dripper; // Address of the Dripper contract + + // Percentage of USDs to be sent to Buyback (e.g., 5000 for 50%) uint256 public buybackPercentage; mapping(address => TokenData) public tokenData; + // Events event Swapped( address indexed srcToken, address indexed dstToken, @@ -50,33 +53,34 @@ contract YieldReserve is ReentrancyGuard, Ownable { event SrcTokenPermissionUpdated(address indexed token, bool isAllowed); event DstTokenPermissionUpdated(address indexed token, bool isAllowed); + // Custom error messages error InvalidSourceToken(); error InvalidDestinationToken(); error AlreadyInDesiredState(); error TokenPriceFeedMissing(); - /// @notice Constructor of the contract - /// @param _buyback Address of Buyback contract - /// @param _vault Address of Vault - /// @param _oracle Address of Oracle - /// @param _dripper Address of the dripper contract + /// @notice Constructor of the YieldReserve contract. + /// @param _buyback Address of the Buyback contract. + /// @param _vault Address of the Vault. + /// @param _oracle Address of the Oracle. + /// @param _dripper Address of the Dripper contract. constructor(address _buyback, address _vault, address _oracle, address _dripper) { updateBuybackAddress(_buyback); updateVaultAddress(_vault); updateOracleAddress(_oracle); updateDripperAddress(_dripper); - /// @dev buybackPercentage is initialized to 50% - updateBuybackPercentage(uint256(5000)); + // Initialize buybackPercentage to 50% + updateBuybackPercentage(5000); } // OPERATION FUNCTIONS - /// @notice Swap function to be called by front end - /// @param _srcToken Source / Input token - /// @param _dstToken Destination / Output token - /// @param _amountIn Input token amount - /// @param _minAmountOut Minimum output tokens expected + /// @notice Swap function to be called by frontend users. + /// @param _srcToken Source/Input token. + /// @param _dstToken Destination/Output token. + /// @param _amountIn Input token amount. + /// @param _minAmountOut Minimum output tokens expected. function swap(address _srcToken, address _dstToken, uint256 _amountIn, uint256 _minAmountOut) external { return swap({ _srcToken: _srcToken, @@ -89,9 +93,9 @@ contract YieldReserve is ReentrancyGuard, Ownable { // ADMIN FUNCTIONS - /// @notice A function to allow or disallow a `_token` as source token - /// @param _token Address of the token - /// @param _isAllowed If True, allow it to be used as src token / input token else don't allow + /// @notice Allow or disallow a specific `token` for use as a source/input token. + /// @param _token Address of the token to be allowed or disallowed. + /// @param _isAllowed If set to `true`, the token will be allowed as a source/input token; otherwise, it will be disallowed. function toggleSrcTokenPermission(address _token, bool _isAllowed) external onlyOwner { TokenData storage data = tokenData[_token]; if (data.srcAllowed == _isAllowed) revert AlreadyInDesiredState(); @@ -107,9 +111,10 @@ contract YieldReserve is ReentrancyGuard, Ownable { emit SrcTokenPermissionUpdated(_token, _isAllowed); } - /// @notice A function to allow or disallow a `_token` as output/destination token. - /// @param _token Address of the token - /// @param _isAllowed If True, allow it to be used as src token / input token else don't allow + /// @notice Allow or disallow a specific `token` for use as a destination/output token. + /// @dev Reverts if caller is not owner. + /// @param _token Address of the token to be allowed or disallowed. + /// @param _isAllowed If set to `true`, the token will be allowed as a destination/output token; otherwise, it will be disallowed. function toggleDstTokenPermission(address _token, bool _isAllowed) external onlyOwner { TokenData storage data = tokenData[_token]; if (data.dstAllowed == _isAllowed) revert AlreadyInDesiredState(); @@ -125,21 +130,20 @@ contract YieldReserve is ReentrancyGuard, Ownable { emit DstTokenPermissionUpdated(_token, _isAllowed); } - /// @notice Emergency withdrawal function for unexpected situations - /// @param _token Address of the asset to be withdrawn - /// @param _receiver Address of the receiver of tokens - /// @param _amount Amount of tokens to be withdrawn + /// @notice Emergency withdrawal function for unexpected situations. + /// @param _token Address of the asset to be withdrawn. + /// @param _receiver Address of the receiver of tokens. + /// @param _amount Amount of tokens to be withdrawn. function withdraw(address _token, address _receiver, uint256 _amount) external onlyOwner { Helpers._isNonZeroAmt(_amount); IERC20(_token).safeTransfer(_receiver, _amount); emit Withdrawn(_token, _receiver, _amount); } - /// @notice Set the % of minted USDs sent Buyback - /// @param _toBuyback % of USDs sent to Buyback - /// @dev The rest of the USDs is sent to VaultCore - /// @dev E.g. _toBuyback == 3000 means 30% of the newly - /// minted USDs would be sent to Buyback; the rest 70% to VaultCore + /// @notice Set the percentage of newly minted USDs to be sent to the Buyback contract. + /// @dev Reverts if caller is not owner. + /// @param _toBuyback The percentage of USDs sent to Buyback (e.g., 3000 for 30%). + /// @dev The remaining USDs are sent to VaultCore for rebase. function updateBuybackPercentage(uint256 _toBuyback) public onlyOwner { Helpers._isNonZeroAmt(_toBuyback); Helpers._isLTEMaxPercentage(_toBuyback); @@ -147,44 +151,49 @@ contract YieldReserve is ReentrancyGuard, Ownable { emit BuybackPercentageUpdated(_toBuyback); } - /// @notice Update Buyback contract's address - /// @param _newBuyBack New address of Buyback contract + /// @notice Update the address of the Buyback contract. + /// @dev Reverts if caller is not owner. + /// @param _newBuyBack New address of the Buyback contract. function updateBuybackAddress(address _newBuyBack) public onlyOwner { Helpers._isNonZeroAddr(_newBuyBack); buyback = _newBuyBack; emit BuybackAddressUpdated(_newBuyBack); } - /// @notice Update Oracle's address - /// @param _newOracle New address of Oracle + /// @notice Update the address of the Oracle contract. + /// @dev Reverts if caller is not owner. + /// @param _newOracle New address of the Oracle contract. function updateOracleAddress(address _newOracle) public onlyOwner { Helpers._isNonZeroAddr(_newOracle); oracle = _newOracle; emit OracleUpdated(_newOracle); } - /// @notice Update Dripper's address - /// @param _newDripper New address of Dripper + /// @notice Update the address of the Dripper contract. + /// @dev Reverts if caller is not owner. + /// @param _newDripper New address of the Dripper contract. function updateDripperAddress(address _newDripper) public onlyOwner { Helpers._isNonZeroAddr(_newDripper); dripper = _newDripper; emit DripperAddressUpdated(_newDripper); } - /// @notice Update VaultCore's address - /// @param _newVault New address of VaultCore + /// @notice Update the address of the VaultCore contract. + /// @dev Reverts if caller is not owner. + /// @param _newVault New address of the VaultCore contract. function updateVaultAddress(address _newVault) public onlyOwner { Helpers._isNonZeroAddr(_newVault); vault = _newVault; emit VaultAddressUpdated(_newVault); } - /// @notice Swap allowed src token to allowed dst token. - /// @param _srcToken Source / Input token - /// @param _dstToken Destination / Output token - /// @param _amountIn Input token amount - /// @param _minAmountOut Minimum output tokens expected - /// @param _receiver Receiver of the tokens + /// @notice Swap allowed source token for allowed destination token. + /// @dev Reverts if caller is not owner. + /// @param _srcToken Source/Input token. + /// @param _dstToken Destination/Output token. + /// @param _amountIn Input token amount. + /// @param _minAmountOut Minimum output tokens expected. + /// @param _receiver Receiver of the tokens. function swap(address _srcToken, address _dstToken, uint256 _amountIn, uint256 _minAmountOut, address _receiver) public nonReentrant @@ -199,8 +208,7 @@ contract YieldReserve is ReentrancyGuard, Ownable { // Mint USDs IERC20(_srcToken).forceApprove(vault, _amountIn); IVault(vault).mint(_srcToken, _amountIn, 0, block.timestamp); - // No need to do slippage check as it is our contract - // and the vault does that. + // No need to do a slippage check as it is our contract, and the vault does that. } IERC20(_dstToken).safeTransfer(_receiver, amountToSend); _sendUSDs(); @@ -213,10 +221,12 @@ contract YieldReserve is ReentrancyGuard, Ownable { }); } - /// @notice A `view` function to get estimated output - /// @param _srcToken Input token address - /// @param _dstToken Output token address - /// @param _amountIn Input amount of _srcToken + /// @notice Get an estimate of the output token amount for a given input token amount. + /// @dev Reverts if caller is not owner. + /// @param _srcToken Input token address. + /// @param _dstToken Output token address. + /// @param _amountIn Input amount of _srcToken. + /// @return Estimated output token amount. function getTokenBForTokenA(address _srcToken, address _dstToken, uint256 _amountIn) public view @@ -239,15 +249,15 @@ contract YieldReserve is ReentrancyGuard, Ownable { // UTILITY FUNCTIONS - /// @notice Sends USDs to buyback as per buybackPercentage - /// and rest to VaultCore for rebase + /// @notice Distributes USDs to the Buyback and Dripper contracts based on buybackPercentage. + /// @dev Sends a portion of the USDs balance to the Buyback contract and the remaining to the Dripper contract for rebase. function _sendUSDs() private { uint256 balance = IERC20(Helpers.USDS).balanceOf(address(this)); - // Calculating the amount to send to Buyback based on buybackPercentage + // Calculate the amount to send to Buyback based on buybackPercentage uint256 toBuyback = (balance * buybackPercentage) / Helpers.MAX_PERCENTAGE; - // Remaining balance will be sent to Dripper for rebase + // The remaining balance is sent to the Dripper for rebase uint256 toDripper = balance - toBuyback; emit USDsSent(toBuyback, toDripper); diff --git a/contracts/interfaces/IOracle.sol b/contracts/interfaces/IOracle.sol index fc57687a..68278d99 100644 --- a/contracts/interfaces/IOracle.sol +++ b/contracts/interfaces/IOracle.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.8.19; interface IOracle { @@ -13,12 +14,13 @@ interface IOracle { /// @notice Validates if price feed exists for a `_token` /// @param _token address of the desired token. + /// @return bool if price feed exists. /// @dev Function reverts if price feed not set. function priceFeedExists(address _token) external view returns (bool); /// @notice Gets the price feed for `_token`. /// @param _token address of the desired token. - /// @dev Function reverts if the price feed does not exists. /// @return (uint256 price, uint256 precision). + /// @dev Function reverts if the price feed does not exists. function getPrice(address _token) external view returns (PriceData memory); } diff --git a/contracts/libraries/Helpers.sol b/contracts/libraries/Helpers.sol index e7f4d5f0..8968dd74 100644 --- a/contracts/libraries/Helpers.sol +++ b/contracts/libraries/Helpers.sol @@ -4,10 +4,12 @@ pragma solidity 0.8.19; /// @title A standard library for errors and constant values /// @author Sperax Foundation library Helpers { + // Constants uint16 internal constant MAX_PERCENTAGE = 10000; address internal constant SPA = 0x5575552988A3A80504bBaeB1311674fCFd40aD4B; address internal constant USDS = 0xD74f5255D557944cf7Dd0E45FF521520002D5748; + // Errors error CustomError(string message); error InvalidAddress(); error GTMaxPercentage(uint256 actual); @@ -15,45 +17,46 @@ library Helpers { error MinSlippageError(uint256 actualAmt, uint256 minExpectedAmt); error MaxSlippageError(uint256 actualAmt, uint256 maxExpectedAmt); - /// @notice A function to check the expiry of transaction's deadline + /// @notice Checks the expiry of a transaction's deadline /// @param _deadline Deadline specified by the sender of the transaction - /// @dev Reverts if `block.timestamp` > `_deadline` + /// @dev Reverts if the current block's timestamp is greater than `_deadline` function _checkDeadline(uint256 _deadline) internal view { if (block.timestamp > _deadline) revert CustomError("Deadline passed"); } - /// @notice Check for non-zero address + /// @notice Checks for a non-zero address /// @param _addr Address to be validated - /// @dev Reverts if `_addr` == `address(0)` + /// @dev Reverts if `_addr` is equal to `address(0)` function _isNonZeroAddr(address _addr) internal pure { if (_addr == address(0)) revert InvalidAddress(); } - /// @notice Check for non-zero mount + /// @notice Checks for a non-zero amount /// @param _amount Amount to be validated - /// @dev Reverts if `_amount` == `0` + /// @dev Reverts if `_amount` is equal to `0` function _isNonZeroAmt(uint256 _amount) internal pure { if (_amount == 0) revert InvalidAmount(); } - /// @notice Check for non-zero mount + /// @notice Checks for a non-zero amount with a custom error message /// @param _amount Amount to be validated - /// @param _err Custom error messages - /// @dev Reverts if `_amount` == `0` with a CustomError string + /// @param _err Custom error message + /// @dev Reverts if `_amount` is equal to `0` with the provided custom error message function _isNonZeroAmt(uint256 _amount, string memory _err) internal pure { if (_amount == 0) revert CustomError(_err); } - /// @notice A function to check whether the `_percentage` is lesser or equal to `MAX_PERCENTAGE` - /// @param _percentage The percentage which is to be checked - /// @dev 1000 == 10% so a valid percentage is between 1 to 10000 (0.01 - 100%) + /// @notice Checks whether the `_percentage` is less than or equal to `MAX_PERCENTAGE` + /// @param _percentage The percentage to be checked + /// @dev Reverts if `_percentage` is greater than `MAX_PERCENTAGE` function _isLTEMaxPercentage(uint256 _percentage) internal pure { if (_percentage > MAX_PERCENTAGE) revert GTMaxPercentage(_percentage); } - /// @notice A function to check whether the `_percentage` is lesser or equal to `MAX_PERCENTAGE` - /// @param _percentage The percentage which is to be checked - /// @dev Reverts with a CustomError and a string + /// @notice Checks whether the `_percentage` is less than or equal to `MAX_PERCENTAGE` with a custom error message + /// @param _percentage The percentage to be checked + /// @param _err Custom error message + /// @dev Reverts with the provided custom error message if `_percentage` is greater than `MAX_PERCENTAGE` function _isLTEMaxPercentage(uint256 _percentage, string memory _err) internal pure { if (_percentage > MAX_PERCENTAGE) revert CustomError(_err); } diff --git a/contracts/oracle/BaseUniOracle.sol b/contracts/oracle/BaseUniOracle.sol index b79f04d0..035418d6 100644 --- a/contracts/oracle/BaseUniOracle.sol +++ b/contracts/oracle/BaseUniOracle.sol @@ -7,9 +7,9 @@ import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV import {IUniswapUtils} from "../strategies/uniswap/interfaces/IUniswapUtils.sol"; interface IMasterPriceOracle { - /// @notice Validates if price feed exists for a `_token` + /// @notice Validates if price feed exists for a `_token`. /// @param _token address of the desired token. - /// @return Returns bool + /// @return Returns bool (true if exists). function priceFeedExists(address _token) external view returns (bool); /// @notice Gets the price feed for `_token`. @@ -19,9 +19,9 @@ interface IMasterPriceOracle { function getPrice(address _token) external view returns (uint256, uint256); } -/// @title Base Uni Oracle contract for USDs protocol -/// @author Sperax Foundation -/// @notice Has all the base functionalities, variables etc to be implemented by child contracts +/// @title Base Uni Oracle contract for USDs protocol. +/// @author Sperax Foundation. +/// @notice Has all the base functionalities, variables etc to be implemented by child contracts. abstract contract BaseUniOracle is Ownable { address public constant UNISWAP_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984; address public constant UNISWAP_UTILS = 0xd2Aa19D3B7f8cdb1ea5B782c5647542055af415e; @@ -29,11 +29,11 @@ abstract contract BaseUniOracle is Ownable { uint256 internal constant MA_PERIOD_LOWER_BOUND = 10 minutes; uint256 internal constant MA_PERIOD_UPPER_BOUND = 2 hours; - address public masterOracle; // Address of the master price oracle - address public pool; // Address of the uniswap pool for the token and quoteToken - address public quoteToken; // Address of the quoteToken - uint32 public maPeriod; // Moving average period - uint128 public quoteTokenPrecision; // QuoteToken price precision + address public masterOracle; // Address of the master price oracle. + address public pool; // Address of the uniswap pool for the token and quoteToken. + address public quoteToken; // Address of the quoteToken. + uint32 public maPeriod; // Moving average period. + uint128 public quoteTokenPrecision; // QuoteToken price precision. event MasterOracleUpdated(address newOracle); event UniMAPriceDataChanged(address quoteToken, uint24 feeTier, uint32 maPeriod); @@ -44,12 +44,13 @@ abstract contract BaseUniOracle is Ownable { error InvalidAddress(); error InvalidMaPeriod(); - /// @notice A function to get price - /// @return (uint256, uint256) Returns price and price precision + /// @notice A function to get price. + /// @return (uint256 price, uint256 precision). function getPrice() external view virtual returns (uint256, uint256); - /// @notice Updates the master price oracle - /// @param _newOracle Address of the desired oracle + /// @notice Updates the master price oracle. + /// @param _newOracle Address of the desired oracle. + /// @dev Reverts if caller is not the owner. function updateMasterOracle(address _newOracle) public onlyOwner { _isNonZeroAddr(_newOracle); masterOracle = _newOracle; @@ -59,11 +60,12 @@ abstract contract BaseUniOracle is Ownable { emit MasterOracleUpdated(_newOracle); } - /// @notice Configures the uniswap price feed for the `_token` - /// @param _token Desired base token - /// @param _quoteToken Token pair to get price Feed from - /// @param _feeTier feeTier for the token pair - /// @param _maPeriod moving average period + /// @notice Configures the uniswap price feed for the `_token`. + /// @param _token Desired base token. + /// @param _quoteToken Token pair to get price Feed from. + /// @param _feeTier feeTier for the token pair. + /// @param _maPeriod moving average period. + /// @dev Reverts if caller is not the owner. function setUniMAPriceData(address _token, address _quoteToken, uint24 _feeTier, uint32 _maPeriod) public onlyOwner @@ -73,12 +75,12 @@ abstract contract BaseUniOracle is Ownable { revert FeedUnavailable(); } - // Validate if the oracle has a price feed for the _quoteToken + // Validate if the oracle has a price feed for the _quoteToken. if (!IMasterPriceOracle(masterOracle).priceFeedExists(_quoteToken)) { revert QuoteTokenFeedMissing(); } - // Validate if maPeriod is between 10 minutes and 2 hours + // Validate if maPeriod is between 10 minutes and 2 hours. if (_maPeriod < MA_PERIOD_LOWER_BOUND || _maPeriod > MA_PERIOD_UPPER_BOUND) { revert InvalidMaPeriod(); } @@ -91,17 +93,17 @@ abstract contract BaseUniOracle is Ownable { emit UniMAPriceDataChanged(_quoteToken, _feeTier, _maPeriod); } - /// @notice Gets the quoteToken's price and pricePrecision - /// @dev For a valid quote should have a configured price feed in the Master Oracle + /// @notice Gets the quoteToken's price and pricePrecision. + /// @dev For a valid quote should have a configured price feed in the Master Oracle. function _getQuoteTokenPrice() internal view returns (uint256, uint256) { return IMasterPriceOracle(masterOracle).getPrice(quoteToken); } - /// @notice get the Uniswap V3 Moving Average (MA) of tokenBPerTokenA - /// @param _tokenA is baseToken - /// @dev e.g. for USDsPerSPA, _tokenA = SPA and tokenB = USDs - /// @param _tokenAPrecision Token a decimal precision (18 decimals -> 1e18, 6 decimals -> 1e6 ... etc) - /// @dev tokenBPerTokenA has the same precision as tokenB + /// @notice Get the Uniswap V3 Moving Average (MA) of tokenBPerTokenA. + /// @param _tokenA is baseToken. + /// @dev e.g. for USDsPerSPA, _tokenA = SPA and tokenB = USDs. + /// @param _tokenAPrecision Token a decimal precision (18 decimals -> 1e18, 6 decimals -> 1e6 ... etc). + /// @dev tokenBPerTokenA has the same precision as tokenB. function _getUniMAPrice(address _tokenA, uint128 _tokenAPrecision) internal view returns (uint256) { // get MA tick uint32 oldestObservationSecondsAgo = IUniswapUtils(UNISWAP_UTILS).getOldestObservationSecondsAgo(pool); diff --git a/contracts/oracle/ChainlinkOracle.sol b/contracts/oracle/ChainlinkOracle.sol index ca4b7c7e..c135b3de 100644 --- a/contracts/oracle/ChainlinkOracle.sol +++ b/contracts/oracle/ChainlinkOracle.sol @@ -6,26 +6,34 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; /// @title Chainlink Oracle contract for USDs protocol /// @author Sperax Foundation -/// @notice Has all the base functionalities, variables etc to be implemented by child contracts +/// @notice This contract provides functionality for obtaining price data from Chainlink's price feeds for various assets. contract ChainlinkOracle is Ownable { + // Struct to store price feed and precision information for each token struct TokenData { - address priceFeed; - uint96 timeout; - uint256 pricePrecision; + address priceFeed; // Address of the Chainlink price feed + uint96 timeout; // timeout value used for price staleness check of a price feed + uint256 pricePrecision; // Precision factor for the token's price } + // Struct to set up token data during contract deployment struct SetupTokenData { - address token; - TokenData data; + address token; // Address of the token + TokenData data; // Token price feed configuration } + // Address of Chainlink's sequencer uptime feed address public constant CHAINLINK_SEQ_UPTIME_FEED = 0xFdB631F5EE196F0ed6FAa767959853A9F217697D; + + // Grace period time in seconds uint256 private constant GRACE_PERIOD_TIME = 3600; + // Mapping to store price feed and precision data for each supported token mapping(address => TokenData) public getTokenData; + // Events event TokenDataChanged(address indexed tokenAddr, address priceFeed, uint256 pricePrecision, uint96 timeout); + // Custom error messages error TokenNotSupported(address token); error ChainlinkSequencerDown(); error GracePeriodNotPassed(uint256 timeSinceUp); @@ -34,6 +42,8 @@ contract ChainlinkOracle is Ownable { error InvalidPrice(); error InvalidPricePrecision(); + /// @notice Constructor to set up initial token data during contract deployment + /// @param _priceFeedData Array of token setup data containing token addresses and price feed configurations constructor(SetupTokenData[] memory _priceFeedData) { for (uint256 i; i < _priceFeedData.length;) { setTokenData(_priceFeedData[i].token, _priceFeedData[i].data); @@ -43,9 +53,10 @@ contract ChainlinkOracle is Ownable { } } - /// @notice Configures chainlink price feed for an asset + /// @notice Configures Chainlink price feed for an asset /// @param _token Address of the desired token /// @param _tokenData Token price feed configuration + /// @dev Only the contract owner can call this function function setTokenData(address _token, TokenData memory _tokenData) public onlyOwner { if (_tokenData.pricePrecision != 10 ** AggregatorV3Interface(_tokenData.priceFeed).decimals()) { revert InvalidPricePrecision(); @@ -54,21 +65,25 @@ contract ChainlinkOracle is Ownable { emit TokenDataChanged(_token, _tokenData.priceFeed, _tokenData.pricePrecision, _tokenData.timeout); } - /// @notice Gets the token price and price precision + /// @notice Gets the price and price precision of a supported token /// @param _token Address of the desired token /// @dev Ref: https://docs.chain.link/data-feeds/l2-sequencer-feeds - /// @return (uint256, uint256) price and pricePrecision + /// @return (uint256, uint256) The token's price and its price precision function getTokenPrice(address _token) public view returns (uint256, uint256) { TokenData memory tokenInfo = getTokenData[_token]; + + // Check if the token is supported if (tokenInfo.pricePrecision == 0) revert TokenNotSupported(_token); + // Retrieve the latest data from Chainlink's sequencer uptime feed. (, int256 answer, uint256 startedAt,,) = AggregatorV3Interface(CHAINLINK_SEQ_UPTIME_FEED).latestRoundData(); + // Answer == 0: Sequencer is up // Answer == 1: Sequencer is down bool isSequencerUp = answer == 0; if (!isSequencerUp) revert ChainlinkSequencerDown(); - // Make sure the grace period has passed after the sequencer is back up. + // Ensure the grace period has passed since the sequencer is back up. uint256 timeSinceUp = block.timestamp - startedAt; if (timeSinceUp <= GRACE_PERIOD_TIME) { revert GracePeriodNotPassed(timeSinceUp); diff --git a/contracts/oracle/MasterPriceOracle.sol b/contracts/oracle/MasterPriceOracle.sol index d05a7e24..23e98c3c 100644 --- a/contracts/oracle/MasterPriceOracle.sol +++ b/contracts/oracle/MasterPriceOracle.sol @@ -8,21 +8,23 @@ import {IOracle} from "../interfaces/IOracle.sol"; /// @author Sperax Foundation /// @notice Communicates with different price feeds to get the price contract MasterPriceOracle is Ownable, IOracle { - // Handles price feed data for a give token. + /// Store price feed data for tokens. mapping(address => PriceFeedData) public tokenPriceFeed; + // Events event PriceFeedUpdated(address indexed token, address indexed source, bytes msgData); event PriceFeedRemoved(address indexed token); + // Custom error messages error UnableToFetchPriceFeed(address token); error InvalidPriceFeed(address token); error PriceFeedNotFound(address token); - /// @notice Update/Add price feed for `_token` + /// @notice Add/Update price feed for `_token` /// @param _token address of the desired token. - /// @dev Have to be extra cautious while updating the price feed. /// @param _source price feed source. /// @param _data call data for fetching the price feed. + /// @dev Have to be extra cautious while updating the price feed. function updateTokenPriceFeed(address _token, address _source, bytes memory _data) external onlyOwner { // Validate if the price feed is being emitted correctly. _getPriceFeed(_token, _source, _data); @@ -31,8 +33,8 @@ contract MasterPriceOracle is Ownable, IOracle { emit PriceFeedUpdated(_token, _source, _data); } - /// @notice Remove an existing price feed for `token`. - /// @param _token Address of the token. + /// @notice Remove an existing price feed for `_token`. + /// @param _token address of the token. function removeTokenPriceFeed(address _token) external onlyOwner { if (tokenPriceFeed[_token].source == address(0)) { revert PriceFeedNotFound(_token); diff --git a/contracts/oracle/SPAOracle.sol b/contracts/oracle/SPAOracle.sol index 763b8e21..c27245f6 100644 --- a/contracts/oracle/SPAOracle.sol +++ b/contracts/oracle/SPAOracle.sol @@ -9,8 +9,8 @@ interface IDiaOracle { } /// @title Oracle contract for USDs protocol for SPA token -/// @dev providing SPA prices (from Uniswap V3 pools and DIA oracle) /// @author Sperax Foundation +/// @dev providing SPA prices (from Uniswap V3 pools and DIA oracle) contract SPAOracle is BaseUniOracle { address public constant SPA = 0x5575552988A3A80504bBaeB1311674fCFd40aD4B; address public constant DIA_ORACLE = 0x7919D08e0f41398cBc1e0A8950Df831e4895c19b; @@ -22,13 +22,20 @@ contract SPAOracle is BaseUniOracle { uint256 public weightDIA; uint256 public diaMaxTimeThreshold; + // Events event DIAParamsUpdated(uint256 weightDIA, uint128 diaMaxTimeThreshold); - // Custom Errors + // Custom error messages error PriceTooOld(); error InvalidWeight(); error InvalidTime(); + /// @notice Constructor of SPAOracle contract. + /// @param _masterOracle Address of master oracle. + /// @param _quoteToken Quote token's address. + /// @param _feeTier Fee tier. + /// @param _maPeriod Moving average period. + /// @param _weightDIA DIA oracle's weight. constructor(address _masterOracle, address _quoteToken, uint24 _feeTier, uint32 _maPeriod, uint256 _weightDIA) { _isNonZeroAddr(_masterOracle); masterOracle = _masterOracle; @@ -36,10 +43,10 @@ contract SPAOracle is BaseUniOracle { updateDIAParams(_weightDIA, 600); } - /// @notice Get SPA price + /// @notice Get SPA price. /// @dev SPA price is a weighted combination of DIA SPA price and Uni SPA - /// price - /// @return uint256 SPA price with precision SPA_PRICE_PRECISION (10^18) + /// price. + /// @return uint256 (uint256, uint256) SPA price with precision SPA_PRICE_PRECISION. function getPrice() external view override returns (uint256, uint256) { uint256 weightUNI = MAX_WEIGHT - weightDIA; // calculate weighted UNI USDsPerSPA @@ -57,13 +64,13 @@ contract SPAOracle is BaseUniOracle { return (spaPrice, SPA_PRICE_PRECISION); } - /// @notice Update the weights of DIA SPA price and Uni SPA price + /// @notice Update the weights of DIA SPA price and Uni SPA price. /// @dev SPA price is a weighted combination of DIA SPA price and Uni SPA - /// price + /// price. /// @dev `_weightDIA` = 70 and `_weightUNI` = 30 would result in a 70% and 30% - /// weights on SPA's final price - /// @param _weightDIA weight for DIA price feed - /// @param _maxTime max age of price feed from DIA + /// weights on SPA's final price. + /// @param _weightDIA weight for DIA price feed. + /// @param _maxTime max age of price feed from DIA. function updateDIAParams(uint256 _weightDIA, uint128 _maxTime) public onlyOwner { if (_weightDIA > MAX_WEIGHT) { revert InvalidWeight(); @@ -78,8 +85,8 @@ contract SPAOracle is BaseUniOracle { emit DIAParamsUpdated(_weightDIA, _maxTime); } - /// @notice Query SPA price according to a UniV3 SPA pool - /// @return uint256 SPA Uni price with precision DIA_PRECISION + /// @notice Query SPA price according to a UniV3 SPA pool. + /// @return uint256 SPA Uni price with precision DIA_PRECISION. function _getSPAUniPrice() private view returns (uint256) { uint256 quoteTokenAmtPerSPA = _getUniMAPrice(SPA, SPA_PRECISION); (uint256 quoteTokenPrice, uint256 quoteTokenPricePrecision) = _getQuoteTokenPrice(); diff --git a/contracts/oracle/USDsOracle.sol b/contracts/oracle/USDsOracle.sol index 5d5182e0..464060af 100644 --- a/contracts/oracle/USDsOracle.sol +++ b/contracts/oracle/USDsOracle.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.19; import {BaseUniOracle} from "./BaseUniOracle.sol"; /// @title Oracle contract for USDs protocol -/// @dev providing USDs prices (from Uniswap V3 pools) /// @author Sperax Foundation +/// @dev providing USDs prices (from Uniswap V3 pools) contract USDsOracle is BaseUniOracle { address public constant USDS = 0xD74f5255D557944cf7Dd0E45FF521520002D5748; uint128 private constant USDS_PRECISION = 1e18; @@ -17,8 +17,8 @@ contract USDsOracle is BaseUniOracle { setUniMAPriceData(USDS, _quoteToken, _feeTier, _maPeriod); } - /// @notice Gets the USDs price from chainlink - /// @return (uint256, uint256) price and pricePrecision + /// @notice Gets the USDs price from chainlink. + /// @return (uint256, uint256) USDs price and pricePrecision. function getPrice() external view override returns (uint256, uint256) { uint256 quoteTokenAmtPerUSDs = _getUniMAPrice(USDS, USDS_PRECISION); (uint256 quoteTokenPrice, uint256 quoteTokenPricePrecision) = _getQuoteTokenPrice(); diff --git a/contracts/rebase/Dripper.sol b/contracts/rebase/Dripper.sol index 9569ec1c..91e10d02 100644 --- a/contracts/rebase/Dripper.sol +++ b/contracts/rebase/Dripper.sol @@ -6,25 +6,30 @@ import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeE import {Helpers} from "../libraries/Helpers.sol"; import {IDripper} from "../interfaces/IDripper.sol"; -/// @title Dripper for USDs protocol -/// @notice Contract to release tokens to a recipient at a steady rate +/// @title Dripper for USDs Protocol. /// @author Sperax Foundation -/// @dev This contract releases USDs at a steady rate to the Vault for rebasing USDs +/// @notice This contract releases tokens at a steady rate to the Vault contract, for rebasing the USDs stablecoin. +/// @dev The Dripper contract ensures that tokens are released gradually over time, allowing for consistent and controlled distribution. contract Dripper is IDripper, Ownable { using SafeERC20 for IERC20; - address public vault; // Address of the contract to get the dripped tokens - uint256 public dripRate; // Calculated dripping rate - uint256 public dripDuration; // Duration to drip the available amount - uint256 public lastCollectTS; // last collection ts + address public vault; // Address of the contract receiving the dripped tokens. + uint256 public dripRate; // Calculated dripping rate. + uint256 public dripDuration; // Duration over which tokens are dripped. + uint256 public lastCollectTS; // Timestamp of the last collection. + // Events event Collected(uint256 amount); event Recovered(address owner, uint256 amount); event VaultUpdated(address vault); event DripDurationUpdated(uint256 dripDuration); + // Custom error messages error NothingToRecover(); + /// @notice Constructor to initialize the Dripper. + /// @param _vault Address of the contract that receives the dripped tokens. + /// @param _dripDuration The duration over which tokens are dripped. constructor(address _vault, uint256 _dripDuration) { updateVault(_vault); updateDripDuration(_dripDuration); @@ -33,8 +38,8 @@ contract Dripper is IDripper, Ownable { // Admin functions - /// @notice Emergency fund recovery function - /// @param _asset Address of the asset + /// @notice Emergency fund recovery function. + /// @param _asset Address of the asset to recover. /// @dev Transfers the asset to the owner of the contract. function recoverTokens(address _asset) external onlyOwner { uint256 bal = IERC20(_asset).balanceOf(address(this)); @@ -43,8 +48,9 @@ contract Dripper is IDripper, Ownable { emit Recovered(msg.sender, bal); } - /// @notice Transfers the dripped tokens to the vault - /// @dev Function also updates the dripRate based on the fund state + /// @notice Transfers the dripped tokens to the vault. + /// @dev This function also updates the dripRate based on the fund state. + /// @return The amount of tokens collected and transferred to the vault. function collect() external returns (uint256) { uint256 collectableAmt = getCollectableAmt(); if (collectableAmt != 0) { @@ -56,32 +62,32 @@ contract Dripper is IDripper, Ownable { return collectableAmt; } - /// @notice Function to be used to send USDs to dripper and update `dripRate` - /// @param _amount Amount of USDs to be sent form caller to this contract + /// @notice Function to be used to send USDs to dripper and update `dripRate`. + /// @param _amount Amount of USDs to be sent form caller to this contract. function addUSDs(uint256 _amount) external { Helpers._isNonZeroAmt(_amount); IERC20(Helpers.USDS).safeTransferFrom(msg.sender, address(this), _amount); dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; } - /// @notice Update the vault address - /// @param _vault Address of the new vault + /// @notice Update the vault address. + /// @param _vault Address of the new vault contract. function updateVault(address _vault) public onlyOwner { Helpers._isNonZeroAddr(_vault); vault = _vault; emit VaultUpdated(_vault); } - /// @notice Updates the dripDuration - /// @param _dripDuration The desired drip duration to be set + /// @notice Updates the dripDuration. + /// @param _dripDuration The desired drip duration to be set. function updateDripDuration(uint256 _dripDuration) public onlyOwner { Helpers._isNonZeroAmt(_dripDuration); dripDuration = _dripDuration; emit DripDurationUpdated(_dripDuration); } - /// @notice Gets the collectible amount of token at current time - /// @return Returns the collectible amount + /// @notice Gets the collectible amount of tokens at the current time. + /// @return The amount of tokens that can be collected. function getCollectableAmt() public view returns (uint256) { uint256 timeElapsed = block.timestamp - lastCollectTS; uint256 dripped = timeElapsed * dripRate; diff --git a/contracts/rebase/RebaseManager.sol b/contracts/rebase/RebaseManager.sol index 4511aa9c..47f5e2fd 100644 --- a/contracts/rebase/RebaseManager.sol +++ b/contracts/rebase/RebaseManager.sol @@ -9,28 +9,31 @@ import {IDripper} from "../interfaces/IDripper.sol"; import {Helpers} from "../libraries/Helpers.sol"; import {IRebaseManager} from "../interfaces/IRebaseManager.sol"; -/// @title RebaseManager for USDs protocol +/// @title Rebase Manager for USDs Protocol /// @author Sperax Foundation -/// @notice Contract handles the configuration for rebase of USDs token -/// Which enables rebase only when the pre-requisites are fulfilled +/// @notice This contract handles the configuration and execution of the rebasing mechanism for the USDs stablecoin. +/// It ensures that rebases occur only when certain prerequisites are fulfilled, such as the time gap between rebases and acceptable APR (Annual Percentage Rate) ranges. +/// @dev The Rebase Manager coordinates with the Vault and Dripper contracts to manage the rebase process. contract RebaseManager is IRebaseManager, Ownable { using SafeMath for uint256; uint256 private constant ONE_YEAR = 365 days; - address public vault; // address of the vault - address public dripper; // address of the dripper for collecting USDs + address public vault; // Address of the vault contract + address public dripper; // Address of the dripper contract for collecting USDs - uint256 public gap; // min gap between two consecutive rebases - uint256 public aprCap; // max allowed APR% for a rebase - uint256 public aprBottom; // min allowed APR% for a rebase - uint256 public lastRebaseTS; // timestamp of the last rebase transaction + uint256 public gap; // Minimum time gap required between two consecutive rebases + uint256 public aprCap; // Maximum allowed APR for a rebase + uint256 public aprBottom; // Minimum allowed APR for a rebase + uint256 public lastRebaseTS; // Timestamp of the last rebase transaction + // Events event VaultUpdated(address vault); event DripperUpdated(address dripper); event GapUpdated(uint256 gap); event APRUpdated(uint256 aprBottom, uint256 aprCap); + // Custom error messages error CallerNotVault(address caller); error InvalidAPRConfig(uint256 aprBottom, uint256 aprCap); @@ -39,6 +42,12 @@ contract RebaseManager is IRebaseManager, Ownable { _; } + /// @notice Constructor to initialize the Rebase Manager + /// @param _vault Address of the vault contract + /// @param _dripper Address of the dripper contract for collecting USDs + /// @param _gap Minimum time gap required between two consecutive rebases + /// @param _aprCap Maximum allowed APR for a rebase + /// @param _aprBottom Minimum allowed APR for a rebase constructor( address _vault, address _dripper, @@ -54,22 +63,22 @@ contract RebaseManager is IRebaseManager, Ownable { } /// @notice Get the current amount valid for rebase - /// @dev Function is called by vault while rebasing - /// @return returns the available amount for rebasing USDs + /// @dev Function is called by the vault while rebasing + /// @return The available amount for rebasing USDs function fetchRebaseAmt() external onlyVault returns (uint256) { uint256 rebaseFund = getAvailableRebaseAmt(); - // Get the current min and max amount based on APR config + // Get the current minimum and maximum amount based on APR config (uint256 minRebaseAmt, uint256 maxRebaseAmt) = getMinAndMaxRebaseAmt(); // Cap the rebase amount uint256 rebaseAmt = (rebaseFund > maxRebaseAmt) ? maxRebaseAmt : rebaseFund; - // Skip if insufficient USDs to rebase or insufficient time has elapsed + // Skip if there are insufficient USDs to rebase or insufficient time has elapsed if (rebaseAmt < minRebaseAmt || block.timestamp < lastRebaseTS + gap) { return 0; } - // update the rebase timestamp + // Update the rebase timestamp lastRebaseTS = block.timestamp; // Collect the dripped USDs amount for rebase @@ -79,31 +88,31 @@ contract RebaseManager is IRebaseManager, Ownable { } /// @notice Updates the vault address - /// @param _newVault Address of the new vault + /// @param _newVault Address of the new vault contract function updateVault(address _newVault) public onlyOwner { Helpers._isNonZeroAddr(_newVault); vault = _newVault; emit VaultUpdated(_newVault); } - /// @notice Updates the dripper for USDs vault - /// @param _dripper address of the new dripper contract + /// @notice Updates the dripper contract for USDs vault + /// @param _dripper Address of the new dripper contract function updateDripper(address _dripper) public onlyOwner { Helpers._isNonZeroAddr(_dripper); dripper = _dripper; emit DripperUpdated(_dripper); } - /// @notice Update the minimum gap required b/w two rebases - /// @param _gap updated gap time + /// @notice Update the minimum time gap required between two rebases + /// @param _gap Updated gap time function updateGap(uint256 _gap) public onlyOwner { gap = _gap; emit GapUpdated(_gap); } /// @notice Update the APR requirements for each rebase - /// @param _aprCap new MAX APR for rebase - /// @param _aprBottom new MIN APR for rebase + /// @param _aprCap New maximum APR for a rebase + /// @param _aprBottom New minimum APR for a rebase function updateAPR(uint256 _aprBottom, uint256 _aprCap) public onlyOwner { if (_aprCap < _aprBottom) revert InvalidAPRConfig(_aprBottom, _aprCap); aprCap = _aprCap; @@ -112,15 +121,15 @@ contract RebaseManager is IRebaseManager, Ownable { } /// @notice Gets the current available rebase fund - /// @return Returns currentBal in vault + collectable dripped USDs amt + /// @return Current balance in the vault plus collectable dripped USDs amount function getAvailableRebaseAmt() public view returns (uint256) { uint256 collectableAmt = IDripper(dripper).getCollectableAmt(); uint256 currentBal = IERC20(Helpers.USDS).balanceOf(vault); return currentBal + collectableAmt; } - /// @notice Gets the min and max rebase USDs amount based on the APR config - /// @return min and max rebase amount + /// @notice Gets the minimum and maximum rebase USDs amount based on the APR config + /// @return Minimum and maximum rebase amounts function getMinAndMaxRebaseAmt() public view returns (uint256, uint256) { uint256 principal = IUSDs(Helpers.USDS).totalSupply() - IUSDs(Helpers.USDS).nonRebasingSupply(); uint256 timeElapsed = block.timestamp - lastRebaseTS; diff --git a/contracts/strategies/InitializableAbstractStrategy.sol b/contracts/strategies/InitializableAbstractStrategy.sol index 86037243..09dbcfc2 100755 --- a/contracts/strategies/InitializableAbstractStrategy.sol +++ b/contracts/strategies/InitializableAbstractStrategy.sol @@ -12,6 +12,9 @@ interface IStrategyVault { function yieldReceiver() external view returns (address); } +/// @title Base strategy for USDs protocol +/// @author Sperax Foundation +/// @dev Contract acts as a single interface for implementing specific yield-earning strategies. abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; @@ -68,80 +71,84 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade _disableInitializers(); } - /// @notice Update the linked vault contract - /// @param _newVault Address of the new Vault + /// @notice Update the linked vault contract. + /// @param _newVault Address of the new Vault. function updateVault(address _newVault) external onlyOwner { Helpers._isNonZeroAddr(_newVault); vault = _newVault; emit VaultUpdated(_newVault); } - /// @notice Updates the HarvestIncentive rate for the user - /// @param _newRate new Desired rate + /// @notice Updates the HarvestIncentive rate for the user. + /// @param _newRate new Desired rate. function updateHarvestIncentiveRate(uint16 _newRate) external onlyOwner { Helpers._isLTEMaxPercentage(_newRate); harvestIncentiveRate = _newRate; emit HarvestIncentiveRateUpdated(_newRate); } - /// @notice A function to recover any erc20 token sent to this strategy mistakenly - /// @dev Only callable by owner - /// @param token Address of the token - /// @param receiver Receiver of the token - /// @param amount Amount to be recovered - /// @dev reverts if amount > balance + /// @notice A function to recover any erc20 token sent to this strategy mistakenly. + /// @dev Only callable by owner. + /// @param token Address of the token. + /// @param receiver Receiver of the token. + /// @param amount Amount to be recovered. + /// @dev reverts if amount > balance. function recoverERC20(address token, address receiver, uint256 amount) external onlyOwner { IERC20(token).safeTransfer(receiver, amount); } - /// @dev Deposit an amount of asset into the platform - /// @param _asset Address for the asset - /// @param _amount Units of asset to deposit + /// @dev Deposit an amount of asset into the platform. + /// @param _asset Address for the asset. + /// @param _amount Units of asset to deposit. function deposit(address _asset, uint256 _amount) external virtual; /// @dev Withdraw an amount of asset from the platform. - /// @param _recipient Address to which the asset should be sent - /// @param _asset Address of the asset - /// @param _amount Units of asset to withdraw - /// @return amountReceived The actual amount received + /// @param _recipient Address to which the asset should be sent. + /// @param _asset Address of the asset. + /// @param _amount Units of asset to withdraw. + /// @return amountReceived The actual amount received. function withdraw(address _recipient, address _asset, uint256 _amount) external virtual returns (uint256 amountReceived); - /// @dev Withdraw an amount of asset from the platform to vault - /// @param _asset Address of the asset - /// @param _amount Units of asset to withdraw + /// @dev Withdraw an amount of asset from the platform to vault. + /// @param _asset Address of the asset. + /// @param _amount Units of asset to withdraw. function withdrawToVault(address _asset, uint256 _amount) external virtual returns (uint256 amount); /// @notice Withdraw the interest earned of asset from the platform. - /// @param _asset Address of the asset + /// @param _asset Address of the asset. function collectInterest(address _asset) external virtual; - /// @notice Collect accumulated reward token and send to Vault + /// @notice Collect accumulated reward token and send to Vault. function collectReward() external virtual; /// @notice Get the amount of a specific asset held in the strategy, - /// excluding the interest - /// @dev Curve: assuming balanced withdrawal - /// @param _asset Address of the asset + /// excluding the interest. + /// @dev Curve: assuming balanced withdrawal. + /// @param _asset Address of the asset. + /// @return uint256 Balance of _asset in the strategy. function checkBalance(address _asset) external view virtual returns (uint256); /// @notice Get the amount of a specific asset held in the strategy, /// excluding the interest and any locked liquidity that is not - /// available for instant withdrawal - /// @dev Curve: assuming balanced withdrawal - /// @param _asset Address of the asset + /// available for instant withdrawal. + /// @dev Curve: assuming balanced withdrawal. + /// @param _asset Address of the asset. + /// @return uint256 Available balance inside the strategy for _asset. function checkAvailableBalance(address _asset) external view virtual returns (uint256); - /// @notice AAVE: Get the interest earned on a specific asset - /// Curve: Get the total interest earned + /// @notice AAVE: Get the interest earned on a specific asset. + /// Curve: Get the total interest earned. /// @dev Curve: to avoid double-counting, _asset has to be of index - /// 'entryTokenIndex' - /// @param _asset Address of the asset + /// 'entryTokenIndex'. + /// @param _asset Address of the asset. + /// @return uint256 Amount of interest earned. function checkInterestEarned(address _asset) external view virtual returns (uint256); - /// @notice Get the amount of claimable reward + /// @notice Get the amount of claimable reward. + /// @return struct array of type RewardData (address token, uint256 amount). function checkRewardEarned() external view virtual returns (RewardData[] memory); /// @notice Get the total LP token balance for a asset. @@ -149,13 +156,13 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade function checkLPTokenBalance(address _asset) external view virtual returns (uint256); /// @notice Check if an asset/collateral is supported. - /// @param _asset Address of the asset - /// @return bool Whether asset is supported + /// @param _asset Address of the asset. + /// @return bool Whether asset is supported. function supportsCollateral(address _asset) external view virtual returns (bool); - /// @notice Change to a new depositSlippage & withdrawSlippage - /// @param _depositSlippage Slippage tolerance for allocation - /// @param _withdrawSlippage Slippage tolerance for withdrawal + /// @notice Change to a new depositSlippage & withdrawSlippage. + /// @param _depositSlippage Slippage tolerance for allocation. + /// @param _withdrawSlippage Slippage tolerance for withdrawal. function updateSlippage(uint16 _depositSlippage, uint16 _withdrawSlippage) public onlyOwner { Helpers._isLTEMaxPercentage(_depositSlippage); Helpers._isLTEMaxPercentage(_withdrawSlippage); @@ -164,10 +171,10 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade emit SlippageUpdated(_depositSlippage, _withdrawSlippage); } - /// @notice Initialize the base properties of the strategy - /// @param _vault Address of the USDs Vault - /// @param _depositSlippage Allowed max slippage for Deposit - /// @param _withdrawSlippage Allowed max slippage for withdraw + /// @notice Initialize the base properties of the strategy. + /// @param _vault Address of the USDs Vault. + /// @param _depositSlippage Allowed max slippage for Deposit. + /// @param _withdrawSlippage Allowed max slippage for withdraw. function _initialize(address _vault, uint16 _depositSlippage, uint16 _withdrawSlippage) internal { OwnableUpgradeable.__Ownable_init(); ReentrancyGuardUpgradeable.__ReentrancyGuard_init(); @@ -179,9 +186,9 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade /// @notice Provide support for asset by passing its pToken address. /// Add to internal mappings and execute the platform specific, - /// abstract method `_abstractSetPToken` - /// @param _asset Address for the asset - /// @param _pToken Address for the corresponding platform token + /// abstract method `_abstractSetPToken`. + /// @param _asset Address for the asset. + /// @param _pToken Address for the corresponding platform token. function _setPTokenAddress(address _asset, address _pToken) internal { address currentPToken = assetToPToken[_asset]; if (currentPToken != address(0)) { @@ -199,8 +206,9 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade } /// @notice Remove a supported asset by passing its index. - /// This method can only be called by the system owner - /// @param _assetIndex Index of the asset to be removed + /// This method can only be called by the system owner. + /// @param _assetIndex Index of the asset to be removed. + /// @return asset address which is removed. function _removePTokenAddress(uint256 _assetIndex) internal returns (address asset) { uint256 numAssets = assetsMapped.length; if (_assetIndex >= numAssets) revert InvalidIndex(); @@ -214,12 +222,13 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade emit PTokenRemoved(asset, pToken); } - /// @notice Splits and sends the accumulated rewards to harvestor and yield receiver - /// @param _token Address of the reward token - /// @param _yieldReceiver Address of the yield receiver - /// @param _harvestor Address of the harvestor - /// @param _amount to be split and sent - /// @dev Sends the amount to harvestor as per `harvestIncentiveRate` and sends the rest to yield receiver + /// @notice Splits and sends the accumulated rewards to harvestor and yield receiver. + /// @param _token Address of the reward token. + /// @param _yieldReceiver Address of the yield receiver. + /// @param _harvestor Address of the harvestor. + /// @param _amount to be split and sent. + /// @dev Sends the amount to harvestor as per `harvestIncentiveRate` and sends the rest to yield receiver. + /// @return uint256 Harvested amount sent to yield receiver. function _splitAndSendReward(address _token, address _yieldReceiver, address _harvestor, uint256 _amount) internal returns (uint256) @@ -236,8 +245,8 @@ abstract contract InitializableAbstractStrategy is Initializable, OwnableUpgrade return _amount; } - /// @notice Call the necessary approvals for the underlying strategy - /// @param _asset Address of the asset + /// @notice Call the necessary approvals for the underlying strategy. + /// @param _asset Address of the asset. /// @param _pToken Address of the corresponding receipt token. function _abstractSetPToken(address _asset, address _pToken) internal virtual; } diff --git a/contracts/strategies/aave/AaveStrategy.sol b/contracts/strategies/aave/AaveStrategy.sol index 16fb7d6c..82ee96b5 100755 --- a/contracts/strategies/aave/AaveStrategy.sol +++ b/contracts/strategies/aave/AaveStrategy.sol @@ -6,9 +6,9 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {InitializableAbstractStrategy, Helpers, IStrategyVault} from "../InitializableAbstractStrategy.sol"; import {IAaveLendingPool, IAToken, IPoolAddressesProvider} from "./interfaces/IAavePool.sol"; -/// @title AAVE strategy for USDs protocol -/// @author Sperax Foundation -/// @notice A yield earning strategy for USDs protocol +/// @title AAVE strategy for USDs protocol. +/// @author Sperax Foundation. +/// @notice A yield earning strategy for USDs protocol. contract AaveStrategy is InitializableAbstractStrategy { using SafeERC20 for IERC20; @@ -21,8 +21,8 @@ contract AaveStrategy is InitializableAbstractStrategy { /// @notice Initializer for setting up strategy internal state. This overrides the /// InitializableAbstractStrategy initializer as AAVE needs several extra /// addresses for the rewards program. - /// @param _platformAddress Address of the AAVE pool - /// @param _vault Address of the vault + /// @param _platformAddress Address of the AAVE pool. + /// @param _vault Address of the vault. function initialize( address _platformAddress, // AAVE PoolAddress provider 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb address _vault @@ -34,16 +34,16 @@ contract AaveStrategy is InitializableAbstractStrategy { } /// @notice Provide support for asset by passing its lpToken address. - /// This method can only be called by the system owner - /// @param _asset Address for the asset - /// @param _lpToken Address for the corresponding platform token + /// This method can only be called by the system owner. + /// @param _asset Address for the asset. + /// @param _lpToken Address for the corresponding platform token. function setPTokenAddress(address _asset, address _lpToken) external onlyOwner { _setPTokenAddress(_asset, _lpToken); } /// @notice Remove a supported asset by passing its index. - /// This method can only be called by the system owner - /// @param _assetIndex Index of the asset to be removed + /// This method can only be called by the system owner. + /// @param _assetIndex Index of the asset to be removed. function removePToken(uint256 _assetIndex) external onlyOwner { address asset = _removePTokenAddress(_assetIndex); if (allocatedAmount[asset] != 0) { @@ -152,10 +152,10 @@ contract AaveStrategy is InitializableAbstractStrategy { return assetToPToken[_asset] != address(0); } - /// @notice Withdraw asset from Aave lending Pool - /// @param _recipient Address to receive withdrawn asset - /// @param _asset Address of asset to withdraw - /// @param _amount Amount of asset to withdraw + /// @notice Withdraw asset from Aave lending Pool. + /// @param _recipient Address to receive withdrawn asset. + /// @param _asset Address of asset to withdraw. + /// @param _amount Amount of asset to withdraw. function _withdraw(address _recipient, address _asset, uint256 _amount) internal returns (uint256) { Helpers._isNonZeroAddr(_recipient); Helpers._isNonZeroAmt(_amount, "Must withdraw something"); @@ -171,8 +171,8 @@ contract AaveStrategy is InitializableAbstractStrategy { /// @dev Internal method to respond to the addition of new asset / lpTokens /// We need to give the AAVE lending pool approval to transfer the /// asset. - /// @param _asset Address of the asset to approve - /// @param _lpToken Address of the lpToken + /// @param _asset Address of the asset to approve. + /// @param _lpToken Address of the lpToken. function _abstractSetPToken(address _asset, address _lpToken) internal view override { if (IAToken(_lpToken).UNDERLYING_ASSET_ADDRESS() != _asset) { revert InvalidAssetLpPair(_asset, _lpToken); @@ -181,8 +181,8 @@ contract AaveStrategy is InitializableAbstractStrategy { /// @notice Get the lpToken wrapped in the IERC20 interface for this asset. /// Fails if the lpToken doesn't exist in our mappings. - /// @param _asset Address of the asset - /// @return Corresponding lpToken to this asset + /// @param _asset Address of the asset. + /// @return Address of corresponding lpToken to this asset. function _getPTokenFor(address _asset) internal view returns (address) { address lpToken = assetToPToken[_asset]; if (lpToken == address(0)) revert CollateralNotSupported(_asset); diff --git a/contracts/strategies/compound/CompoundStrategy.sol b/contracts/strategies/compound/CompoundStrategy.sol index d13a7f2d..c6fa6260 100644 --- a/contracts/strategies/compound/CompoundStrategy.sol +++ b/contracts/strategies/compound/CompoundStrategy.sol @@ -7,6 +7,7 @@ import {InitializableAbstractStrategy, Helpers, IStrategyVault} from "../Initial import {IComet, IReward} from "./interfaces/ICompoundHelper.sol"; /// @title Compound strategy for USDs protocol +/// @author Sperax Foundation /// @notice A yield earning strategy for USDs protocol /// @notice Important contract addresses: /// Addresses https://docs.compound.finance/#networks diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 7a4c8599..8fa0450c 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -22,12 +22,14 @@ contract StargateStrategy is InitializableAbstractStrategy { uint16 pid; // maps asset to pool id } - address public router; - address public farm; + address public router; // Address of the Stargate router contract + address public farm; // Address of the Stargate farm contract (LPStaking) mapping(address => AssetInfo) public assetInfo; + // Events event FarmUpdated(address newFarm); + // Custom errors error IncorrectPoolId(address asset, uint16 pid); error IncorrectRewardPoolId(address asset, uint256 rewardPid); @@ -226,6 +228,7 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @notice Get the amount STG pending to be collected. /// @param _asset Address for the asset + /// @return Amount of STG pending to be collected. function checkPendingRewards(address _asset) public view returns (uint256) { if (!supportsCollateral(_asset)) revert CollateralNotSupported(_asset); return ILPStaking(farm).pendingEmissionToken(assetInfo[_asset].rewardPID, address(this)); @@ -281,6 +284,7 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @notice Convert amount of lpToken to collateral. /// @param _asset Address for the asset /// @param _lpTokenAmount Amount of lpToken + /// @return Amount of collateral equivalent to the lpToken amount function _convertToCollateral(address _asset, uint256 _lpTokenAmount) internal view returns (uint256) { IStargatePool pool = IStargatePool(assetToPToken[_asset]); return ((_lpTokenAmount * pool.totalLiquidity()) / pool.totalSupply()) * pool.convertRate(); @@ -289,6 +293,7 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @notice Convert amount of collateral to lpToken. /// @param _asset Address for the asset /// @param _collateralAmount Amount of collateral + /// @return Amount of lpToken equivalent to the collateral amount function _convertToPToken(address _asset, uint256 _collateralAmount) internal view returns (uint256) { IStargatePool pool = IStargatePool(assetToPToken[_asset]); return (_collateralAmount * pool.totalSupply()) / (pool.totalLiquidity() * pool.convertRate()); @@ -299,6 +304,7 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @param _recipient Recipient of the amount /// @param _asset Address of the asset token /// @param _amount Amount to be withdrawn + /// @return Amount withdrawn /// @dev Validate if the farm has enough STG to withdraw as rewards. /// @dev It is designed to be called from functions with the `nonReentrant` modifier to ensure reentrancy protection. function _withdraw(bool _withdrawInterest, address _recipient, address _asset, uint256 _amount) @@ -329,6 +335,7 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @notice Validate if the farm has sufficient funds to claim rewards. /// @param _asset Address for the asset + /// @return bool if the farm has sufficient funds to claim rewards. /// @dev skipRwdValidation is a flag to skip the validation. function _validateRwdClaim(address _asset) private view returns (bool) { return checkPendingRewards(_asset) <= IERC20(rewardTokenAddress[0]).balanceOf(farm); diff --git a/contracts/token/USDs.sol b/contracts/token/USDs.sol index bf4c301d..d3924d4a 100644 --- a/contracts/token/USDs.sol +++ b/contracts/token/USDs.sol @@ -14,17 +14,14 @@ import {StableMath} from "../libraries/StableMath.sol"; import {Helpers} from "../libraries/Helpers.sol"; import {IUSDs} from "../interfaces/IUSDs.sol"; -/// NOTE that this is an ERC20 token but the invariant that the sum of -/// balanceOf(x) for all x is not >= totalSupply(). This is a consequence of the -/// rebasing design. Any integrations with USDs should be aware. - /// @title USDs Token Contract on Arbitrum (L2) -/// @dev ERC20 compatible contract for USDs -/// @dev support rebase feature -/// @dev inspired by OUSD: https://github.com/OriginProtocol/origin-dollar/blob/master/contracts/contracts/token/OUSD.sol /// @author Sperax Foundation +/// @dev ERC20 compatible contract for USDs supporting the rebase feature. +/// This ERC20 token represents USDs on the Arbitrum (L2) network. Note that the invariant holds that the sum of +/// balanceOf(x) for all x is not greater than totalSupply(). This is a consequence of the rebasing design. Integrations +/// with USDs should be aware of this feature. +/// Inspired by OUSD: https://github.com/OriginProtocol/origin-dollar/blob/master/contracts/contracts/token/OUSD.sol contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable, IUSDs { - // @todo validate upgrade with Openzeppelin's upgradeValidation lib using SafeMathUpgradeable for uint256; using StableMath for uint256; @@ -36,32 +33,36 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr uint256 private constant MAX_SUPPLY = ~uint128(0); // (2^128) - 1 // solhint-disable var-name-mixedcase - uint256 internal _totalSupply; // the total supply of USDs + uint256 internal _totalSupply; // Total supply of USDs uint256[4] private _deprecated_vars; // totalMinted, totalBurnt, mintedViaGateway, burntViaGateway mapping(address => mapping(address => uint256)) private _allowances; - address public vault; // the address where (i) all collaterals of USDs protocol reside, e.g. USDT, USDC, ETH, etc and (ii) major actions like USDs minting are initiated - // an user's balance of USDs is based on her balance of "credits." - // in a rebase process, her USDs balance will change according to her credit balance and the rebase ratio + address public vault; // The address where (i) all collaterals of USDs protocol reside, e.g., USDT, USDC, ETH, etc., and (ii) major actions like USDs minting are initiated. + + // An user's balance of USDs is based on her balance of "credits." + // In a rebase process, her USDs balance will change according to her credit balance and the rebase ratio. mapping(address => uint256) private _creditBalances; uint256 private _deprecated_rebasingCredits; - uint256 public rebasingCreditsPerToken; // the rebase ratio = num of credits / num of USDs - // Frozen address/credits are non rebasing (value is held in contracts which - // do not receive yield unless they explicitly opt in) - uint256 public nonRebasingSupply; // num of USDs that are not affected by rebase - // @note nonRebasingCreditsPerToken value is set as 1 for each account - mapping(address => uint256) public nonRebasingCreditsPerToken; // the rebase ratio of non-rebasing accounts just before they opt out - mapping(address => RebaseOptions) public rebaseState; // the rebase state of each account, i.e. opt in or opt out + uint256 public rebasingCreditsPerToken; // The rebase ratio = number of credits / number of USDs. + + // Frozen address/credits are non-rebasing (value is held in contracts which + // do not receive yield unless they explicitly opt in). + uint256 public nonRebasingSupply; // The number of USDs that are not affected by rebase. + // The nonRebasingCreditsPerToken value is set as 1 for each account. + mapping(address => uint256) public nonRebasingCreditsPerToken; // The rebase ratio of non-rebasing accounts just before they opt out. + mapping(address => RebaseOptions) public rebaseState; // The rebase state of each account, i.e., opt in or opt out. address[2] private _deprecated_gatewayAddr; mapping(address => bool) private _deprecated_isUpgraded; bool public paused; // solhint-enable var-name-mixedcase + // Events event TotalSupplyUpdated(uint256 totalSupply, uint256 rebasingCredits, uint256 rebasingCreditsPerToken); event Paused(bool isPaused); event VaultUpdated(address newVault); event RebaseOptIn(address indexed account); event RebaseOptOut(address indexed account); + // Custom error messages error CallerNotVault(address caller); error ContractPaused(); error IsAlreadyRebasingAccount(address account); @@ -73,16 +74,21 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr error MintToZeroAddr(); error MaxSupplyReached(uint256 totalSupply); - /// @notice Verifies that the caller is the Savings Manager contract + /// @notice Verifies that the caller is the Savings Manager contract. modifier onlyVault() { if (msg.sender != vault) revert CallerNotVault(msg.sender); _; } + // Disable initialization for the implementation contract constructor() { _disableInitializers(); } + /// @notice Initializes the contract with the provided name, symbol, and vault address. + /// @param _nameArg The name of the USDs token. + /// @param _symbolArg The symbol of the USDs token. + /// @param _vaultAddress The address where collaterals of USDs protocol reside, and major actions like USDs minting are initiated. function initialize(string memory _nameArg, string memory _symbolArg, address _vaultAddress) external initializer { Helpers._isNonZeroAddr(_vaultAddress); __ERC20_init(_nameArg, _symbolArg); @@ -95,44 +101,44 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr } /// @notice Mints new USDs tokens, increasing totalSupply. - /// @param _account the account address the newly minted USDs will be attributed to - /// @param _amount the amount of USDs that will be minted + /// @param _account The account address to which the newly minted USDs will be attributed. + /// @param _amount The amount of USDs to be minted. function mint(address _account, uint256 _amount) external override onlyVault nonReentrant { _mint(_account, _amount); } /// @notice Burns tokens, decreasing totalSupply. - /// @param _amount amount to burn + /// @param _amount The amount to burn. function burn(uint256 _amount) external override nonReentrant { _burn(msg.sender, _amount); } - /// @notice Voluntary opt-in for rebase - /// @dev Useful for smart-contract wallets + /// @notice Voluntary opt-in for rebase. + /// @dev Useful for smart-contract wallets. function rebaseOptIn() external { _rebaseOptIn(msg.sender); } - /// @notice Voluntary opt-out from rebase + /// @notice Voluntary opt-out from rebase. function rebaseOptOut() external { _rebaseOptOut(msg.sender); } - /// @notice Adds `_account` to rebasing account list - /// @param _account Address of the desired account + /// @notice Adds `_account` to the rebasing account list. + /// @param _account Address of the desired account. function rebaseOptIn(address _account) external onlyOwner { _rebaseOptIn(_account); } - /// @notice Adds `_account` to non-rebasing account list - /// @param _account Address of the desired account + /// @notice Adds `_account` to the non-rebasing account list. + /// @param _account Address of the desired account. function rebaseOptOut(address _account) external onlyOwner { _rebaseOptOut(_account); } - /// @notice The rebase function. Modify the supply without minting new tokens. This uses a change in - /// the exchange rate between "credits" and USDs tokens to change balances. - /// @param _rebaseAmt amount of USDs to rebase with. + /// @notice The rebase function. Modifies the supply without minting new tokens. + /// This uses a change in the exchange rate between "credits" and USDs tokens to change balances. + /// @param _rebaseAmt The amount of USDs to rebase with. function rebase(uint256 _rebaseAmt) external override onlyVault nonReentrant { uint256 prevTotalSupply = _totalSupply; @@ -140,19 +146,19 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr if (_totalSupply == 0) revert CannotIncreaseZeroSupply(); - // Compute the existing rebasing credits, + // Compute the existing rebasing credits. uint256 rebasingCredits = (_totalSupply - nonRebasingSupply).mulTruncate(rebasingCreditsPerToken); - // special case: if the total supply remains the same + // Special case: if the total supply remains the same. if (_totalSupply == prevTotalSupply) { emit TotalSupplyUpdated(_totalSupply, rebasingCredits, rebasingCreditsPerToken); return; } - // check if the new total supply surpasses the MAX + // Check if the new total supply surpasses the MAX. _totalSupply = prevTotalSupply > MAX_SUPPLY ? MAX_SUPPLY : prevTotalSupply; - // calculate the new rebase ratio, i.e. credits per token + // Calculate the new rebase ratio, i.e., credits per token. rebasingCreditsPerToken = rebasingCredits.divPrecisely(_totalSupply - nonRebasingSupply); if (rebasingCreditsPerToken == 0) revert InvalidRebase(); @@ -160,25 +166,25 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr emit TotalSupplyUpdated(_totalSupply, rebasingCredits, rebasingCreditsPerToken); } - /// @notice change the vault address - /// @param _newVault the new vault address + /// @notice Change the vault address. + /// @param _newVault The new vault address. function updateVault(address _newVault) external onlyOwner { Helpers._isNonZeroAddr(_newVault); vault = _newVault; emit VaultUpdated(_newVault); } - /// @notice Called by the owner to pause | unpause the contract - /// @param _pause pauseSwitch state. + /// @notice Called by the owner to pause or unpause the contract. + /// @param _pause The state of the pause switch. function pauseSwitch(bool _pause) external onlyOwner { paused = _pause; emit Paused(_pause); } /// @notice Transfer tokens to a specified address. - /// @param _to the address to transfer to. - /// @param _value the _amount to be transferred. - /// @return true on success. + /// @param _to The address to transfer to. + /// @param _value The amount to be transferred. + /// @return True on success. function transfer(address _to, uint256 _value) public override returns (bool) { if (_to == address(0)) revert TransferToZeroAddr(); uint256 bal = balanceOf(msg.sender); @@ -192,17 +198,16 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr } /// @notice Transfer tokens from one address to another. - /// @param _from The address you want to send tokens from. - /// @param _to The address you want to transfer to. - /// @param _value The _amount of tokens to be transferred. + /// @param _from The address from which you want to send tokens. + /// @param _to The address to which the tokens will be transferred. + /// @param _value The amount of tokens to be transferred. /// @return true on success. function transferFrom(address _from, address _to, uint256 _value) public override returns (bool) { if (_to == address(0)) revert TransferToZeroAddr(); uint256 bal = balanceOf(_from); if (_value > bal) revert TransferGreaterThanBal(_value, bal); - // notice: allowance balance check depends on "sub" non-negative check - + // Notice: allowance balance check depends on "sub" non-negative check _allowances[_from][msg.sender] = _allowances[_from][msg.sender].sub(_value, "Insufficient allowance"); _executeTransfer(_from, _to, _value); @@ -212,14 +217,14 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr return true; } - /// @notice Approve the passed address to spend the specified _amount of tokens on behalf of + /// @notice Approve the passed address to spend the specified amount of tokens on behalf of /// msg.sender. This method is included for ERC20 compatibility. - /// increaseAllowance and decreaseAllowance should be used instead. + /// @dev increaseAllowance and decreaseAllowance should be used instead. /// Changing an allowance with this method brings the risk that someone may transfer both /// the old and the new allowance - if they are both greater than zero - if a transfer /// transaction is mined before the later approve() call is mined. - /// @param _spender The address which will spend the funds. - /// @param _value The _amount of tokens to be spent. + /// @param _spender The address that will spend the funds. + /// @param _value The amount of tokens to be spent. /// @return true on success. function approve(address _spender, uint256 _value) public override returns (bool) { _allowances[msg.sender][_spender] = _value; @@ -227,11 +232,11 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr return true; } - /// @notice Increase the _amount of tokens that an owner has allowed to a _spender. + /// @notice Increase the amount of tokens that an owner has allowed a `_spender` to spend. /// This method should be used instead of approve() to avoid the double approval vulnerability /// described above. - /// @param _spender The address which will spend the funds. - /// @param _addedValue The _amount of tokens to increase the allowance by. + /// @param _spender The address that will spend the funds. + /// @param _addedValue The amount of tokens to increase the allowance by. /// @return true on success. function increaseAllowance(address _spender, uint256 _addedValue) public override returns (bool) { _allowances[msg.sender][_spender] = _allowances[msg.sender][_spender] + _addedValue; @@ -239,9 +244,9 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr return true; } - /// @notice Decrease the _amount of tokens that an owner has allowed to a _spender. - /// @param _spender The address which will spend the funds. - /// @param _subtractedValue The _amount of tokens to decrease the allowance by. + /// @notice Decrease the amount of tokens that an owner has allowed a `_spender` to spend. + /// @param _spender The address that will spend the funds. + /// @param _subtractedValue The amount of tokens to decrease the allowance by. /// @return true on success. function decreaseAllowance(address _spender, uint256 _subtractedValue) public override returns (bool) { uint256 oldValue = _allowances[msg.sender][_spender]; @@ -254,62 +259,55 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr return true; } - /// @notice check the current total supply of USDs + /// @notice Check the current total supply of USDs. /// @return The total supply of USDs. function totalSupply() public view override(ERC20Upgradeable, IUSDs) returns (uint256) { return _totalSupply; } /// @notice Gets the USDs balance of the specified address. - /// @param _account Address to query the balance of. - /// @return A uint256 representing the _amount of base units owned by the - /// specified address. + /// @param _account The address to query the balance of. + /// @return A uint256 representing the amount of base units owned by the specified address. function balanceOf(address _account) public view override returns (uint256) { return _balanceOf(_account); } /// @notice Gets the credits balance of the specified address. /// @param _account The address to query the balance of. - /// @return (uint256, uint256) Credit balance and credits per token of the - /// address + /// @return (uint256, uint256) Credit balance and credits per token of the address. function creditsBalanceOf(address _account) public view returns (uint256, uint256) { return (_creditBalances[_account], _creditsPerToken(_account)); } - /// @notice Function to check the _amount of tokens that an owner has allowed to a _spender. - /// @param _owner The address which owns the funds. - /// @param _spender The address which will spend the funds. - /// @return The number of tokens still available for the _spender. + /// @notice Function to check the amount of tokens that an owner has allowed a spender. + /// @param _owner The address that owns the funds. + /// @param _spender The address that will spend the funds. + /// @return The number of tokens still available for the spender. function allowance(address _owner, address _spender) public view override returns (uint256) { return _allowances[_owner][_spender]; } - /// @notice Creates `_amount` tokens and assigns them to `_account`, increasing - /// the total supply. - /// - /// Emits a {Transfer} event with `from` set to the zero address. - /// - /// Requirements - /// - /// - `to` cannot be the zero address. - /// @param _account the account address the newly minted USDs will be attributed to - /// @param _amount the amount of USDs that will be minted + /// @notice Creates `_amount` tokens and assigns them to `_account`, increasing the total supply. + /// @dev Emits a {Transfer} event with `from` set to the zero address. + /// @dev Requirements - `to` cannot be the zero address. + /// @param _account The account address to which the newly minted USDs will be attributed. + /// @param _amount The amount of USDs that will be minted. function _mint(address _account, uint256 _amount) internal override { _isNotPaused(); if (_account == address(0)) revert MintToZeroAddr(); - // notice: If the account is non rebasing and doesn't have a set creditsPerToken - // then set it i.e. this is a mint from a fresh contract + // Notice: If the account is non-rebasing and doesn't have a set creditsPerToken, + // then set it i.e. this is a mint from a fresh contract // creditAmount for non-rebasing accounts = _amount uint256 creditAmount = _amount; - // update global stats + // Update global stats if (_isNonRebasingAccount(_account)) { nonRebasingSupply = nonRebasingSupply + _amount; } else { creditAmount = _amount.mulTruncate(rebasingCreditsPerToken); } - // update credit balance for the account + // Update credit balance for the account _creditBalances[_account] = _creditBalances[_account] + creditAmount; _totalSupply = _totalSupply + _amount; @@ -319,13 +317,11 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr emit Transfer(address(0), _account, _amount); } - /// @notice Destroys `_amount` tokens from `_account`, reducing the - /// total supply. - /// - /// Emits a {Transfer} event with `to` set to the zero address. - /// - /// Requirements - /// + /// @notice Destroys `_amount` tokens from `_account`, reducing the total supply. + /// @param _account The account address from which the USDs will be burnt. + /// @param _amount The amount of USDs that will be burnt. + /// @dev Emits a {Transfer} event with `to` set to the zero address. + /// @dev Requirements: /// - `_account` cannot be the zero address. /// - `_account` must have at least `_amount` tokens. function _burn(address _account, uint256 _amount) internal override { @@ -349,9 +345,9 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr emit Transfer(_account, address(0), _amount); } - /// @notice Update the count of non rebasing credits in response to a transfer - /// @param _from The address you want to send tokens from. - /// @param _to The address you want to transfer to. + /// @notice Update the count of non-rebasing credits in response to a transfer + /// @param _from The address from which you want to send tokens. + /// @param _to The address to which the tokens will be transferred. /// @param _value Amount of USDs to transfer function _executeTransfer(address _from, address _to, uint256 _value) private { _isNotPaused(); @@ -362,12 +358,12 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr if (isNonRebasingFrom) { _creditBalances[_from] = _creditBalances[_from].sub(_value, "Transfer amount exceeds balance"); if (!isNonRebasingTo) { - // Transfer to rebasing account from non-rebasing account + // Transfer to a rebasing account from a non-rebasing account // Decreasing non-rebasing supply by the amount that was sent nonRebasingSupply = nonRebasingSupply.sub(_value); } } else { - // updating credit balance for rebasing account + // Updating credit balance for a rebasing account _creditBalances[_from] = _creditBalances[_from].sub(creditAmount, "Transfer amount exceeds balance"); } @@ -375,19 +371,20 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr _creditBalances[_to] = _creditBalances[_to] + _value; if (!isNonRebasingFrom) { - // Transfer to non-rebasing account from rebasing account, + // Transfer to a non-rebasing account from a rebasing account, // Increasing non-rebasing supply by the amount that was sent nonRebasingSupply = nonRebasingSupply + _value; } } else { - // updating credit balance for rebasing account + // Updating credit balance for a rebasing account _creditBalances[_to] = _creditBalances[_to] + creditAmount; } } - /// @notice Add a contract address to the non rebasing exception list. I.e. the + /// @notice Add a contract address to the non-rebasing exception list. I.e., the /// address's balance will be part of rebases so the account will be exposed /// to upside and downside. + /// @param _account address of the account opting in for rebase. function _rebaseOptIn(address _account) private { if (!_isNonRebasingAccount(_account)) { revert IsAlreadyRebasingAccount(_account); @@ -395,10 +392,10 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr uint256 bal = _balanceOf(_account); - // Decreasing non rebasing supply + // Decreasing non-rebasing supply nonRebasingSupply = nonRebasingSupply - bal; - // convert the balance to credits + // Convert the balance to credits _creditBalances[_account] = bal.mulTruncate(rebasingCreditsPerToken); rebaseState[_account] = RebaseOptions.OptIn; @@ -409,17 +406,17 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr emit RebaseOptIn(_account); } - /// @notice Remove a contract address to the non rebasing exception list. + /// @notice Remove a contract address from the non-rebasing exception list. function _rebaseOptOut(address _account) private { if (_isNonRebasingAccount(_account)) { revert IsAlreadyNonRebasingAccount(_account); } uint256 bal = _balanceOf(_account); - // Increase non rebasing supply + // Increase non-rebasing supply nonRebasingSupply = nonRebasingSupply + bal; - // adjusting credits + // Adjusting credits _creditBalances[_account] = bal; // Set fixed credits per token @@ -444,12 +441,12 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr } /// @notice Ensures internal account for rebasing and non-rebasing credits and - /// supply is updated following deployment of frozen yield change. + /// supply is updated following the deployment of frozen yield change. /// @param _account Address of the account. function _ensureNonRebasingMigration(address _account) private { if (nonRebasingCreditsPerToken[_account] == 0) { if (_creditBalances[_account] != 0) { - // Update non rebasing supply + // Update non-rebasing supply uint256 bal = _balanceOf(_account); nonRebasingSupply = nonRebasingSupply + bal; _creditBalances[_account] = bal; @@ -458,7 +455,7 @@ contract USDs is ERC20PermitUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgr } } - /// @notice Calculates balance of account + /// @notice Calculates the balance of the account. /// @dev Function assumes the _account is already upgraded. /// @param _account Address of the account. function _balanceOf(address _account) private view returns (uint256) { diff --git a/contracts/vault/CollateralManager.sol b/contracts/vault/CollateralManager.sol index 2ae55078..c2cc4ae9 100644 --- a/contracts/vault/CollateralManager.sol +++ b/contracts/vault/CollateralManager.sol @@ -11,37 +11,40 @@ interface IERC20Custom is IERC20 { function decimals() external view returns (uint8); } -/// @title Collateral Manager contract for USDs protocol +/// @title Collateral Manager for the USDs Protocol /// @author Sperax Foundation -/// @notice Manages addition and removal of collateral, configures -/// collateral strategies and percentage of allocation +/// @notice This contract manages the addition and removal of collateral, configuration of collateral strategies, and allocation percentages. +/// @dev Collateral Manager interacts with the Vault and various strategies for collateral management. contract CollateralManager is ICollateralManager, Ownable { + // Struct for storing collateral data struct CollateralData { - bool mintAllowed; - bool redeemAllowed; - bool allocationAllowed; + bool mintAllowed; // mint switch for collateral + bool redeemAllowed; // redemption switch for collateral + bool allocationAllowed; // allocation switch for collateral bool exists; - address defaultStrategy; + address defaultStrategy; // default redemption strategy for collateral uint16 baseMintFee; uint16 baseRedeemFee; - uint16 downsidePeg; - uint16 desiredCollateralComposition; - uint16 collateralCapacityUsed; - uint256 conversionFactor; + uint16 downsidePeg; // min price of collateral to be eligible for minting + uint16 desiredCollateralComposition; // collateral composition in vault + uint16 collateralCapacityUsed; // tracks current allocation capacity of a collateral + uint256 conversionFactor; // normalization factor for bringing token amounts to same decimal levels } + // Struct for storing strategy data struct StrategyData { uint16 allocationCap; bool exists; } - uint16 public collateralCompositionUsed; - address public immutable VAULT; - address[] private collaterals; - mapping(address => CollateralData) public collateralInfo; - mapping(address => mapping(address => StrategyData)) private collateralStrategyInfo; - mapping(address => address[]) private collateralStrategies; + uint16 public collateralCompositionUsed; // vault composition allocated to collaterals + address public immutable VAULT; // address of USDs-vault + address[] private collaterals; // address of all registered collaterals + mapping(address => CollateralData) public collateralInfo; // collateral configuration + mapping(address => mapping(address => StrategyData)) private collateralStrategyInfo; // collateral -> strategy => collateralStrategy config + mapping(address => address[]) private collateralStrategies; // collateral => strategies[] + // Events event CollateralAdded(address collateral, CollateralBaseData data); event CollateralRemoved(address collateral); event CollateralInfoUpdated(address collateral, CollateralBaseData data); @@ -49,6 +52,7 @@ contract CollateralManager is ICollateralManager, Ownable { event CollateralStrategyUpdated(address collateral, address strategy); event CollateralStrategyRemoved(address collateral, address strategy); + // Custom error messages error CollateralExists(); error CollateralDoesNotExist(); error CollateralStrategyExists(); @@ -60,6 +64,8 @@ contract CollateralManager is ICollateralManager, Ownable { error AllocationPercentageLowerThanAllocatedAmt(); error IsDefaultStrategy(); + /// @dev Constructor to initialize the Collateral Manager + /// @param _vault Address of the Vault contract constructor(address _vault) { Helpers._isNonZeroAddr(_vault); VAULT = _vault; @@ -69,14 +75,16 @@ contract CollateralManager is ICollateralManager, Ownable { /// @param _collateral Address of the collateral /// @param _data Collateral configuration data function addCollateral(address _collateral, CollateralBaseData memory _data) external onlyOwner { - // Test if collateral is already added + // Check if collateral is already added // Initialize collateral storage data if (collateralInfo[_collateral].exists) revert CollateralExists(); + // Check that configuration values do not exceed maximum percentage Helpers._isLTEMaxPercentage(_data.downsidePeg); Helpers._isLTEMaxPercentage(_data.baseMintFee); Helpers._isLTEMaxPercentage(_data.baseRedeemFee); + // Check the desired collateral composition does not exceed the maximum Helpers._isLTEMaxPercentage( _data.desiredCollateralComposition + collateralCompositionUsed, "Collateral composition exceeded" ); @@ -105,23 +113,26 @@ contract CollateralManager is ICollateralManager, Ownable { /// @param _collateral Address of the collateral /// @param _updateData Updated configuration for the collateral function updateCollateralData(address _collateral, CollateralBaseData memory _updateData) external onlyOwner { - // Check if collateral added; + // Check if collateral is added // Update the collateral storage data if (!collateralInfo[_collateral].exists) { revert CollateralDoesNotExist(); } + // Check that updated configuration values do not exceed maximum percentage Helpers._isLTEMaxPercentage(_updateData.downsidePeg); Helpers._isLTEMaxPercentage(_updateData.baseMintFee); Helpers._isLTEMaxPercentage(_updateData.baseRedeemFee); CollateralData storage data = collateralInfo[_collateral]; + // Calculate the new capacity used to ensure it does not exceed the maximum collateral composition uint16 newCapacityUsed = (collateralCompositionUsed - data.desiredCollateralComposition + _updateData.desiredCollateralComposition); Helpers._isLTEMaxPercentage(newCapacityUsed, "Collateral composition exceeded"); + // Update the collateral data data.mintAllowed = _updateData.mintAllowed; data.redeemAllowed = _updateData.redeemAllowed; data.allocationAllowed = _updateData.allocationAllowed; @@ -130,6 +141,7 @@ contract CollateralManager is ICollateralManager, Ownable { data.downsidePeg = _updateData.downsidePeg; data.desiredCollateralComposition = _updateData.desiredCollateralComposition; + // Update the collateral composition used collateralCompositionUsed = newCapacityUsed; emit CollateralInfoUpdated(_collateral, _updateData); @@ -138,9 +150,11 @@ contract CollateralManager is ICollateralManager, Ownable { /// @notice Un-list a collateral /// @param _collateral Address of the collateral function removeCollateral(address _collateral) external onlyOwner { + // Check if the collateral exists if (!collateralInfo[_collateral].exists) { revert CollateralDoesNotExist(); } + // Check if collateral strategies are empty if (collateralStrategies[_collateral].length != 0) { revert CollateralStrategyExists(); } @@ -149,9 +163,12 @@ contract CollateralManager is ICollateralManager, Ownable { for (uint256 i; i < numCollateral;) { if (collaterals[i] == _collateral) { + // Remove the collateral from the list collaterals[i] = collaterals[numCollateral - 1]; collaterals.pop(); + // Update the collateral composition used collateralCompositionUsed -= collateralInfo[_collateral].desiredCollateralComposition; + // Delete the collateral data delete (collateralInfo[_collateral]); break; } @@ -173,21 +190,21 @@ contract CollateralManager is ICollateralManager, Ownable { // Check if the collateral is valid if (!collateralData.exists) revert CollateralDoesNotExist(); - // Check if collateral strategy not already added. + // Check if the collateral strategy is not already added. if (collateralStrategyInfo[_collateral][_strategy].exists) { revert CollateralStrategyMapped(); } - // Check if collateral is allocation is supported by the strategy. + // Check if collateral allocation is supported by the strategy. if (!IStrategy(_strategy).supportsCollateral(_collateral)) { revert CollateralNotSupportedByStrategy(); } - // Check if _allocation Per <= 100 - collateralCapacityUsed + // Check if the allocation percentage is within bounds Helpers._isLTEMaxPercentage( _allocationCap + collateralData.collateralCapacityUsed, "Allocation percentage exceeded" ); - // add info to collateral mapping + // Add information to collateral mapping collateralStrategyInfo[_collateral][_strategy] = StrategyData(_allocationCap, true); collateralStrategies[_collateral].push(_strategy); collateralData.collateralCapacityUsed += _allocationCap; @@ -203,9 +220,9 @@ contract CollateralManager is ICollateralManager, Ownable { external onlyOwner { - // Check if collateral and strategy are mapped - // Check if _allocationCap <= 100 - collateralCapacityUsed + oldAllocationPer - // Update the info + // Check if the collateral and strategy are mapped + // Check if the new allocation percentage is within bounds + // _allocationCap <= 100 - collateralCapacityUsed + oldAllocationPer if (!collateralStrategyInfo[_collateral][_strategy].exists) { revert CollateralStrategyNotMapped(); } @@ -213,16 +230,21 @@ contract CollateralManager is ICollateralManager, Ownable { CollateralData storage collateralData = collateralInfo[_collateral]; StrategyData storage strategyData = collateralStrategyInfo[_collateral][_strategy]; + // Calculate the new capacity used to ensure it's within bounds uint16 newCapacityUsed = collateralData.collateralCapacityUsed - strategyData.allocationCap + _allocationCap; Helpers._isLTEMaxPercentage(newCapacityUsed, "Allocation percentage exceeded"); + // Calculate the current allocated percentage uint256 totalCollateral = getCollateralInVault(_collateral) + getCollateralInStrategies(_collateral); uint256 currentAllocatedPer = (getCollateralInAStrategy(_collateral, _strategy) * Helpers.MAX_PERCENTAGE) / totalCollateral; + // Ensure the new allocation percentage is greater than or equal to the currently allocated percentage if (_allocationCap < currentAllocatedPer) { revert AllocationPercentageLowerThanAllocatedAmt(); } + + // Update the collateral data and strategy data collateralData.collateralCapacityUsed = newCapacityUsed; strategyData.allocationCap = _allocationCap; @@ -235,10 +257,9 @@ contract CollateralManager is ICollateralManager, Ownable { /// @dev Ensure all the collateral is removed from the strategy before calling this /// Otherwise it will create error in collateral accounting function removeCollateralStrategy(address _collateral, address _strategy) external onlyOwner { - // Check if the collateral and strategy are mapped. - // ensure none of the collateral is deposited to strategy - // remove collateralCapacity. - // remove item from list. + // Check if the collateral and strategy are mapped + // Ensure none of the collateral is deposited into the strategy + // Remove collateral capacity and the strategy from the list if (!collateralStrategyInfo[_collateral][_strategy].exists) { revert CollateralStrategyNotMapped(); } @@ -251,7 +272,7 @@ contract CollateralManager is ICollateralManager, Ownable { } uint256 numStrategy = collateralStrategies[_collateral].length; - + // Unlink the strategy from the collateral and update collateral capacity used for (uint256 i; i < numStrategy;) { if (collateralStrategies[_collateral][i] == _strategy) { collateralStrategies[_collateral][i] = collateralStrategies[_collateral][numStrategy - 1]; @@ -294,8 +315,10 @@ contract CollateralManager is ICollateralManager, Ownable { strategyData.allocationCap * (getCollateralInVault(_collateral) + getCollateralInStrategies(_collateral)) ) / Helpers.MAX_PERCENTAGE; + // Get the collateral balance in the specified strategy uint256 collateralBalance = IStrategy(_strategy).checkBalance(_collateral); + // Check if the allocation request is within the allowed limits if (maxCollateralUsage >= collateralBalance) { return ((maxCollateralUsage - collateralBalance) >= _amount); } @@ -359,23 +382,23 @@ contract CollateralManager is ICollateralManager, Ownable { }); } - /// @notice Gets list of all the listed collateral - /// @return address[] of listed collaterals + /// @notice Gets a list of all listed collaterals + /// @return List of addresses representing all listed collaterals function getAllCollaterals() external view returns (address[] memory) { return collaterals; } - /// @notice Gets list of all the collateral linked strategies + /// @notice Gets a list of all strategies linked to a collateral /// @param _collateral Address of the collateral - /// @return address[] list of available strategies for a collateral + /// @return List of addresses representing available strategies for the collateral function getCollateralStrategies(address _collateral) external view returns (address[] memory) { return collateralStrategies[_collateral]; } - /// @notice Verify if a strategy is linked to a collateral + /// @notice Verifies if a strategy is linked to a collateral /// @param _collateral Address of the collateral /// @param _strategy Address of the strategy - /// @return boolean true if the strategy is linked to the collateral + /// @return True if the strategy is linked to the collateral, otherwise False function isValidStrategy(address _collateral, address _strategy) external view returns (bool) { return collateralStrategyInfo[_collateral][_strategy].exists; } diff --git a/contracts/vault/FeeCalculator.sol b/contracts/vault/FeeCalculator.sol index a0a58416..0f5e1002 100644 --- a/contracts/vault/FeeCalculator.sol +++ b/contracts/vault/FeeCalculator.sol @@ -6,6 +6,9 @@ import {ICollateralManager} from "./interfaces/ICollateralManager.sol"; import {Helpers} from "../libraries/Helpers.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +/// @title Fee Calculator Contract for the USDs Protocol +/// @author Sperax Foundation +/// @dev A contract that calculates fees for minting and redeeming USDs. contract FeeCalculator is IFeeCalculator { struct FeeData { uint32 nextUpdate; @@ -23,8 +26,10 @@ contract FeeCalculator is IFeeCalculator { mapping(address => FeeData) public collateralFee; + // Events event FeeCalibrated(address indexed collateral, uint16 mintFee, uint16 redeemFee); + // Custom error messages error InvalidCalibration(); error FeeNotCalibrated(address collateral); diff --git a/contracts/vault/VaultCore.sol b/contracts/vault/VaultCore.sol index afb0a3b5..2e92bc0e 100644 --- a/contracts/vault/VaultCore.sol +++ b/contracts/vault/VaultCore.sol @@ -15,20 +15,21 @@ import {ICollateralManager} from "./interfaces/ICollateralManager.sol"; import {IStrategy} from "./interfaces/IStrategy.sol"; import {Helpers} from "../libraries/Helpers.sol"; -/// @title Savings manager (Vault) contract for USDs protocol +/// @title Savings Manager (Vault) Contract for USDs Protocol /// @author Sperax Foundation -/// @notice Lets users mint/redeem USDs for/with allowed collaterals -/// @notice Allocates collateral in strategies by consulting Collateral Manager contract +/// @notice This contract enables users to mint and redeem USDs with allowed collaterals. +/// @notice It also allocates collateral to strategies based on the Collateral Manager contract. contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20Upgradeable for IERC20Upgradeable; - address public feeVault; // SPABuyback contract - address public yieldReceiver; - address public collateralManager; - address public feeCalculator; - address public oracle; - address public rebaseManager; + address public feeVault; // Address of the SPABuyback contract + address public yieldReceiver; // Address of the Yield Receiver contract + address public collateralManager; // Address of the Collateral Manager contract + address public feeCalculator; // Address of the Fee Calculator contract + address public oracle; // Address of the Oracle contract + address public rebaseManager; // Address of the Rebase Manager contract + // Events event FeeVaultUpdated(address newFeeVault); event YieldReceiverUpdated(address newYieldReceiver); event CollateralManagerUpdated(address newCollateralManager); @@ -44,6 +45,7 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea event RebasedUSDs(uint256 rebaseAmt); event Allocated(address indexed collateral, address indexed strategy, uint256 amount); + // Custom Error messages error AllocationNotAllowed(address collateral, address strategy, uint256 amount); error RedemptionPausedForCollateral(address collateral); error InsufficientCollateral(address collateral, address strategy, uint256 amount, uint256 availableAmount); @@ -59,60 +61,60 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea __ReentrancyGuard_init(); } - /// @notice Updates the address receiving fee - /// @param _feeVault updated address of the fee vault + /// @notice Updates the address receiving fee. + /// @param _feeVault New desired SPABuyback address. function updateFeeVault(address _feeVault) external onlyOwner { Helpers._isNonZeroAddr(_feeVault); feeVault = _feeVault; emit FeeVaultUpdated(_feeVault); } - /// @notice Updates the address receiving yields from strategies - /// @param _yieldReceiver new desired address + /// @notice Updates the address receiving yields from strategies. + /// @param _yieldReceiver New desired yield receiver address. function updateYieldReceiver(address _yieldReceiver) external onlyOwner { Helpers._isNonZeroAddr(_yieldReceiver); yieldReceiver = _yieldReceiver; emit YieldReceiverUpdated(_yieldReceiver); } - /// @notice Updates the address having the config for collaterals - /// @param _collateralManager new desired address + /// @notice Updates the address having the configuration for collaterals. + /// @param _collateralManager New desired collateral manager address. function updateCollateralManager(address _collateralManager) external onlyOwner { Helpers._isNonZeroAddr(_collateralManager); collateralManager = _collateralManager; emit CollateralManagerUpdated(_collateralManager); } - /// @notice Updates the address having the config for rebase - /// @param _rebaseManager new desired address + /// @notice Updates the address having the configuration for rebases. + /// @param _rebaseManager New desired rebase manager address. function updateRebaseManager(address _rebaseManager) external onlyOwner { Helpers._isNonZeroAddr(_rebaseManager); rebaseManager = _rebaseManager; emit RebaseManagerUpdated(_rebaseManager); } - /// @notice Updates the fee calculator library - /// @param _feeCalculator new desired address + /// @notice Updates the fee calculator library. + /// @param _feeCalculator New desired fee calculator address. function updateFeeCalculator(address _feeCalculator) external onlyOwner { Helpers._isNonZeroAddr(_feeCalculator); feeCalculator = _feeCalculator; emit FeeCalculatorUpdated(_feeCalculator); } - /// @notice Updates the price oracle address - /// @param _oracle new desired address + /// @notice Updates the price oracle address. + /// @param _oracle New desired oracle address. function updateOracle(address _oracle) external onlyOwner { Helpers._isNonZeroAddr(_oracle); oracle = _oracle; emit OracleUpdated(_oracle); } - /// @notice Allocate `_amount` of`_collateral` to `_strategy` - /// @param _collateral address of the desired collateral - /// @param _strategy address of the desired strategy - /// @param _amount amount of collateral to be allocated + /// @notice Allocates `_amount` of `_collateral` to `_strategy`. + /// @param _collateral Address of the desired collateral. + /// @param _strategy Address of the desired strategy. + /// @param _amount Amount of collateral to be allocated. function allocate(address _collateral, address _strategy, uint256 _amount) external nonReentrant { - // Validate the allocation is based on the desired configuration + // Validate the allocation based on the desired configuration if (!ICollateralManager(collateralManager).validateAllocation(_collateral, _strategy, _amount)) { revert AllocationNotAllowed(_collateral, _strategy, _amount); } @@ -121,11 +123,11 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea emit Allocated(_collateral, _strategy, _amount); } - /// @notice mint USDs by depositing collateral - /// @param _collateral address of the collateral - /// @param _collateralAmt amount of collateral to mint USDs with - /// @param _minUSDSAmt minimum expected amount of USDs to be minted - /// @param _deadline the expiry time of the transaction + /// @notice Mint USDs by depositing collateral. + /// @param _collateral Address of the collateral. + /// @param _collateralAmt Amount of collateral to mint USDs with. + /// @param _minUSDSAmt Minimum expected amount of USDs to be minted. + /// @param _deadline Expiry time of the transaction. function mint(address _collateral, uint256 _collateralAmt, uint256 _minUSDSAmt, uint256 _deadline) external nonReentrant @@ -133,27 +135,27 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea _mint(_collateral, _collateralAmt, _minUSDSAmt, _deadline); } - /// @notice mint USDs by depositing collateral - /// @param _collateral address of the collateral - /// @param _collateralAmt amount of collateral to mint USDs with - /// @param _minUSDSAmt minimum expected amount of USDs to be minted - /// @param _deadline the expiry time of the transaction - /// @dev This function is for backward compatibility + /// @notice Mint USDs by depositing collateral (backward compatibility). + /// @param _collateral Address of the collateral. + /// @param _collateralAmt Amount of collateral to mint USDs with. + /// @param _minUSDSAmt Minimum expected amount of USDs to be minted. + /// @param _deadline Expiry time of the transaction. + /// @dev This function is for backward compatibility. function mintBySpecifyingCollateralAmt( address _collateral, uint256 _collateralAmt, uint256 _minUSDSAmt, - uint256, // deprecated + uint256, // Deprecated uint256 _deadline ) external nonReentrant { _mint(_collateral, _collateralAmt, _minUSDSAmt, _deadline); } - /// @notice redeem USDs for `_collateral` - /// @param _collateral address of the collateral - /// @param _usdsAmt Amount of USDs to be redeemed - /// @param _minCollAmt minimum expected amount of collateral to be received - /// @param _deadline expiry time of the transaction + /// @notice Redeem USDs for `_collateral`. + /// @param _collateral Address of the collateral. + /// @param _usdsAmt Amount of USDs to be redeemed. + /// @param _minCollAmt Minimum expected amount of collateral to be received. + /// @param _deadline Expiry time of the transaction. /// @dev In case where there is not sufficient collateral available in the vault, /// the collateral is withdrawn from the default strategy configured for the collateral. function redeem(address _collateral, uint256 _usdsAmt, uint256 _minCollAmt, uint256 _deadline) @@ -169,12 +171,12 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea }); } - /// @notice redeem USDs for `_collateral` - /// @param _collateral address of the collateral - /// @param _usdsAmt Amount of USDs to be redeemed - /// @param _minCollAmt minimum expected amount of collateral to be received - /// @param _deadline expiry time of the transaction - /// @param _strategy address of the strategy to withdraw excess collateral from + /// @notice Redeem USDs for `_collateral` from a specific strategy. + /// @param _collateral Address of the collateral. + /// @param _usdsAmt Amount of USDs to be redeemed. + /// @param _minCollAmt Minimum expected amount of collateral to be received. + /// @param _deadline Expiry time of the transaction. + /// @param _strategy Address of the strategy to withdraw excess collateral from. function redeem(address _collateral, uint256 _usdsAmt, uint256 _minCollAmt, uint256 _deadline, address _strategy) external nonReentrant @@ -188,15 +190,15 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea }); } - /// @notice Get the expected redeem result - /// @param _collateral desired collateral address - /// @param _usdsAmt amount of usds to be redeemed - /// @return calculatedCollateralAmt expected amount of collateral to be released - /// based on the price calculation - /// @return usdsBurnAmt expected amount of USDs to be burnt in the process - /// @return feeAmt amount of USDs collected as fee for redemption - /// @return vaultAmt amount of Collateral released from Vault - /// @return strategyAmt amount of Collateral to withdraw from strategy + /// @notice Get the expected redeem result. + /// @param _collateral Desired collateral address. + /// @param _usdsAmt Amount of USDs to be redeemed. + /// @return calculatedCollateralAmt Expected amount of collateral to be released + /// based on the price calculation. + /// @return usdsBurnAmt Expected amount of USDs to be burnt in the process. + /// @return feeAmt Amount of USDs collected as fee for redemption. + /// @return vaultAmt Amount of collateral released from Vault. + /// @return strategyAmt Amount of collateral to withdraw from the strategy. function redeemView(address _collateral, uint256 _usdsAmt) external view @@ -212,16 +214,16 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea _redeemView(_collateral, _usdsAmt, address(0)); } - /// @notice Get the expected redeem result - /// @param _collateral desired collateral address - /// @param _usdsAmt amount of usds to be redeemed - /// @param _strategyAddr Address of strategy to redeem from - /// @return calculatedCollateralAmt expected amount of collateral to be released - /// based on the price calculation - /// @return usdsBurnAmt expected amount of USDs to be burnt in the process - /// @return feeAmt amount of USDs collected as fee for redemption - /// @return vaultAmt amount of Collateral released from Vault - /// @return strategyAmt amount of Collateral to withdraw from strategy + /// @notice Get the expected redeem result from a specific strategy. + /// @param _collateral Desired collateral address. + /// @param _usdsAmt Amount of USDs to be redeemed. + /// @param _strategyAddr Address of strategy to redeem from. + /// @return calculatedCollateralAmt Expected amount of collateral to be released + /// based on the price calculation. + /// @return usdsBurnAmt Expected amount of USDs to be burnt in the process. + /// @return feeAmt Amount of USDs collected as fee for redemption. + /// @return vaultAmt Amount of collateral released from Vault. + /// @return strategyAmt Amount of collateral to withdraw from the strategy. function redeemView(address _collateral, uint256 _usdsAmt, address _strategyAddr) external view @@ -237,8 +239,8 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea _redeemView(_collateral, _usdsAmt, _strategyAddr); } - /// @notice Rebase USDs to share earned yield with the USDs holders - /// @dev If Rebase manager returns a non zero value, it calls rebase function on token/ USDs contract + /// @notice Rebase USDs to share earned yield with the USDs holders. + /// @dev If Rebase manager returns a non-zero value, it calls the rebase function on the USDs contract. function rebase() public { uint256 rebaseAmt = IRebaseManager(rebaseManager).fetchRebaseAmt(); if (rebaseAmt != 0) { @@ -247,10 +249,10 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea } } - /// @notice Get the expected mint result (USDs amt, fee) - /// @param _collateral address of the collateral - /// @param _collateralAmt amount of collateral - /// @return Returns the expected USDs mint amount and fee for minting + /// @notice Get the expected mint result (USDs amount, fee). + /// @param _collateral Address of collateral. + /// @param _collateralAmt Amount of collateral. + /// @return Returns the expected USDs mint amount and fee for minting. function mintView(address _collateral, uint256 _collateralAmt) public view returns (uint256, uint256) { // Get mint configuration ICollateralManager.CollateralMintData memory collateralMintConfig = @@ -290,11 +292,16 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea return (toMinterAmt, feeAmt); } - /// @notice mint USDs - /// @param _collateral address of collateral - /// @param _collateralAmt amount of collateral to deposit - /// @param _minUSDSAmt min expected USDs amount to be minted - /// @param _deadline Deadline timestamp for executing mint + /// @notice Mint USDs by depositing collateral. + /// @param _collateral Address of the collateral. + /// @param _collateralAmt Amount of collateral to deposit. + /// @param _minUSDSAmt Minimum expected amount of USDs to be minted. + /// @param _deadline Deadline timestamp for executing mint. + /// @dev Mints USDs by locking collateral based on user input, ensuring a minimum + /// expected minted amount is met. + /// @dev If the minimum expected amount is not met, the transaction will revert. + /// @dev Fee is collected, and collateral is transferred accordingly. + /// @dev A rebase operation is triggered after minting. function _mint(address _collateral, uint256 _collateralAmt, uint256 _minUSDSAmt, uint256 _deadline) private { Helpers._checkDeadline(_deadline); (uint256 toMinterAmt, uint256 feeAmt) = mintView(_collateral, _collateralAmt); @@ -320,13 +327,16 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea }); } - /// @notice Redeem USDs - /// @param _collateral address of collateral to receive - /// @param _usdsAmt amount of USDs to redeem - /// @param _minCollateralAmt min expected Collateral amount to be received - /// @param _deadline Deadline timestamp for executing mint - /// @param _strategyAddr Address of the strategy to withdraw from - /// @dev withdraw from strategy is triggered only if vault doesn't have enough funds + /// @notice Redeem USDs for collateral. + /// @param _collateral Address of the collateral to receive. + /// @param _usdsAmt Amount of USDs to redeem. + /// @param _minCollateralAmt Minimum expected collateral amount to be received. + /// @param _deadline Deadline timestamp for executing the redemption. + /// @param _strategyAddr Address of the strategy to withdraw from. + /// @dev Redeems USDs for collateral, ensuring a minimum expected collateral amount + /// is met. + /// @dev If the minimum expected collateral amount is not met, the transaction will revert. + /// @dev Fee is collected, collateral is transferred, and a rebase operation is triggered. function _redeem( address _collateral, uint256 _usdsAmt, @@ -374,17 +384,25 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea }); } - /// @notice Get the expected redeem result - /// @param _collateral desired collateral address - /// @param _usdsAmt amount of usds to be redeemed - /// @param _strategyAddr address of the strategy to redeem from - /// @return calculatedCollateralAmt expected amount of collateral to be released - /// based on the price calculation - /// @return usdsBurnAmt expected amount of USDs to be burnt in the process - /// @return feeAmt amount of USDs collected as fee for redemption - /// @return vaultAmt amount of Collateral to be released from Vault - /// @return strategyAmt amount of Collateral to be withdrawn from strategy - /// @return strategy Strategy to withdraw collateral from + /// @notice Get the expected redeem result. + /// @param _collateral Desired collateral address. + /// @param _usdsAmt Amount of USDs to be redeemed. + /// @param _strategyAddr Address of the strategy to redeem from. + /// @return calculatedCollateralAmt Expected amount of collateral to be released + /// based on the price calculation. + /// @return usdsBurnAmt Expected amount of USDs to be burnt in the process. + /// @return feeAmt Amount of USDs collected as a fee for redemption. + /// @return vaultAmt Amount of collateral released from Vault. + /// @return strategyAmt Amount of collateral to withdraw from the strategy. + /// @return strategy Strategy contract to withdraw collateral from. + /// @dev Calculates the expected results of a redemption, including collateral + /// amount, fees, and strategy-specific details. + /// @dev Ensures that the redemption is allowed for the specified collateral. + /// @dev Calculates fees, burn amounts, and collateral amounts based on prices + /// and conversion factors. + /// @dev Determines if collateral needs to be withdrawn from a strategy, and if + /// so, checks the availability of collateral in the strategy. + function _redeemView(address _collateral, uint256 _usdsAmt, address _strategyAddr) private view diff --git a/package-lock.json b/package-lock.json index 1399ab8f..b0674c3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1515,46 +1515,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/globalthis": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", @@ -2575,6 +2535,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/solhint/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/solhint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2609,6 +2578,25 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/solhint/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/solhint/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2618,6 +2606,18 @@ "node": ">=8" } }, + "node_modules/solhint/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/solhint/node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", From 1f0341fab29610043c986b02ecf623faecf47028 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Mon, 8 Jan 2024 17:35:31 +0530 Subject: [PATCH 39/64] perf(contracts/oracle): Add missing nonZero addr checks --- contracts/oracle/ChainlinkOracle.sol | 4 ++++ contracts/oracle/MasterPriceOracle.sol | 2 ++ 2 files changed, 6 insertions(+) diff --git a/contracts/oracle/ChainlinkOracle.sol b/contracts/oracle/ChainlinkOracle.sol index c135b3de..f85a0ccc 100644 --- a/contracts/oracle/ChainlinkOracle.sol +++ b/contracts/oracle/ChainlinkOracle.sol @@ -39,7 +39,9 @@ contract ChainlinkOracle is Ownable { error GracePeriodNotPassed(uint256 timeSinceUp); error RoundNotComplete(); error StalePrice(); + error InvalidAddress(); error InvalidPrice(); + error InvalidPriceFeed(); error InvalidPricePrecision(); /// @notice Constructor to set up initial token data during contract deployment @@ -58,6 +60,8 @@ contract ChainlinkOracle is Ownable { /// @param _tokenData Token price feed configuration /// @dev Only the contract owner can call this function function setTokenData(address _token, TokenData memory _tokenData) public onlyOwner { + if (_token == address(0)) revert InvalidAddress(); + if (_tokenData.priceFeed == address(0)) revert InvalidPriceFeed(); if (_tokenData.pricePrecision != 10 ** AggregatorV3Interface(_tokenData.priceFeed).decimals()) { revert InvalidPricePrecision(); } diff --git a/contracts/oracle/MasterPriceOracle.sol b/contracts/oracle/MasterPriceOracle.sol index 23e98c3c..e57ea782 100644 --- a/contracts/oracle/MasterPriceOracle.sol +++ b/contracts/oracle/MasterPriceOracle.sol @@ -16,6 +16,7 @@ contract MasterPriceOracle is Ownable, IOracle { event PriceFeedRemoved(address indexed token); // Custom error messages + error InvalidAddress(); error UnableToFetchPriceFeed(address token); error InvalidPriceFeed(address token); error PriceFeedNotFound(address token); @@ -26,6 +27,7 @@ contract MasterPriceOracle is Ownable, IOracle { /// @param _data call data for fetching the price feed. /// @dev Have to be extra cautious while updating the price feed. function updateTokenPriceFeed(address _token, address _source, bytes memory _data) external onlyOwner { + if (_token == address(0)) revert InvalidAddress(); // Validate if the price feed is being emitted correctly. _getPriceFeed(_token, _source, _data); From d62b3df00a5f26afd0573926287e52a3e2e5eacd Mon Sep 17 00:00:00 2001 From: YashP16 Date: Mon, 8 Jan 2024 17:36:15 +0530 Subject: [PATCH 40/64] refactor(contracts/oracle): Update `diaMaxThreshold` init value --- contracts/oracle/SPAOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/oracle/SPAOracle.sol b/contracts/oracle/SPAOracle.sol index c27245f6..ca004e58 100644 --- a/contracts/oracle/SPAOracle.sol +++ b/contracts/oracle/SPAOracle.sol @@ -40,7 +40,7 @@ contract SPAOracle is BaseUniOracle { _isNonZeroAddr(_masterOracle); masterOracle = _masterOracle; setUniMAPriceData(SPA, _quoteToken, _feeTier, _maPeriod); - updateDIAParams(_weightDIA, 600); + updateDIAParams(_weightDIA, 86400); } /// @notice Get SPA price. From 0dbe1f16957ac741c17bc07f479d9538b17a14dc Mon Sep 17 00:00:00 2001 From: YashP16 Date: Mon, 8 Jan 2024 17:40:06 +0530 Subject: [PATCH 41/64] refactor(scripts): Add additional steps in deploy_oracles.py --- scripts/deploy_oracles.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/scripts/deploy_oracles.py b/scripts/deploy_oracles.py index 91b80aca..9b2e2e05 100644 --- a/scripts/deploy_oracles.py +++ b/scripts/deploy_oracles.py @@ -4,35 +4,43 @@ USDS_OWNER_ADDR, SPA, USDS, + USDC, USDC_E, USDT, FRAX, DAI, LUSD, + ARB ) from .utils import get_user +PUBLISH_SRC = False def main(): user = get_user("Import user: ") chainlink_feeds = [ - [USDC_E, ["0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3", 1e8]], # USDC.e - [DAI, ["0xc5C8E77B397E531B8EC06BFb0048328B30E9eCfB", 1e8]], # DAI - [FRAX, ["0x0809E3d38d1B4214958faf06D8b1B1a2b73f2ab8", 1e8]], # FRAX - [USDT, ["0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7", 1e8]], # USDT - [LUSD, ["0x0411D28c94d85A36bC72Cb0f875dfA8371D8fFfF", 1e8]], # LUSD + [USDC_E, ["0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3", 86400, 1e8]], # USDC.e + [USDC, ["0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3", 86400, 1e8]], # USDC + [DAI, ["0xc5C8E77B397E531B8EC06BFb0048328B30E9eCfB", 86400, 1e8]], # DAI + [FRAX, ["0x0809E3d38d1B4214958faf06D8b1B1a2b73f2ab8", 86400, 1e8]], # FRAX + [USDT, ["0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7", 86400, 1e8]], # USDT + [LUSD, ["0x0411D28c94d85A36bC72Cb0f875dfA8371D8fFfF", 86400, 1e8]], # LUSD + [ARB, ["0xb2A824043730FE05F3DA2efaFa1CBbe83fa548D6", 86400, 1e8]], # ARB ] chainlink_oracle = ChainlinkOracle.deploy( - chainlink_feeds, {"from": user}, publish_source=True + chainlink_feeds, {"from": user}, publish_source=PUBLISH_SRC ) - master_price_oracle = MasterPriceOracle.deploy({"from": user}, publish_source=True) + master_price_oracle = MasterPriceOracle.deploy({"from": user}, publish_source=PUBLISH_SRC) master_price_oracle.updateTokenPriceFeed( USDC_E, chainlink_oracle, chainlink_oracle.getTokenPrice.encode_input(USDC_E) ) + master_price_oracle.updateTokenPriceFeed( + USDC, chainlink_oracle, chainlink_oracle.getTokenPrice.encode_input(USDC) + ) master_price_oracle.updateTokenPriceFeed( DAI, chainlink_oracle, chainlink_oracle.getTokenPrice.encode_input(DAI) ) @@ -46,12 +54,16 @@ def main(): LUSD, chainlink_oracle, chainlink_oracle.getTokenPrice.encode_input(LUSD) ) + master_price_oracle.updateTokenPriceFeed( + ARB, chainlink_oracle, chainlink_oracle.getTokenPrice.encode_input(ARB) + ) + spa_oracle = SPAOracle.deploy( - master_price_oracle, USDC_E, 10000, 600, 70, {"from": user}, publish_source=True + master_price_oracle, USDC_E, 10000, 600, 70, {"from": user}, publish_source=PUBLISH_SRC ) usds_oracle = USDsOracle.deploy( - master_price_oracle, USDC_E, 500, 600, {"from": user}, publish_source=True + master_price_oracle, USDC_E, 500, 600, {"from": user}, publish_source=PUBLISH_SRC ) master_price_oracle.updateTokenPriceFeed( @@ -66,6 +78,6 @@ def main(): usds_oracle.transferOwnership(USDS_OWNER_ADDR, {"from": user}) master_price_oracle.transferOwnership(USDS_OWNER_ADDR, {"from": user}) - tokens = [SPA, USDS, USDC_E, USDT, FRAX, DAI, LUSD] + tokens = [SPA, USDS, USDC_E, USDT, FRAX, DAI, LUSD, ARB] for token in tokens: print(f"Fetching Price feed for {token}: {master_price_oracle.getPrice(token)}") From 6429d640581bfc04a5a1e0490f092d21c91fb084 Mon Sep 17 00:00:00 2001 From: mehultuteja Date: Tue, 9 Jan 2024 13:21:26 +0530 Subject: [PATCH 42/64] Updated setter's fn names and events --- contracts/buyback/YieldReserve.sol | 28 ++++++++++++++-------------- test/buyback/YieldReserve.t.sol | 24 ++++++++++++------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/contracts/buyback/YieldReserve.sol b/contracts/buyback/YieldReserve.sol index 4166b469..5c365305 100644 --- a/contracts/buyback/YieldReserve.sol +++ b/contracts/buyback/YieldReserve.sol @@ -45,10 +45,10 @@ contract YieldReserve is ReentrancyGuard, Ownable { event USDsMintedViaSwapper(address indexed collateralAddr, uint256 usdsMinted); event Withdrawn(address indexed token, address indexed receiver, uint256 amount); event BuybackPercentageUpdated(uint256 toBuyback); - event BuybackAddressUpdated(address newBuyback); + event BuybackUpdated(address newBuyback); event OracleUpdated(address newOracle); - event VaultAddressUpdated(address newVault); - event DripperAddressUpdated(address newDripper); + event VaultUpdated(address newVault); + event DripperUpdated(address newDripper); event USDsSent(uint256 toBuyback, uint256 toVault); event SrcTokenPermissionUpdated(address indexed token, bool isAllowed); event DstTokenPermissionUpdated(address indexed token, bool isAllowed); @@ -65,10 +65,10 @@ contract YieldReserve is ReentrancyGuard, Ownable { /// @param _oracle Address of the Oracle. /// @param _dripper Address of the Dripper contract. constructor(address _buyback, address _vault, address _oracle, address _dripper) { - updateBuybackAddress(_buyback); - updateVaultAddress(_vault); - updateOracleAddress(_oracle); - updateDripperAddress(_dripper); + updateBuyback(_buyback); + updateVault(_vault); + updateOracle(_oracle); + updateDripper(_dripper); // Initialize buybackPercentage to 50% updateBuybackPercentage(5000); @@ -154,16 +154,16 @@ contract YieldReserve is ReentrancyGuard, Ownable { /// @notice Update the address of the Buyback contract. /// @dev Reverts if caller is not owner. /// @param _newBuyBack New address of the Buyback contract. - function updateBuybackAddress(address _newBuyBack) public onlyOwner { + function updateBuyback(address _newBuyBack) public onlyOwner { Helpers._isNonZeroAddr(_newBuyBack); buyback = _newBuyBack; - emit BuybackAddressUpdated(_newBuyBack); + emit BuybackUpdated(_newBuyBack); } /// @notice Update the address of the Oracle contract. /// @dev Reverts if caller is not owner. /// @param _newOracle New address of the Oracle contract. - function updateOracleAddress(address _newOracle) public onlyOwner { + function updateOracle(address _newOracle) public onlyOwner { Helpers._isNonZeroAddr(_newOracle); oracle = _newOracle; emit OracleUpdated(_newOracle); @@ -172,19 +172,19 @@ contract YieldReserve is ReentrancyGuard, Ownable { /// @notice Update the address of the Dripper contract. /// @dev Reverts if caller is not owner. /// @param _newDripper New address of the Dripper contract. - function updateDripperAddress(address _newDripper) public onlyOwner { + function updateDripper(address _newDripper) public onlyOwner { Helpers._isNonZeroAddr(_newDripper); dripper = _newDripper; - emit DripperAddressUpdated(_newDripper); + emit DripperUpdated(_newDripper); } /// @notice Update the address of the VaultCore contract. /// @dev Reverts if caller is not owner. /// @param _newVault New address of the VaultCore contract. - function updateVaultAddress(address _newVault) public onlyOwner { + function updateVault(address _newVault) public onlyOwner { Helpers._isNonZeroAddr(_newVault); vault = _newVault; - emit VaultAddressUpdated(_newVault); + emit VaultUpdated(_newVault); } /// @notice Swap allowed source token for allowed destination token. diff --git a/test/buyback/YieldReserve.t.sol b/test/buyback/YieldReserve.t.sol index 5a78ca72..8b855ea7 100644 --- a/test/buyback/YieldReserve.t.sol +++ b/test/buyback/YieldReserve.t.sol @@ -168,61 +168,61 @@ contract YieldReserveTest is YieldReserveSetup { function test_updateBuybackAddress_auth_error() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); - yieldReserve.updateBuybackAddress(VAULT); + yieldReserve.updateBuyback(VAULT); } function test_updateBuybackAddress_inputs() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); - yieldReserve.updateBuybackAddress(address(0)); + yieldReserve.updateBuyback(address(0)); } function test_updateBuybackAddress() public useKnownActor(USDS_OWNER) { - yieldReserve.updateBuybackAddress(VAULT); + yieldReserve.updateBuyback(VAULT); assertEq(yieldReserve.buyback(), VAULT); } function test_updateOracleAddress_auth_error() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); - yieldReserve.updateOracleAddress(VAULT); + yieldReserve.updateOracle(VAULT); } function test_updateOracleAddress_inputs() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); - yieldReserve.updateOracleAddress(address(0)); + yieldReserve.updateOracle(address(0)); } function test_updateOracleAddress() public useKnownActor(USDS_OWNER) { - yieldReserve.updateOracleAddress(VAULT); + yieldReserve.updateOracle(VAULT); assertEq(yieldReserve.oracle(), VAULT); } function test_updateDripperAddress_auth_error() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); - yieldReserve.updateDripperAddress(VAULT); + yieldReserve.updateDripper(VAULT); } function test_updateDripperAddress_inputs() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); - yieldReserve.updateDripperAddress(address(0)); + yieldReserve.updateDripper(address(0)); } function test_updateDripperAddress() public useKnownActor(USDS_OWNER) { - yieldReserve.updateDripperAddress(VAULT); + yieldReserve.updateDripper(VAULT); assertEq(yieldReserve.dripper(), VAULT); } function test_updateVaultAddress_auth_error() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); - yieldReserve.updateVaultAddress(VAULT); + yieldReserve.updateVault(VAULT); } function test_updateVaultAddress_inputs() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); - yieldReserve.updateVaultAddress(address(0)); + yieldReserve.updateVault(address(0)); } function test_updateVaultAddress() public useKnownActor(USDS_OWNER) { - yieldReserve.updateVaultAddress(ORACLE); + yieldReserve.updateVault(ORACLE); assertEq(yieldReserve.vault(), ORACLE); } From 96bbc75d7dc375e8e0ce485b6662c73337630e70 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Wed, 10 Jan 2024 15:42:53 +0530 Subject: [PATCH 43/64] refactor(contracts/vault): Remove unused error message --- contracts/vault/FeeCalculator.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/vault/FeeCalculator.sol b/contracts/vault/FeeCalculator.sol index 0f5e1002..5ad4af96 100644 --- a/contracts/vault/FeeCalculator.sol +++ b/contracts/vault/FeeCalculator.sol @@ -31,7 +31,6 @@ contract FeeCalculator is IFeeCalculator { // Custom error messages error InvalidCalibration(); - error FeeNotCalibrated(address collateral); constructor(address _collateralManager) { COLLATERAL_MANAGER = ICollateralManager(_collateralManager); From 195fcb22a4f37319364ba6319ef1195bddeb0034 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Wed, 10 Jan 2024 16:10:45 +0530 Subject: [PATCH 44/64] docs(deployed): Add SpaBuybackUpgrade details --- ...de_spa_buyback_v3_01-08-2024_18:13:57.json | 24 +++++++++++++++++++ scripts/configurations.py | 18 ++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 deployed/arbitrum-one/Upgrade_spa_buyback_v3_01-08-2024_18:13:57.json diff --git a/deployed/arbitrum-one/Upgrade_spa_buyback_v3_01-08-2024_18:13:57.json b/deployed/arbitrum-one/Upgrade_spa_buyback_v3_01-08-2024_18:13:57.json new file mode 100644 index 00000000..2a65fec4 --- /dev/null +++ b/deployed/arbitrum-one/Upgrade_spa_buyback_v3_01-08-2024_18:13:57.json @@ -0,0 +1,24 @@ +{ + "new_impl": "0xa8308A2dEEaEb7eE7CB27845328A27Cd755C8Dc9", + "Description": "1. Upgrade solc version \n2. Add new veSPA rewarder and integrate new oracle", + "type": "Upgrade", + "transactions": [ + { + "step": "New_implementation_deployment", + "tx_hash": "0x3db3c68228b0885f638bbbabc4bce8c9f2f195fdc730d5dcde81c7d452eadaf0", + "contract": "SPABuyback", + "contract_addr": "0xa8308A2dEEaEb7eE7CB27845328A27Cd755C8Dc9", + "tx_func": "constructor", + "blocknumber": 168340658, + "gas_used": 16448193, + "gas_limit": 20208250 + } + ], + "config_name": "spa_buyback_v3", + "config": { + "proxy_address": "0xFbc0d3cA777722d234FE01dba94DeDeDb277AFe3", + "gnosis_upgrade": true, + "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25", + "post_upgrade_steps": [] + } +} \ No newline at end of file diff --git a/scripts/configurations.py b/scripts/configurations.py index 0ddee2dc..ab3667ee 100644 --- a/scripts/configurations.py +++ b/scripts/configurations.py @@ -1,4 +1,8 @@ -from brownie import VaultCore, MasterPriceOracle, ChainlinkOracle, USDs +from brownie import ( + SPABuyback, + VaultCore, + USDs, +) from .utils import ( Deployment_data, @@ -10,16 +14,19 @@ PROXY_ADMIN = "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25" USDS_ADDR = "0xD74f5255D557944cf7Dd0E45FF521520002D5748" +SPA_BUYBACK_ADDR = "0xFbc0d3cA777722d234FE01dba94DeDeDb277AFe3" USDS_OWNER_ADDR = "0x5b12d9846F8612E439730d18E1C12634753B1bF1" ## Tokens: SPA = "0x5575552988A3A80504bBaeB1311674fCFd40aD4B" USDS = "0xD74f5255D557944cf7Dd0E45FF521520002D5748" +USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" USDC_E = "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8" DAI = "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1" FRAX = "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F" USDT = "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9" LUSD = "0x93b346b6bc2548da6a1e7d98e9a421b42541425b" +ARB = "0x912CE59144191C1204E64559FE8253a0e49E6548" deployment_config = { @@ -47,5 +54,12 @@ gnosis_upgrade=True, proxy_address=USDS_ADDR, proxy_admin=PROXY_ADMIN ), description="Remove upgrade account functionality", - ) + ), + "spa_buyback_v3": Upgrade_data( + contract=SPABuyback, + config=Upgrade_config( + gnosis_upgrade=True, proxy_address=SPA_BUYBACK_ADDR, proxy_admin=PROXY_ADMIN + ), + description="1. Upgrade solc version \n2. Add new veSPA rewarder and integrate new oracle", + ), } From b8b482c54bddc2fc100bb10233e68b0d0b50b15d Mon Sep 17 00:00:00 2001 From: YashP16 Date: Wed, 10 Jan 2024 17:34:30 +0530 Subject: [PATCH 45/64] refactor(contracts/rebase): Add collect() as a part of addUSDs() in dripper. --- contracts/rebase/Dripper.sol | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/contracts/rebase/Dripper.sol b/contracts/rebase/Dripper.sol index 91e10d02..d2ad6818 100644 --- a/contracts/rebase/Dripper.sol +++ b/contracts/rebase/Dripper.sol @@ -48,10 +48,19 @@ contract Dripper is IDripper, Ownable { emit Recovered(msg.sender, bal); } + /// @notice Function to be used to send USDs to dripper and update `dripRate`. + /// @param _amount Amount of USDs to be sent form caller to this contract. + function addUSDs(uint256 _amount) external { + collect(); + Helpers._isNonZeroAmt(_amount); + IERC20(Helpers.USDS).safeTransferFrom(msg.sender, address(this), _amount); + dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; + } + /// @notice Transfers the dripped tokens to the vault. /// @dev This function also updates the dripRate based on the fund state. /// @return The amount of tokens collected and transferred to the vault. - function collect() external returns (uint256) { + function collect() public returns (uint256) { uint256 collectableAmt = getCollectableAmt(); if (collectableAmt != 0) { lastCollectTS = block.timestamp; @@ -62,14 +71,6 @@ contract Dripper is IDripper, Ownable { return collectableAmt; } - /// @notice Function to be used to send USDs to dripper and update `dripRate`. - /// @param _amount Amount of USDs to be sent form caller to this contract. - function addUSDs(uint256 _amount) external { - Helpers._isNonZeroAmt(_amount); - IERC20(Helpers.USDS).safeTransferFrom(msg.sender, address(this), _amount); - dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; - } - /// @notice Update the vault address. /// @param _vault Address of the new vault contract. function updateVault(address _vault) public onlyOwner { From 1327d47ad907d64be9fc4281b469ef50713b9e1f Mon Sep 17 00:00:00 2001 From: YashP16 Date: Wed, 10 Jan 2024 17:39:34 +0530 Subject: [PATCH 46/64] perf(contracts/rebase): Add USDsAdded event in Dripper. --- contracts/rebase/Dripper.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/rebase/Dripper.sol b/contracts/rebase/Dripper.sol index d2ad6818..ea5e8edb 100644 --- a/contracts/rebase/Dripper.sol +++ b/contracts/rebase/Dripper.sol @@ -23,6 +23,7 @@ contract Dripper is IDripper, Ownable { event Recovered(address owner, uint256 amount); event VaultUpdated(address vault); event DripDurationUpdated(uint256 dripDuration); + event USDsAdded(uint256 _amount); // Custom error messages error NothingToRecover(); @@ -55,6 +56,7 @@ contract Dripper is IDripper, Ownable { Helpers._isNonZeroAmt(_amount); IERC20(Helpers.USDS).safeTransferFrom(msg.sender, address(this), _amount); dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; + emit USDsAdded(_amount); } /// @notice Transfers the dripped tokens to the vault. From 96ea5fc310c81e9a71be2e59eb219dc295a436dc Mon Sep 17 00:00:00 2001 From: Parv3213 Date: Wed, 10 Jan 2024 17:45:35 +0530 Subject: [PATCH 47/64] perf(contracts/rebase): Move `collect()` after zero check --- contracts/rebase/Dripper.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/rebase/Dripper.sol b/contracts/rebase/Dripper.sol index ea5e8edb..cefcb326 100644 --- a/contracts/rebase/Dripper.sol +++ b/contracts/rebase/Dripper.sol @@ -52,8 +52,8 @@ contract Dripper is IDripper, Ownable { /// @notice Function to be used to send USDs to dripper and update `dripRate`. /// @param _amount Amount of USDs to be sent form caller to this contract. function addUSDs(uint256 _amount) external { - collect(); Helpers._isNonZeroAmt(_amount); + collect(); IERC20(Helpers.USDS).safeTransferFrom(msg.sender, address(this), _amount); dripRate = IERC20(Helpers.USDS).balanceOf(address(this)) / dripDuration; emit USDsAdded(_amount); From a31f999c8c7d3412b3592ef00b2881be3bf6d6de Mon Sep 17 00:00:00 2001 From: YashP16 Date: Thu, 11 Jan 2024 15:53:03 +0530 Subject: [PATCH 48/64] feat(contracts/buyback): Add mintUSDs() in YieldReserve contract Changes: * Add mintUSDs() in yieldReserve contract to directly convert collaterals to USDs. * Don't charge any mint fee for yieldReserve contract. --- contracts/buyback/YieldReserve.sol | 17 ++++++++++++++--- contracts/vault/VaultCore.sol | 2 +- test/buyback/YieldReserve.t.sol | 30 ++++++++++++++++++++++++++++++ test/vault/VaultCore.t.sol | 7 +++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/contracts/buyback/YieldReserve.sol b/contracts/buyback/YieldReserve.sol index 5c365305..623ac686 100644 --- a/contracts/buyback/YieldReserve.sol +++ b/contracts/buyback/YieldReserve.sol @@ -49,7 +49,7 @@ contract YieldReserve is ReentrancyGuard, Ownable { event OracleUpdated(address newOracle); event VaultUpdated(address newVault); event DripperUpdated(address newDripper); - event USDsSent(uint256 toBuyback, uint256 toVault); + event USDsSent(uint256 toBuyback, uint256 toDripper); event SrcTokenPermissionUpdated(address indexed token, bool isAllowed); event DstTokenPermissionUpdated(address indexed token, bool isAllowed); @@ -188,7 +188,6 @@ contract YieldReserve is ReentrancyGuard, Ownable { } /// @notice Swap allowed source token for allowed destination token. - /// @dev Reverts if caller is not owner. /// @param _srcToken Source/Input token. /// @param _dstToken Destination/Output token. /// @param _amountIn Input token amount. @@ -221,8 +220,20 @@ contract YieldReserve is ReentrancyGuard, Ownable { }); } + /// @notice Mints USDs directly with the allowed collaterals for USDs. + /// @param _token Address of token to mint USDs with + /// @dev Only collaterals configured in USDs vault are allowed to be used for minting. + function mintUSDs(address _token) public nonReentrant { + Helpers._isNonZeroAddr(_token); + uint256 bal = IERC20(_token).balanceOf(address(this)); + IERC20(_token).forceApprove(vault, bal); + IVault(vault).mint(_token, bal, 0, block.timestamp); + // No need to do slippage check as it is our contract + // and the vault does that. + _sendUSDs(); + } + /// @notice Get an estimate of the output token amount for a given input token amount. - /// @dev Reverts if caller is not owner. /// @param _srcToken Input token address. /// @param _dstToken Output token address. /// @param _amountIn Input amount of _srcToken. diff --git a/contracts/vault/VaultCore.sol b/contracts/vault/VaultCore.sol index 2e92bc0e..3f6f8e0a 100644 --- a/contracts/vault/VaultCore.sol +++ b/contracts/vault/VaultCore.sol @@ -271,7 +271,7 @@ contract VaultCore is Initializable, OwnableUpgradeable, ReentrancyGuardUpgradea // Skip fee collection for owner uint256 feePercentage = 0; - if (msg.sender != owner()) { + if (msg.sender != owner() && msg.sender != yieldReceiver) { // Calculate mint fee based on collateral data feePercentage = IFeeCalculator(feeCalculator).getMintFee(_collateral); } diff --git a/test/buyback/YieldReserve.t.sol b/test/buyback/YieldReserve.t.sol index 8b855ea7..ac285977 100644 --- a/test/buyback/YieldReserve.t.sol +++ b/test/buyback/YieldReserve.t.sol @@ -275,6 +275,36 @@ contract YieldReserveTest is YieldReserveSetup { } } +contract Test_MintUSDs is YieldReserveSetup { + address buyback; + + event Minted( + address indexed wallet, address indexed collateralAddr, uint256 usdsAmt, uint256 collateralAmt, uint256 feeAmt + ); + event USDsAdded(uint256 _amount); + + function setUp() public override { + super.setUp(); + buyback = yieldReserve.buyback(); + deal(address(USDCe), address(yieldReserve), 1e9); + mockPrice(USDCe, 1e8, PRICE_PRECISION); + mockPrice(USDS, 1e8, PRICE_PRECISION); + } + + function test_mintUSDs() public { + uint256 buyback_initialUSDsBal = IERC20(USDS).balanceOf(buyback); + uint256 reserveBal = IERC20(USDCe).balanceOf(address(yieldReserve)); + (uint256 usdsToMint, uint256 feeAmt) = IVault(VAULT).mintView(USDCe, reserveBal); + uint256 toBuyback = (usdsToMint * yieldReserve.buybackPercentage() / Helpers.MAX_PERCENTAGE); + vm.expectEmit(); + emit Minted(address(yieldReserve), USDCe, usdsToMint, reserveBal, feeAmt); + emit USDsAdded(usdsToMint - toBuyback); + yieldReserve.mintUSDs(USDCe); + assertEq(IERC20(USDCe).balanceOf(address(yieldReserve)), 0); + assertLe(IERC20(USDS).balanceOf(buyback), buyback_initialUSDsBal + toBuyback); + } +} + contract SwapTest is YieldReserveSetup { function setUp() public override { super.setUp(); diff --git a/test/vault/VaultCore.t.sol b/test/vault/VaultCore.t.sol index bdd2aed3..178469fa 100644 --- a/test/vault/VaultCore.t.sol +++ b/test/vault/VaultCore.t.sol @@ -427,6 +427,13 @@ contract TestMintView is VaultCoreTest { assertEq(_fee, 0); } + function test_Fee0If_CallerIsYieldReceiver() public { + vm.prank(IVault(VAULT).yieldReceiver()); + (_toMinter, _fee) = IVault(VAULT).mintView(_collateral, _collateralAmt); + assertTrue(_toMinter > 98e20); + assertEq(_fee, 0); + } + function test_MintView() public mockOracle(101e6) { uint256 expectedFee; uint256 expectedToMinter; From a0769cf11cef8271fc0d9a288ab68dcd67415357 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Thu, 11 Jan 2024 16:40:21 +0530 Subject: [PATCH 49/64] refactor(contracts/rebase): Update lastCollectTs even on 0 collect from dripper. --- contracts/rebase/Dripper.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/rebase/Dripper.sol b/contracts/rebase/Dripper.sol index cefcb326..6575ef2d 100644 --- a/contracts/rebase/Dripper.sol +++ b/contracts/rebase/Dripper.sol @@ -65,10 +65,10 @@ contract Dripper is IDripper, Ownable { function collect() public returns (uint256) { uint256 collectableAmt = getCollectableAmt(); if (collectableAmt != 0) { - lastCollectTS = block.timestamp; IERC20(Helpers.USDS).safeTransfer(vault, collectableAmt); emit Collected(collectableAmt); } + lastCollectTS = block.timestamp; if (IERC20(Helpers.USDS).balanceOf(address(this)) == 0) dripRate = 0; return collectableAmt; } From 60f1e90a2a6cbc68b95dc3c6ec54d5da3663363f Mon Sep 17 00:00:00 2001 From: YashP16 Date: Thu, 11 Jan 2024 19:19:12 +0530 Subject: [PATCH 50/64] docs: Add deployment artifacts --- ...e_spa_buyback_v3_01-08-2024_18-13-57.json} | 0 .../Upgrade_usds_v2_01-11-2024_17-09-36.json | 24 +++++++++++++++++++ deployed/arbitrum-one/deployment_data.json | 23 ++++++++++++++++++ 3 files changed, 47 insertions(+) rename deployed/arbitrum-one/{Upgrade_spa_buyback_v3_01-08-2024_18:13:57.json => Upgrade_spa_buyback_v3_01-08-2024_18-13-57.json} (100%) create mode 100644 deployed/arbitrum-one/Upgrade_usds_v2_01-11-2024_17-09-36.json create mode 100644 deployed/arbitrum-one/deployment_data.json diff --git a/deployed/arbitrum-one/Upgrade_spa_buyback_v3_01-08-2024_18:13:57.json b/deployed/arbitrum-one/Upgrade_spa_buyback_v3_01-08-2024_18-13-57.json similarity index 100% rename from deployed/arbitrum-one/Upgrade_spa_buyback_v3_01-08-2024_18:13:57.json rename to deployed/arbitrum-one/Upgrade_spa_buyback_v3_01-08-2024_18-13-57.json diff --git a/deployed/arbitrum-one/Upgrade_usds_v2_01-11-2024_17-09-36.json b/deployed/arbitrum-one/Upgrade_usds_v2_01-11-2024_17-09-36.json new file mode 100644 index 00000000..4e99100a --- /dev/null +++ b/deployed/arbitrum-one/Upgrade_usds_v2_01-11-2024_17-09-36.json @@ -0,0 +1,24 @@ +{ + "new_impl": "0x67b58013742ce6Bb0Cdc2EF3CE0A35c1d2F5f3c2", + "Description": "Remove upgrade account functionality", + "type": "Upgrade", + "transactions": [ + { + "step": "New_implementation_deployment", + "tx_hash": "0x80757cfd846fac2aea8261e45dc5b5f860e804fd4e237f0bedf88dce2d1d3de0", + "contract": "USDs", + "contract_addr": "0x67b58013742ce6Bb0Cdc2EF3CE0A35c1d2F5f3c2", + "tx_func": "constructor", + "blocknumber": 169353778, + "gas_used": 24921731, + "gas_limit": 30403123 + } + ], + "config_name": "usds_v2", + "config": { + "proxy_address": "0xD74f5255D557944cf7Dd0E45FF521520002D5748", + "gnosis_upgrade": true, + "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25", + "post_upgrade_steps": [] + } +} \ No newline at end of file diff --git a/deployed/arbitrum-one/deployment_data.json b/deployed/arbitrum-one/deployment_data.json new file mode 100644 index 00000000..c98a0e68 --- /dev/null +++ b/deployed/arbitrum-one/deployment_data.json @@ -0,0 +1,23 @@ +{ + "ARB": "0x912CE59144191C1204E64559FE8253a0e49E6548", + "USDC": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + "USDCe": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + "DAI": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", + "USDT": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", + "SPA": "0x5575552988A3A80504bBaeB1311674fCFd40aD4B", + "FRAX": "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", + "LUSD": "0x93b346b6bc2548da6a1e7d98e9a421b42541425b", + "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25", + "usds": "0xD74f5255D557944cf7Dd0E45FF521520002D5748", + "chainlink_oracle": "0x0Ca9bf1c701a403f6dB0695a0D6996288F70935d", + "master_price_oracle": "0x14D99412dAB1878dC01Fe7a1664cdE85896e8E50", + "spa_buyback_impl": "0xa8308A2dEEaEb7eE7CB27845328A27Cd755C8Dc9", + "spa_buyback": "0xFbc0d3cA777722d234FE01dba94DeDeDb277AFe3", + "vault": "0x6Bbc476Ee35CBA9e9c3A59fc5b10d7a0BC6f74Ca", + "vault_impl": "0xB31EA46Eb77C7381ED3a28FDD28563885A97DB7C", + "fee_calculator": "0xd122840Fa5b48B2ddB723cCC5928f88dcb558AFC", + "collateral_manager": "0xdA423BFa1E196598190deEfbAFC28aDb36FaeDF0", + "dripper": "0xd50193e8fFb00beA274bD2b11d0a7Ea08dA044c1", + "rebase_manager": "0x297331A0155B1e30bBFA85CF3609eC0fF037BEEC", + "yield_reserve": "0xfD14C8ef0993fd9409f7820BA8BA80370529d861" +} \ No newline at end of file From d8d5f86df507c0cbaacaf5634454a1e3659df8a9 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Thu, 11 Jan 2024 19:24:42 +0530 Subject: [PATCH 51/64] feat(scripts): Add deployment and preset script for USDsV2 --- scripts/deploy_vault.py | 265 ++++++++++++++++++++++++++++++++++++++++ scripts/utils.py | 2 +- 2 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 scripts/deploy_vault.py diff --git a/scripts/deploy_vault.py b/scripts/deploy_vault.py new file mode 100644 index 00000000..66aa9a8e --- /dev/null +++ b/scripts/deploy_vault.py @@ -0,0 +1,265 @@ +from brownie import ( + MasterPriceOracle, + USDs, + VaultCore, + CollateralManager, + FeeCalculator, + Dripper, + RebaseManager, + SPABuyback, + YieldReserve, + ProxyAdmin, + TUP, + Contract, + network, +) + +import json + +from .utils import get_user +import eth_utils + +DEPLOYMENT_ARTIFACTS = f'deployed/{network.show_active()}/deployment_data.json' +USDS_OWNER_ADDR = "0x5b12d9846F8612E439730d18E1C12634753B1bF1" +PUBLISH_SRC = False + +class Token: + def __init__(self, name, symbol, decimals): + self.name = name + self.symbol = symbol + self.decimals = decimals + + +class CollateralData: + def __init__( + self, + address, + mint_allowed, + redeem_allowed, + allocation_allowed, + base_fee_in, + base_fee_out, + downside_peg, + desired_collateral_composition, + ): + self.address = address + self.mint_allowed = mint_allowed + self.redeem_allowed = redeem_allowed + self.allocation_allowed = allocation_allowed + self.base_fee_in = base_fee_in + self.base_fee_out = base_fee_out + self.downside_peg = downside_peg + self.desired_collateral_composition = desired_collateral_composition + + +def deploy(deployments, contract, args, key): + if key in deployments.keys(): + print(f'\n Using pre-deployed {key}\n') + return contract.at(deployments[key]), False + else: + return contract.deploy(*args, publish_source=PUBLISH_SRC), True + + +def main(): + owner = get_user('Select deployer ') + deployments = {} + data = {} + with open(DEPLOYMENT_ARTIFACTS) as file: + deployments = json.load(file) + + # Deploy USDs Contract + print('\n -- Deploying USDs contract -- \n') + proxy_admin, _ = deploy(deployments, ProxyAdmin, [{'from': owner}], 'proxy_admin') + usds = Contract.from_abi('USDs', deployments['usds'], USDs.abi) + + # Deploy Vault contract + print('\n -- Deploying Vault -- \n') + vault_impl, _ = deploy(deployments, VaultCore, [{'from': owner}], 'vault_impl') + + vault_proxy, new_vault = deploy( + deployments, + TUP, + [vault_impl, proxy_admin, eth_utils.to_bytes(hexstr='0x'), {'from': owner}], + 'vault', + ) + vault = Contract.from_abi('Vault', vault_proxy, VaultCore.abi) + if new_vault: + vault.initialize({'from': owner}) + + # Deploy oracles + print('\n -- Deploying and configuring Oracle contracts -- \n') + master_price_oracle, new_master_oracle = deploy( + deployments, MasterPriceOracle, [{'from': owner}], 'master_price_oracle' + ) + + # Deploy Vault plugins + print('\n -- Deploying Vault Plugins -- \n') + collateral_manager, new_collateral_manager = deploy( + deployments, CollateralManager, [vault, {'from': owner}], 'collateral_manager' + ) + fee_calculator, new_fee_calculator = deploy( + deployments, FeeCalculator, [collateral_manager, {'from': owner}], 'fee_calculator' + ) + dripper, new_dripper = deploy( + deployments, Dripper, [vault, 7 * 86400, {'from': owner}], 'dripper' + ) + + rebase_manager, new_rebase_manager = deploy( + deployments, + RebaseManager, + [vault, dripper, 86400, 1000, 300, {'from': owner}], + 'rebase_manager', + ) + if(new_dripper and not new_rebase_manager): + rebase_manager.updateDripper(dripper, {'from':owner}) + + spa_buyback_impl, new_spa_buyback_impl = deploy( + deployments, SPABuyback, [{'from': owner}], 'spa_buyback_impl' + ) + spa_buyback_proxy, new_spa_buyback = deploy( + deployments, + TUP, + [ + spa_buyback_impl, + proxy_admin, + eth_utils.to_bytes(hexstr='0x'), + {'from': owner}, + ], + 'spa_buyback', + ) + spa_buyback = Contract.from_abi('SPABuyback', spa_buyback_proxy, SPABuyback.abi) + + yield_reserve, new_yield_reserve = deploy( + deployments, + YieldReserve, + [spa_buyback, vault, master_price_oracle, dripper, {'from': owner}], + 'yield_reserve', + ) + + print('Configuring yield reserve contract') + yield_reserve.toggleSrcTokenPermission(usds, True, {'from': owner}) + yield_reserve.toggleDstTokenPermission(deployments['USDC'], True, {'from': owner}) + yield_reserve.toggleDstTokenPermission(deployments['USDCe'], True, {'from': owner}) + yield_reserve.toggleDstTokenPermission(deployments['DAI'], True, {'from': owner}) + yield_reserve.toggleDstTokenPermission(deployments['FRAX'], True, {'from': owner}) + yield_reserve.toggleDstTokenPermission(deployments['LUSD'], True, {'from': owner}) + + # Configuring vault + if(new_collateral_manager or new_vault): + vault.updateCollateralManager(collateral_manager, {'from': owner}) + + if(new_spa_buyback or new_vault): + vault.updateFeeVault(spa_buyback, {'from': owner}) + + if(new_master_oracle or new_vault): + vault.updateOracle(master_price_oracle, {'from': owner}) + + if(new_fee_calculator or new_vault): + vault.updateFeeCalculator(fee_calculator, {'from': owner}) + + if(new_rebase_manager or new_vault): + vault.updateRebaseManager(rebase_manager, {'from': owner}) + + if(new_yield_reserve or new_vault): + vault.updateYieldReceiver(yield_reserve, {'from': owner}) + + collateral_data = [ + CollateralData( + deployments['USDC'], + True, + True, + True, + 1, + 2, + 9700, + 2500, + ), + CollateralData( + deployments['USDCe'], + True, + True, + True, + 1, + 2, + 9700, + 2000, + ), + CollateralData( + deployments['DAI'], True, True, True, 1, 2, 9700, 2000 + ), + CollateralData( + deployments['USDT'], + True, + True, + True, + 1, + 2, + 9700, + 1500, + ), + CollateralData( + deployments['FRAX'], + True, + True, + True, + 10, + 1, + 9700, + 1000, + ), + CollateralData( + deployments['LUSD'], + True, + True, + True, + 50, + 1, + 9700, + 1000, + ), + ] + + print('Setting up collateral information:') + if new_collateral_manager: + for item in collateral_data: + print(f'Adding collateral: {item.address}') + collateral_manager.addCollateral( + item.address, + [ + item.mint_allowed, + item.redeem_allowed, + item.allocation_allowed, + item.base_fee_in, + item.base_fee_out, + item.downside_peg, + item.desired_collateral_composition, + ], + {'from': owner}, + ) + + # vault.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) + # collateral_manager.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) + # yield_reserve.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) + # dripper.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) + # rebase_manager.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) + + + data = { + **data, + 'proxy_admin': proxy_admin.address, + 'usds': usds.address, + 'master_price_oracle': master_price_oracle.address, + 'vault': vault.address, + 'vault_impl': vault_impl.address, + 'fee_calculator': fee_calculator.address, + 'collateral_manager': collateral_manager.address, + 'dripper': dripper.address, + 'rebase_manager': rebase_manager.address, + 'spa_buyback_impl': spa_buyback_impl.address, + 'spa_buyback': spa_buyback.address, + 'yield_reserve': yield_reserve.address, + } + + deployments = {**deployments, **data} + with open(DEPLOYMENT_ARTIFACTS, 'w') as outfile: + json.dump(deployments, outfile) \ No newline at end of file diff --git a/scripts/utils.py b/scripts/utils.py index 1a0c3da4..c69d626a 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -122,7 +122,7 @@ def save_deployment_artifacts(data, name, operation_type=""): + "_" + name + "_" - + time.strftime("%m-%d-%Y_%H:%M:%S") + + time.strftime("%m-%d-%Y_%H-%M-%S") + ".json", ) with open(file, "w") as json_file: From 211a6ad7c568893aca696f92d9f3b6315d9d6e88 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Thu, 11 Jan 2024 19:42:13 +0530 Subject: [PATCH 52/64] feat(script): Add preset script --- deployed/arbitrum-one/deployment_data.json | 1 + scripts/preset.py | 70 ++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 scripts/preset.py diff --git a/deployed/arbitrum-one/deployment_data.json b/deployed/arbitrum-one/deployment_data.json index c98a0e68..bda57c3d 100644 --- a/deployed/arbitrum-one/deployment_data.json +++ b/deployed/arbitrum-one/deployment_data.json @@ -9,6 +9,7 @@ "LUSD": "0x93b346b6bc2548da6a1e7d98e9a421b42541425b", "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25", "usds": "0xD74f5255D557944cf7Dd0E45FF521520002D5748", + "usds_impl": "0x67b58013742ce6Bb0Cdc2EF3CE0A35c1d2F5f3c2", "chainlink_oracle": "0x0Ca9bf1c701a403f6dB0695a0D6996288F70935d", "master_price_oracle": "0x14D99412dAB1878dC01Fe7a1664cdE85896e8E50", "spa_buyback_impl": "0xa8308A2dEEaEb7eE7CB27845328A27Cd755C8Dc9", diff --git a/scripts/preset.py b/scripts/preset.py new file mode 100644 index 00000000..404822ef --- /dev/null +++ b/scripts/preset.py @@ -0,0 +1,70 @@ +from brownie import ( + ERC20, + MasterPriceOracle, + ChainlinkOracle, + USDs, + VaultCore, + CollateralManager, + FeeCalculator, + Dripper, + RebaseManager, + SPABuyback, + YieldReserve, + ProxyAdmin, + TUP, + Contract, + network, +) + +import json + +from .utils import get_user +import eth_utils + +DEPLOYMENT_ARTIFACTS = f'deployed/{network.show_active()}/deployment_data.json' +MIGRATE = True + +def main(): + owner = get_user('Select deployer ') + data = {} + with open(DEPLOYMENT_ARTIFACTS) as file: + data = json.load(file) + + # Deploy all the ERC20 contracts + usdc = ERC20.at(data['USDC']) + dai = ERC20.at(data['DAI']) + usdt = ERC20.at(data['USDT']) + spa = ERC20.at(data['SPA']) + frax = ERC20.at(data['FRAX']) + arb = ERC20.at(data['ARB']) + lusd = ERC20.at(data['LUSD']) + + proxy_admin = Contract.from_abi('ProxyAdmin', data['proxy_admin'], ProxyAdmin.abi) + usds = Contract.from_abi('USDs', data['usds'], USDs.abi) + + chainlink_oracle = Contract.from_abi( + 'chainlinkOracle', data['chainlink_oracle'], ChainlinkOracle.abi + ) + master_price_oracle = Contract.from_abi( + 'MPO', data['master_price_oracle'], MasterPriceOracle.abi + ) + + vault = Contract.from_abi('Vault', data['vault'], VaultCore.abi) + fee_calculator = Contract.from_abi( + 'FeeCalculator', data['fee_calculator'], FeeCalculator.abi + ) + collateral_manager = Contract.from_abi( + 'CM', data['collateral_manager'], CollateralManager.abi + ) + dripper = Contract.from_abi('Dripper', data['dripper'], Dripper.abi) + rebase_manager = Contract.from_abi( + 'RebaseManager', data['rebase_manager'], RebaseManager.abi + ) + spa_buyback = Contract.from_abi('SPABuyback', data['spa_buyback'], SPABuyback.abi) + yield_reserve = Contract.from_abi( + 'YieldReserve', data['yield_reserve'], YieldReserve.abi + ) + + if (MIGRATE): + proxy_admin.upgrade(usds, data['usds_impl'], {'from': proxy_admin.owner()}) + usds.updateVault(vault, {'from': usds.owner()}) From eb5c451fcd0fb0f16d8b82f763b70a0e4c0f782e Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 12 Jan 2024 15:49:20 +0530 Subject: [PATCH 53/64] feat(contracts/strategies): Add depositLp() in AaveStrategy to facilitate migration --- contracts/strategies/aave/AaveStrategy.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/contracts/strategies/aave/AaveStrategy.sol b/contracts/strategies/aave/AaveStrategy.sol index 82ee96b5..303d8a78 100755 --- a/contracts/strategies/aave/AaveStrategy.sol +++ b/contracts/strategies/aave/AaveStrategy.sol @@ -66,6 +66,19 @@ contract AaveStrategy is InitializableAbstractStrategy { emit Deposit(_asset, _amount); } + /// @notice Deposit the lpToken in the strategy. + /// @param _asset Address of the collateral. + /// @param _amount Amount of token to be added. + /// @dev This function is added to facilitate Migration of funds + /// from old AaveStrategy. + function depositLp(address _asset, uint256 _amount) external onlyVaultOrOwner nonReentrant { + if (!supportsCollateral(_asset)) revert CollateralNotSupported(_asset); + Helpers._isNonZeroAmt(_amount); + allocatedAmount[_asset] += _amount; + IERC20(assetToPToken[_asset]).safeTransferFrom(msg.sender, address(this), _amount); + emit Deposit(_asset, _amount); + } + /// @inheritdoc InitializableAbstractStrategy function withdraw(address _recipient, address _asset, uint256 _amount) external From 1bb5ecc4b13edf18c2bdb8cd50809ac20f1fe3f9 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 12 Jan 2024 17:57:32 +0530 Subject: [PATCH 54/64] refactor(contracts/strategies): Make rewardTokenConfigurable in updateFarm() --- contracts/strategies/stargate/StargateStrategy.sol | 6 +++++- test/strategy/StargateStrategy.t.sol | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/contracts/strategies/stargate/StargateStrategy.sol b/contracts/strategies/stargate/StargateStrategy.sol index 8fa0450c..2b37c572 100755 --- a/contracts/strategies/stargate/StargateStrategy.sol +++ b/contracts/strategies/stargate/StargateStrategy.sol @@ -183,7 +183,10 @@ contract StargateStrategy is InitializableAbstractStrategy { /// @notice A function to withdraw from old farm, update farm and deposit in new farm /// @param _newFarm Address of the new farm /// @dev Only callable by owner - function updateFarm(address _newFarm) external nonReentrant onlyOwner { + /// @dev @note Claim the rewards before calling this function! + function updateFarm(address _newFarm, address _rewardToken) external nonReentrant onlyOwner { + Helpers._isNonZeroAddr(_rewardToken); + Helpers._isNonZeroAddr(_newFarm); address _oldFarm = farm; uint256 _numAssets = assetsMapped.length; address _asset; @@ -201,6 +204,7 @@ contract StargateStrategy is InitializableAbstractStrategy { } } farm = _newFarm; + rewardTokenAddress[0] = _rewardToken; emit FarmUpdated(_newFarm); } diff --git a/test/strategy/StargateStrategy.t.sol b/test/strategy/StargateStrategy.t.sol index 857a3e8a..8c3e95c6 100644 --- a/test/strategy/StargateStrategy.t.sol +++ b/test/strategy/StargateStrategy.t.sol @@ -673,7 +673,7 @@ contract TestUpdateFarm is StargateStrategyTest { function test_revertsWhen_CallerIsNotOwner() public { vm.expectRevert("Ownable: caller is not the owner"); - strategy.updateFarm(_newFarm); + strategy.updateFarm(_newFarm, address(0)); } function test_UpdateFarm() public useKnownActor(USDS_OWNER) { @@ -685,7 +685,7 @@ contract TestUpdateFarm is StargateStrategyTest { } vm.expectEmit(true, true, true, true); emit FarmUpdated(_newFarm); - strategy.updateFarm(_newFarm); + strategy.updateFarm(_newFarm, STARGATE); for (uint8 i = 0; i < assetData.length; ++i) { (newFarmBalances[i],) = ILPStaking(_newFarm).userInfo(assetData[i].rewardPid, address(strategy)); assertEq(oldFarmBalances[i], newFarmBalances[i], "Mismatch in balance"); From 5f3b7fc1847ed687949aeffd3bb39ad0cc5c613e Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 12 Jan 2024 18:43:00 +0530 Subject: [PATCH 55/64] chore(scripts): Add strategy deployment configs --- scripts/configurations.py | 134 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/scripts/configurations.py b/scripts/configurations.py index ab3667ee..a39021f7 100644 --- a/scripts/configurations.py +++ b/scripts/configurations.py @@ -1,6 +1,9 @@ from brownie import ( SPABuyback, VaultCore, + AaveStrategy, + StargateStrategy, + CompoundStrategy, USDs, ) @@ -16,6 +19,7 @@ USDS_ADDR = "0xD74f5255D557944cf7Dd0E45FF521520002D5748" SPA_BUYBACK_ADDR = "0xFbc0d3cA777722d234FE01dba94DeDeDb277AFe3" USDS_OWNER_ADDR = "0x5b12d9846F8612E439730d18E1C12634753B1bF1" +VAULT = "0x6Bbc476Ee35CBA9e9c3A59fc5b10d7a0BC6f74Ca" ## Tokens: SPA = "0x5575552988A3A80504bBaeB1311674fCFd40aD4B" @@ -45,6 +49,136 @@ ], ), ), + "aaveStrategy": Deployment_data( + # @note https://github.com/bgd-labs/aave-address-book/blob/main/src/AaveV3Arbitrum.sol # noqa + contract=AaveStrategy, + config=Deployment_config( + upgradeable=True, + proxy_admin=PROXY_ADMIN, + deployment_params={ + 'platform_addr': '0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb', + 'vault': VAULT + }, + post_deployment_steps=[ + Step( + func="setPTokenAddress", + args={"asset": USDC, "lpToken": "0x724dc807b04555b71ed48a6896b6F41593b8C637"}, + transact=True, + ), + Step( + func="setPTokenAddress", + args={"asset": DAI, "lpToken": "0x82E64f49Ed5EC1bC6e43DAD4FC8Af9bb3A2312EE"}, + transact=True, + ), + Step( + func="setPTokenAddress", + args={"asset": USDC_E, "lpToken": "0x625E7708f30cA75bfd92586e17077590C60eb4cD"}, + transact=True, + ), + Step( + func="setPTokenAddress", + args={"asset": LUSD, "lpToken": "0x8ffDf2DE812095b1D19CB146E4c004587C0A0692"}, + transact=True, + ), + Step( + func="transferOwnership", + args={"new_admin": USDS_OWNER_ADDR}, + transact=True, + ) + ] + ) + ), + "stargateStrategy": Deployment_data( + # @note https://stargateprotocol.gitbook.io/stargate/developers/contract-addresses/mainnet#arbitrum + contract=StargateStrategy, + config=Deployment_config( + upgradeable=True, + proxy_admin=PROXY_ADMIN, + deployment_params={ + 'router': '0x53Bf833A5d6c4ddA888F69c22C88C9f356a41614', + 'vault': VAULT, + 'eToken': '0x912CE59144191C1204E64559FE8253a0e49E6548', + 'farm': '0x9774558534036Ff2E236331546691b4eB70594b1', + 'depositSlippage': 50, + 'withdrawSlippage': 50 + }, + post_deployment_steps=[ + Step( + func="setPTokenAddress", + args={ + "asset": USDC_E, + "lpToken": "0x892785f33CdeE22A30AEF750F285E18c18040c3e", + "pid": 1, + "rewardPid": 0 + }, + transact=True, + ), + Step( + func="setPTokenAddress", + args={ + "asset": USDT, + "lpToken": "0xB6CfcF89a7B22988bfC96632aC2A9D6daB60d641", + "pid": 2, + "rewardPid": 1 + }, + transact=True, + ), + Step( + func="setPTokenAddress", + args={ + "asset": FRAX, + "lpToken": "0xaa4BF442F024820B2C28Cd0FD72b82c63e66F56C", + "pid": 7, + "rewardPid": 3 + }, + transact=True, + ), + Step( + func="transferOwnership", + args={"new_admin": USDS_OWNER_ADDR}, + transact=True, + ) + + ] + + ) + ), + "compoundStrategy": Deployment_data( + # @note https://docs.compound.finance/#networks + contract=CompoundStrategy, + config=Deployment_config( + upgradeable=True, + proxy_admin=PROXY_ADMIN, + deployment_params={ + 'vault': VAULT, + 'rewardPool': '0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae', + }, + post_deployment_steps=[ + Step( + func="setPTokenAddress", + args={ + "asset": USDC, + "lpToken": "0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf", + }, + transact=True, + ), + Step( + func="setPTokenAddress", + args={ + "asset": USDC_E, + "lpToken": "0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA", + }, + transact=True, + ), + Step( + func="transferOwnership", + args={"new_admin": USDS_OWNER_ADDR}, + transact=True, + ) + ] + ) + ) + } upgrade_config = { From 9bced99bf9075649d085fe4a207222042c72494e Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 12 Jan 2024 20:18:57 +0530 Subject: [PATCH 56/64] feat(contracts/utils): Add interfaces to interact with old contracts for migration. --- contracts/utils/MigrationUtils.sol | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 contracts/utils/MigrationUtils.sol diff --git a/contracts/utils/MigrationUtils.sol b/contracts/utils/MigrationUtils.sol new file mode 100644 index 00000000..b1738289 --- /dev/null +++ b/contracts/utils/MigrationUtils.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.19; + +// @dev The below interfaces are only for testing migration. +interface IOldAaveStrategy { + function withdrawLPForMigration(address _asset, uint256 _amount) external; + + function withdrawToVault(address _asset, uint256 _amount) external; + + function checkATokenBalance(address _asset) external view returns (uint256); + + function checkBalance(address _asset) external view returns (uint256); + + function checkAvailableBalance(address _asset) external view returns (uint256); +} + +interface IOldVault { + // @notice Migrates funds to newVault + function migrateFunds(address[] memory _assets, address _newVault) external; + + function updateBuybackAddr(address _newBuyback) external; + + function harvestInterest(address _strategy, address _collateral) external; +} From 63d2158e9246879502d65385834d4c11bc28d8e3 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 12 Jan 2024 20:19:39 +0530 Subject: [PATCH 57/64] docs(deployment-artifacts): Add deployment data for aaveStrategy --- ...ment_aaveStrategy_01-12-2024_18-41-25.json | 148 ++++++++++++++++++ deployed/arbitrum-one/deployment_data.json | 3 +- 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 deployed/arbitrum-one/Deployment_aaveStrategy_01-12-2024_18-41-25.json diff --git a/deployed/arbitrum-one/Deployment_aaveStrategy_01-12-2024_18-41-25.json b/deployed/arbitrum-one/Deployment_aaveStrategy_01-12-2024_18-41-25.json new file mode 100644 index 00000000..3484542a --- /dev/null +++ b/deployed/arbitrum-one/Deployment_aaveStrategy_01-12-2024_18-41-25.json @@ -0,0 +1,148 @@ +{ + "proxy_addr": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388", + "impl_addr": "0xe7762Cad3Aafd3324a299AEeC331C660B74EE78e", + "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25", + "type": "Deployment", + "transactions": [ + { + "step": "Implementation_deployment", + "tx_hash": "0x2c273bfc9bdb01e761e0b49bca2b96c2d12e74da7af350c7f42457278da6d6a8", + "contract": "AaveStrategy", + "contract_addr": "0xe7762Cad3Aafd3324a299AEeC331C660B74EE78e", + "tx_func": "constructor", + "blocknumber": 169716220, + "gas_used": 22740260, + "gas_limit": 27806512 + }, + { + "step": "Proxy_deployment", + "tx_hash": "0xef97947b010c33e2c65098f1d2a38cd9dbf1bf0deb7bf03f00f3afa34a563789", + "contract": "TUP", + "contract_addr": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388", + "tx_func": "constructor", + "blocknumber": 169716243, + "gas_used": 10469517, + "gas_limit": 12911930 + }, + { + "step": "Proxy_initialization", + "tx_hash": "0xa4d8518cc61df2c3a51f9209dba93ad8607d77d6bffdcb6f4fcb32369a148c24", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 169716267, + "gas_used": 909223, + "gas_limit": 1255240 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0x1bb36854eaefd17fecb33c89f4694e8195a1b66c5090476c64d1a7eded347eba", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 169716292, + "gas_used": 868120, + "gas_limit": 1208042 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0x18d5b5cd004e26470de9872aaf95bec36e57130b3c7f08af016084c6a34ae44d", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 169716309, + "gas_used": 850940, + "gas_limit": 1188843 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0xff0131957797048d5926baf1b6e0cea92d9e2510b4da3c53ee52d8158eab43db", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 169716323, + "gas_used": 850964, + "gas_limit": 1188870 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0x2ddb9239b8a63a5fb169fcc27de17f63ed40fa859eb4a011ff494383614ef829", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 169716340, + "gas_used": 851020, + "gas_limit": 1188932 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0x7c4c8a2e29d5d71e8a0f554a0b5481d5065454e536696f37eb0c45eb9c6d5dd7", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 169716356, + "gas_used": 654526, + "gas_limit": 954921 + } + ], + "config_name": "aaveStrategy", + "config": { + "deployment_params": { + "platform_addr": "0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb", + "vault": "0x6Bbc476Ee35CBA9e9c3A59fc5b10d7a0BC6f74Ca" + }, + "post_deployment_steps": [ + { + "func": "setPTokenAddress", + "args": { + "asset": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + "lpToken": "0x724dc807b04555b71ed48a6896b6F41593b8C637" + }, + "transact": true, + "contract": "aaveStrategy", + "contract_addr": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388" + }, + { + "func": "setPTokenAddress", + "args": { + "asset": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", + "lpToken": "0x82E64f49Ed5EC1bC6e43DAD4FC8Af9bb3A2312EE" + }, + "transact": true, + "contract": "aaveStrategy", + "contract_addr": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388" + }, + { + "func": "setPTokenAddress", + "args": { + "asset": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + "lpToken": "0x625E7708f30cA75bfd92586e17077590C60eb4cD" + }, + "transact": true, + "contract": "aaveStrategy", + "contract_addr": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388" + }, + { + "func": "setPTokenAddress", + "args": { + "asset": "0x93b346b6bc2548da6a1e7d98e9a421b42541425b", + "lpToken": "0x8ffDf2DE812095b1D19CB146E4c004587C0A0692" + }, + "transact": true, + "contract": "aaveStrategy", + "contract_addr": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388" + }, + { + "func": "transferOwnership", + "args": { + "new_admin": "0x5b12d9846F8612E439730d18E1C12634753B1bF1" + }, + "transact": true, + "contract": "aaveStrategy", + "contract_addr": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388" + } + ], + "upgradeable": true, + "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25" + } +} \ No newline at end of file diff --git a/deployed/arbitrum-one/deployment_data.json b/deployed/arbitrum-one/deployment_data.json index bda57c3d..21e1bed2 100644 --- a/deployed/arbitrum-one/deployment_data.json +++ b/deployed/arbitrum-one/deployment_data.json @@ -20,5 +20,6 @@ "collateral_manager": "0xdA423BFa1E196598190deEfbAFC28aDb36FaeDF0", "dripper": "0xd50193e8fFb00beA274bD2b11d0a7Ea08dA044c1", "rebase_manager": "0x297331A0155B1e30bBFA85CF3609eC0fF037BEEC", - "yield_reserve": "0xfD14C8ef0993fd9409f7820BA8BA80370529d861" + "yield_reserve": "0xfD14C8ef0993fd9409f7820BA8BA80370529d861", + "aave_strategy": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388" } \ No newline at end of file From 0df55204c26a5e60804f8d2ee49df40b8dd87da0 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 12 Jan 2024 20:20:10 +0530 Subject: [PATCH 58/64] chore(scripts): Update preset script for simulating migration --- scripts/preset.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/scripts/preset.py b/scripts/preset.py index 404822ef..e6978e00 100644 --- a/scripts/preset.py +++ b/scripts/preset.py @@ -1,7 +1,9 @@ from brownie import ( ERC20, + interface, MasterPriceOracle, ChainlinkOracle, + AaveStrategy, USDs, VaultCore, CollateralManager, @@ -23,6 +25,7 @@ DEPLOYMENT_ARTIFACTS = f'deployed/{network.show_active()}/deployment_data.json' MIGRATE = True +USDs_OWNER = '0x5b12d9846F8612E439730d18E1C12634753B1bF1' def main(): owner = get_user('Select deployer ') @@ -32,6 +35,7 @@ def main(): # Deploy all the ERC20 contracts usdc = ERC20.at(data['USDC']) + usdc_e = ERC20.at(data['USDCe']) dai = ERC20.at(data['DAI']) usdt = ERC20.at(data['USDT']) spa = ERC20.at(data['SPA']) @@ -64,7 +68,71 @@ def main(): yield_reserve = Contract.from_abi( 'YieldReserve', data['yield_reserve'], YieldReserve.abi ) + aave_strategy = Contract.from_abi( + 'AaveStrategy', data['aave_strategy'], AaveStrategy.abi + ) + # stargate_strategy = Contract.from_abi( + # 'StargateStrategy', data['stargate_strategy', StargateStrategy.abi] + # ) + # compound_strategy = Contract.from_abi( + # 'CompoundStrategy', data['compound_strategy', CompoundStrategy] + # ) if (MIGRATE): - proxy_admin.upgrade(usds, data['usds_impl'], {'from': proxy_admin.owner()}) + print('Add collateral Strategies') + # @note add USDT to aave strategy + collateral_manager.addCollateralStrategy(usdc, aave_strategy, 5000, {'from': owner}) + collateral_manager.addCollateralStrategy(lusd, aave_strategy, 5000, {'from': owner}) + collateral_manager.addCollateralStrategy(usdc_e, aave_strategy, 5000, {'from': owner}) + collateral_manager.addCollateralStrategy(dai, aave_strategy, 5000, {'from': owner}) + + # Old addresses + old_vault_addr = '0xF783DD830A4650D2A8594423F123250652340E3f' + old_vault_impl = '0xEC399A159cc60bCEd415A58f50B138E5D0bB6f89' + old_aave_impl = '0xB172d61f8682b977Cf0888ce9337C41B50f94910' + # stargate_strategy = Contract.from_abi('old_aave_strategy', '0xF30Db0F56674b51050630e53043c403f8E162Bf2', S.abi) + pa_owner = proxy_admin.owner() + + # GnosisTxn # Upgrade old vault for migration + print('Upgrade essential old contracts') + old_aave_strategy = Contract.from_abi('old_aave_strategy', '0xBC683Dee915313b01dEff10D29342E59e1d75C09', interface.IOldAaveStrategy.abi) + proxy_admin.upgrade(old_vault_addr, old_vault_impl, {'from': pa_owner}) + proxy_admin.upgrade(old_aave_strategy, old_aave_impl, {'from': pa_owner}) + old_vault = Contract.from_abi('old_vault', old_vault_addr, interface.IOldVault.abi) + + # Transfer any collected yield to yield_reserve + print('Harvest Yield from old strategies') + old_vault.updateBuybackAddr(yield_reserve, {'from': USDs_OWNER}) + old_vault.harvestInterest(old_aave_strategy, usdc_e, {'from': USDs_OWNER}) + old_vault.harvestInterest(old_aave_strategy, lusd, {'from': USDs_OWNER}) + old_vault.harvestInterest(old_aave_strategy, dai, {'from': USDs_OWNER}) + + # Migrate the funds + print('Withdraw funds to vault from old strategy') + old_aave_strategy.withdrawToVault(usdc, old_aave_strategy.checkBalance(usdc), {'from': USDs_OWNER}) + old_aave_strategy.withdrawToVault(dai, old_aave_strategy.checkBalance(dai), {'from': USDs_OWNER}) + old_aave_strategy.withdrawToVault(usdc_e, old_aave_strategy.checkBalance(usdc_e), {'from': USDs_OWNER}) + old_aave_strategy.withdrawToVault(lusd, old_aave_strategy.checkAvailableBalance(lusd), {'from': USDs_OWNER}) + + print('Rescue LUSD') + # https://github.com/bgd-labs/aave-address-book/blob/main/src/AaveV3Arbitrum.sol + aLusd = ERC20.at('0x8ffDf2DE812095b1D19CB146E4c004587C0A0692') + old_aave_strategy.withdrawLPForMigration(lusd, 2**256 - 1, {'from': USDs_OWNER}) + aLusd.approve(aave_strategy, 1e24, {'from': USDs_OWNER}) + aave_strategy.depositLp(lusd, aLusd.balanceOf(USDs_OWNER), {'from': USDs_OWNER}) + + print("Migrate funds from old vault") + old_vault.migrateFunds([usdc, dai, lusd, usdc_e, frax], vault, {'from': USDs_OWNER}) + + # Auto yield-reserve + print("Bootstrap dripper with yield reserve") + old_vault.migrateFunds([usds], USDs_OWNER, {'from': USDs_OWNER}) + usds.approve(dripper, usds.balanceOf(USDs_OWNER), {'from': USDs_OWNER}) + dripper.addUSDs(usds.balanceOf(USDs_OWNER), {'from': USDs_OWNER}) + + # GnosisTxn # Upgrade the USDs contract + print("upgrade the new contracts and connect vault") + proxy_admin.upgrade(usds, data['usds_impl'], {'from': pa_owner}) usds.updateVault(vault, {'from': usds.owner()}) + + fee_calculator.calibrateFeeForAll({'from': owner}) \ No newline at end of file From 2afdb1051840363633f230173d41c555cbfa7c01 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Mon, 15 Jan 2024 12:51:26 +0530 Subject: [PATCH 59/64] docs(deployment-artifacts): Add strategy deployment data. --- ..._compoundStrategy_01-15-2024_11-10-46.json | 108 ++++++++++++++ ..._stargateStrategy_01-15-2024_11-09-41.json | 138 ++++++++++++++++++ deployed/arbitrum-one/deployment_data.json | 4 +- 3 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 deployed/arbitrum-one/Deployment_compoundStrategy_01-15-2024_11-10-46.json create mode 100644 deployed/arbitrum-one/Deployment_stargateStrategy_01-15-2024_11-09-41.json diff --git a/deployed/arbitrum-one/Deployment_compoundStrategy_01-15-2024_11-10-46.json b/deployed/arbitrum-one/Deployment_compoundStrategy_01-15-2024_11-10-46.json new file mode 100644 index 00000000..df1df81b --- /dev/null +++ b/deployed/arbitrum-one/Deployment_compoundStrategy_01-15-2024_11-10-46.json @@ -0,0 +1,108 @@ +{ + "proxy_addr": "0xBCeb48625771E35420076f79Ec6921E783a82442", + "impl_addr": "0x63611Fe8E3f96F961Ebd13510Fdc6A4bBedf7163", + "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25", + "type": "Deployment", + "transactions": [ + { + "step": "Implementation_deployment", + "tx_hash": "0x33e2d6c214e29c469c4937fd846438d236b4883eb60a6d9343b889e47055547c", + "contract": "CompoundStrategy", + "contract_addr": "0x63611Fe8E3f96F961Ebd13510Fdc6A4bBedf7163", + "tx_func": "constructor", + "blocknumber": 170601384, + "gas_used": 13789414, + "gas_limit": 16758316 + }, + { + "step": "Proxy_deployment", + "tx_hash": "0x02f1fb8f9192484a05baea05181b3dfd1b0bf537afeec1be2e9994f7866db8f9", + "contract": "TUP", + "contract_addr": "0xBCeb48625771E35420076f79Ec6921E783a82442", + "tx_func": "constructor", + "blocknumber": 170601415, + "gas_used": 5642279, + "gas_limit": 6903602 + }, + { + "step": "Proxy_initialization", + "tx_hash": "0x87335888554620a56e824ac3b4892a117638627d8d10e212583bfa7f22634ae0", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 170601442, + "gas_used": 533136, + "gas_limit": 718232 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0xddaa0c2bed8ce7fbcd86f026a14884e2474d9c48a4777de5df58b77cbd616af1", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 170601460, + "gas_used": 498301, + "gas_limit": 678037 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0x92ae102776abb2b025262d6f9babf5596ed14659bdeee097eaa22e73c9b368af", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 170601475, + "gas_used": 481201, + "gas_limit": 658929 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0x3d9a2fac1d1fef77c8b074eac64406b26b841b1f2093b83c83de1d9bc2ddc30a", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 170601498, + "gas_used": 351654, + "gas_limit": 506798 + } + ], + "config_name": "compoundStrategy", + "config": { + "deployment_params": { + "vault": "0x6Bbc476Ee35CBA9e9c3A59fc5b10d7a0BC6f74Ca", + "rewardPool": "0x88730d254A2f7e6AC8388c3198aFd694bA9f7fae" + }, + "post_deployment_steps": [ + { + "func": "setPTokenAddress", + "args": { + "asset": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", + "lpToken": "0x9c4ec768c28520B50860ea7a15bd7213a9fF58bf" + }, + "transact": true, + "contract": "compoundStrategy", + "contract_addr": "0xBCeb48625771E35420076f79Ec6921E783a82442" + }, + { + "func": "setPTokenAddress", + "args": { + "asset": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + "lpToken": "0xA5EDBDD9646f8dFF606d7448e414884C7d905dCA" + }, + "transact": true, + "contract": "compoundStrategy", + "contract_addr": "0xBCeb48625771E35420076f79Ec6921E783a82442" + }, + { + "func": "transferOwnership", + "args": { + "new_admin": "0x5b12d9846F8612E439730d18E1C12634753B1bF1" + }, + "transact": true, + "contract": "compoundStrategy", + "contract_addr": "0xBCeb48625771E35420076f79Ec6921E783a82442" + } + ], + "upgradeable": true, + "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25" + } +} \ No newline at end of file diff --git a/deployed/arbitrum-one/Deployment_stargateStrategy_01-15-2024_11-09-41.json b/deployed/arbitrum-one/Deployment_stargateStrategy_01-15-2024_11-09-41.json new file mode 100644 index 00000000..75b4a272 --- /dev/null +++ b/deployed/arbitrum-one/Deployment_stargateStrategy_01-15-2024_11-09-41.json @@ -0,0 +1,138 @@ +{ + "proxy_addr": "0xb9C9100720D8c6E35eb8dd0F9C1aBEf320dAA136", + "impl_addr": "0xc61CEFE2fE6F8De61067419c1336e720A802ffea", + "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25", + "type": "Deployment", + "transactions": [ + { + "step": "Implementation_deployment", + "tx_hash": "0x4074feee7e2a697b97a6bb3ba82b2ac39015f1b23179be52e9b29923ea3989b3", + "contract": "StargateStrategy", + "contract_addr": "0xc61CEFE2fE6F8De61067419c1336e720A802ffea", + "tx_func": "constructor", + "blocknumber": 170601120, + "gas_used": 17486028, + "gas_limit": 21098256 + }, + { + "step": "Proxy_deployment", + "tx_hash": "0x4052dfcf153a18b3ed531d1368c68075b530df65cfa2e4b2fd4c645975727ff6", + "contract": "TUP", + "contract_addr": "0xb9C9100720D8c6E35eb8dd0F9C1aBEf320dAA136", + "tx_func": "constructor", + "blocknumber": 170601151, + "gas_used": 5633684, + "gas_limit": 6906202 + }, + { + "step": "Proxy_initialization", + "tx_hash": "0xbebb3d93569c6f5d384ba6cffbfd94d48e4fe1023462a064c1a6844815690b3a", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 170601179, + "gas_used": 878275, + "gas_limit": 1132948 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0x568d4a1b716649c1740a78c0b26b51a5411495f7f15a2ed5a4586d4dea21d188", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 170601198, + "gas_used": 671755, + "gas_limit": 886374 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0x7f2cc1a32f1055416538c057e053aca3433629917e793cf094b4308f8fe8c971", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 170601215, + "gas_used": 674567, + "gas_limit": 889517 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0xfa3f25a3d168ffbf6147193e98e9ac8da73bfb9fc6e322f3960b4f384430419d", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 170601239, + "gas_used": 674555, + "gas_limit": 889504 + }, + { + "step": "Post_deployment_step", + "tx_hash": "0x793742d5fdffca31a1425424840304eb30dcf418173986449f70397819769554", + "contract": "TUP", + "contract_addr": null, + "tx_func": null, + "blocknumber": 170601257, + "gas_used": 351632, + "gas_limit": 506774 + } + ], + "config_name": "stargateStrategy", + "config": { + "deployment_params": { + "router": "0x53Bf833A5d6c4ddA888F69c22C88C9f356a41614", + "vault": "0x6Bbc476Ee35CBA9e9c3A59fc5b10d7a0BC6f74Ca", + "eToken": "0x912CE59144191C1204E64559FE8253a0e49E6548", + "farm": "0x9774558534036Ff2E236331546691b4eB70594b1", + "depositSlippage": 50, + "withdrawSlippage": 50 + }, + "post_deployment_steps": [ + { + "func": "setPTokenAddress", + "args": { + "asset": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + "lpToken": "0x892785f33CdeE22A30AEF750F285E18c18040c3e", + "pid": 1, + "rewardPid": 0 + }, + "transact": true, + "contract": "stargateStrategy", + "contract_addr": "0xb9C9100720D8c6E35eb8dd0F9C1aBEf320dAA136" + }, + { + "func": "setPTokenAddress", + "args": { + "asset": "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", + "lpToken": "0xB6CfcF89a7B22988bfC96632aC2A9D6daB60d641", + "pid": 2, + "rewardPid": 1 + }, + "transact": true, + "contract": "stargateStrategy", + "contract_addr": "0xb9C9100720D8c6E35eb8dd0F9C1aBEf320dAA136" + }, + { + "func": "setPTokenAddress", + "args": { + "asset": "0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F", + "lpToken": "0xaa4BF442F024820B2C28Cd0FD72b82c63e66F56C", + "pid": 7, + "rewardPid": 3 + }, + "transact": true, + "contract": "stargateStrategy", + "contract_addr": "0xb9C9100720D8c6E35eb8dd0F9C1aBEf320dAA136" + }, + { + "func": "transferOwnership", + "args": { + "new_admin": "0x5b12d9846F8612E439730d18E1C12634753B1bF1" + }, + "transact": true, + "contract": "stargateStrategy", + "contract_addr": "0xb9C9100720D8c6E35eb8dd0F9C1aBEf320dAA136" + } + ], + "upgradeable": true, + "proxy_admin": "0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25" + } +} \ No newline at end of file diff --git a/deployed/arbitrum-one/deployment_data.json b/deployed/arbitrum-one/deployment_data.json index 21e1bed2..33d1ad04 100644 --- a/deployed/arbitrum-one/deployment_data.json +++ b/deployed/arbitrum-one/deployment_data.json @@ -21,5 +21,7 @@ "dripper": "0xd50193e8fFb00beA274bD2b11d0a7Ea08dA044c1", "rebase_manager": "0x297331A0155B1e30bBFA85CF3609eC0fF037BEEC", "yield_reserve": "0xfD14C8ef0993fd9409f7820BA8BA80370529d861", - "aave_strategy": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388" + "aave_strategy": "0x974993eE8DF7F5C4F3f9Aa4eB5b4534F359f3388", + "stargate_strategy": "0xb9C9100720D8c6E35eb8dd0F9C1aBEf320dAA136", + "compound_strategy": "0xBCeb48625771E35420076f79Ec6921E783a82442" } \ No newline at end of file From 3ef3bd9b1ef39310ad781e7a55958c77c7587054 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Mon, 15 Jan 2024 18:57:11 +0530 Subject: [PATCH 60/64] refactor(scripts): Remove unnecessary steps post migration --- scripts/deploy_vault.py | 7 ---- scripts/preset.py | 74 +++++------------------------------------ 2 files changed, 8 insertions(+), 73 deletions(-) diff --git a/scripts/deploy_vault.py b/scripts/deploy_vault.py index 66aa9a8e..0f1ae4a4 100644 --- a/scripts/deploy_vault.py +++ b/scripts/deploy_vault.py @@ -237,13 +237,6 @@ def main(): {'from': owner}, ) - # vault.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) - # collateral_manager.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) - # yield_reserve.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) - # dripper.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) - # rebase_manager.transferOwnership(USDS_OWNER_ADDR, {'from': owner}) - - data = { **data, 'proxy_admin': proxy_admin.address, diff --git a/scripts/preset.py b/scripts/preset.py index e6978e00..835004f8 100644 --- a/scripts/preset.py +++ b/scripts/preset.py @@ -1,9 +1,10 @@ from brownie import ( ERC20, - interface, MasterPriceOracle, ChainlinkOracle, AaveStrategy, + StargateStrategy, + CompoundStrategy, USDs, VaultCore, CollateralManager, @@ -71,68 +72,9 @@ def main(): aave_strategy = Contract.from_abi( 'AaveStrategy', data['aave_strategy'], AaveStrategy.abi ) - # stargate_strategy = Contract.from_abi( - # 'StargateStrategy', data['stargate_strategy', StargateStrategy.abi] - # ) - # compound_strategy = Contract.from_abi( - # 'CompoundStrategy', data['compound_strategy', CompoundStrategy] - # ) - - if (MIGRATE): - print('Add collateral Strategies') - # @note add USDT to aave strategy - collateral_manager.addCollateralStrategy(usdc, aave_strategy, 5000, {'from': owner}) - collateral_manager.addCollateralStrategy(lusd, aave_strategy, 5000, {'from': owner}) - collateral_manager.addCollateralStrategy(usdc_e, aave_strategy, 5000, {'from': owner}) - collateral_manager.addCollateralStrategy(dai, aave_strategy, 5000, {'from': owner}) - - # Old addresses - old_vault_addr = '0xF783DD830A4650D2A8594423F123250652340E3f' - old_vault_impl = '0xEC399A159cc60bCEd415A58f50B138E5D0bB6f89' - old_aave_impl = '0xB172d61f8682b977Cf0888ce9337C41B50f94910' - # stargate_strategy = Contract.from_abi('old_aave_strategy', '0xF30Db0F56674b51050630e53043c403f8E162Bf2', S.abi) - pa_owner = proxy_admin.owner() - - # GnosisTxn # Upgrade old vault for migration - print('Upgrade essential old contracts') - old_aave_strategy = Contract.from_abi('old_aave_strategy', '0xBC683Dee915313b01dEff10D29342E59e1d75C09', interface.IOldAaveStrategy.abi) - proxy_admin.upgrade(old_vault_addr, old_vault_impl, {'from': pa_owner}) - proxy_admin.upgrade(old_aave_strategy, old_aave_impl, {'from': pa_owner}) - old_vault = Contract.from_abi('old_vault', old_vault_addr, interface.IOldVault.abi) - - # Transfer any collected yield to yield_reserve - print('Harvest Yield from old strategies') - old_vault.updateBuybackAddr(yield_reserve, {'from': USDs_OWNER}) - old_vault.harvestInterest(old_aave_strategy, usdc_e, {'from': USDs_OWNER}) - old_vault.harvestInterest(old_aave_strategy, lusd, {'from': USDs_OWNER}) - old_vault.harvestInterest(old_aave_strategy, dai, {'from': USDs_OWNER}) - - # Migrate the funds - print('Withdraw funds to vault from old strategy') - old_aave_strategy.withdrawToVault(usdc, old_aave_strategy.checkBalance(usdc), {'from': USDs_OWNER}) - old_aave_strategy.withdrawToVault(dai, old_aave_strategy.checkBalance(dai), {'from': USDs_OWNER}) - old_aave_strategy.withdrawToVault(usdc_e, old_aave_strategy.checkBalance(usdc_e), {'from': USDs_OWNER}) - old_aave_strategy.withdrawToVault(lusd, old_aave_strategy.checkAvailableBalance(lusd), {'from': USDs_OWNER}) - - print('Rescue LUSD') - # https://github.com/bgd-labs/aave-address-book/blob/main/src/AaveV3Arbitrum.sol - aLusd = ERC20.at('0x8ffDf2DE812095b1D19CB146E4c004587C0A0692') - old_aave_strategy.withdrawLPForMigration(lusd, 2**256 - 1, {'from': USDs_OWNER}) - aLusd.approve(aave_strategy, 1e24, {'from': USDs_OWNER}) - aave_strategy.depositLp(lusd, aLusd.balanceOf(USDs_OWNER), {'from': USDs_OWNER}) - - print("Migrate funds from old vault") - old_vault.migrateFunds([usdc, dai, lusd, usdc_e, frax], vault, {'from': USDs_OWNER}) - - # Auto yield-reserve - print("Bootstrap dripper with yield reserve") - old_vault.migrateFunds([usds], USDs_OWNER, {'from': USDs_OWNER}) - usds.approve(dripper, usds.balanceOf(USDs_OWNER), {'from': USDs_OWNER}) - dripper.addUSDs(usds.balanceOf(USDs_OWNER), {'from': USDs_OWNER}) - - # GnosisTxn # Upgrade the USDs contract - print("upgrade the new contracts and connect vault") - proxy_admin.upgrade(usds, data['usds_impl'], {'from': pa_owner}) - usds.updateVault(vault, {'from': usds.owner()}) - - fee_calculator.calibrateFeeForAll({'from': owner}) \ No newline at end of file + stargate_strategy = Contract.from_abi( + 'StargateStrategy', data['stargate_strategy'], StargateStrategy.abi + ) + compound_strategy = Contract.from_abi( + 'CompoundStrategy', data['compound_strategy'], CompoundStrategy.abi + ) \ No newline at end of file From 29760f74cba25c8ba8f51429520ce07a973bfcdb Mon Sep 17 00:00:00 2001 From: Parv Garg Date: Thu, 18 Jan 2024 19:59:21 +0530 Subject: [PATCH 61/64] Fix/Improve Tests (#59) * chore(test): Improved MasterPriceOracle tests - Refactor tests into separate contracts - Made constant variables for all manual variables - Made event emit checks more robust * test(buyback/SPABuyback): Fix test naming convention and generalize test * improved naming convention and event testing in test files * test cases improved * improved stargate testing * improved FeeCalculator testing * chore(test): Improved USDs tests - Refactor tests into separate contracts - Made constant variables for all manual variables - Made event emit checks more robust * chore(test): Fix failing USDs tests - Fix fuzz range * improved test cases * update * fixed stargate testing * chore(VaultCore.t): Add fee related tests * chore(Dripper.t): Improve tests * standarfized test function names * fixed test function names * chore(Dripper.t): Fix tests * test(VaultCore): Fix redemption from strategy tests * improved variable names in VaultCore.t.sol * chore(USDs.t): Fix failing USDs tests - Fix fuzz range * chore(Dripper.t): Add additional test for modifier code * chore(RebaseManager.t): Fix/add tests * chore(Dripper.t): Fix test for modified code * fixed test_burn_rebasing test case in USDs.t.sol * refactor(tests): Update old addresses and other fixes * added depositLp test cases for aave * improved compound strategy coverage and test cases * update * refactor(tests): Improve tests and coverage * refactor(tests): Consistent use of `RevertWhen` * refactor(USDs.t): Remove TODOs * refactor(AaveStrategy.t): reduce redundancy and improve tests --------- Co-authored-by: YashP16 Co-authored-by: TechnoGeek01 Co-authored-by: mehultuteja --- test/buyback/SPABuyback.t.sol | 154 ++++++---- test/buyback/YieldReserve.t.sol | 207 +++++++++---- test/oracle/ChainlinkOracle.t.sol | 18 +- test/oracle/MasterPriceOracle.t.sol | 188 +++++++----- test/oracle/SPAOracle.t.sol | 55 +++- test/rebase/Dripper.t.sol | 175 ++++++++--- test/rebase/RebaseManager.t.sol | 152 +++++++--- test/strategy/AaveStrategy.t.sol | 73 ++++- test/strategy/CompoundStrategy.t.sol | 84 +++++- test/strategy/StargateStrategy.t.sol | 140 +++++++-- test/token/USDs.t.sol | 415 +++++++++++++++++++-------- test/utils/BaseTest.sol | 10 +- test/utils/DeploymentSetup.sol | 9 +- test/vault/CollateralManager.t.sol | 44 +-- test/vault/FeeCalculator.t.sol | 20 +- test/vault/VaultCore.t.sol | 328 +++++++++++++-------- 16 files changed, 1465 insertions(+), 607 deletions(-) diff --git a/test/buyback/SPABuyback.t.sol b/test/buyback/SPABuyback.t.sol index 22c6d6cc..214a9697 100644 --- a/test/buyback/SPABuyback.t.sol +++ b/test/buyback/SPABuyback.t.sol @@ -9,6 +9,7 @@ import {IOracle} from "../../contracts/interfaces/IOracle.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IveSPARewarder} from "../../contracts/interfaces/IveSPARewarder.sol"; import {IUSDs} from "../../contracts/interfaces/IUSDs.sol"; +import {USDs} from "../../contracts/token/USDs.sol"; contract SPABuybackTestSetup is BaseTest { SPABuyback internal spaBuyback; @@ -68,11 +69,11 @@ contract SPABuybackTestSetup is BaseTest { } } -contract TestInit is SPABuybackTestSetup { +contract Test_Init is SPABuybackTestSetup { SPABuyback private _spaBuybackImpl; SPABuyback private _spaBuyback; - function testInitialize() public { + function test_initialize() public { address _proxy; vm.startPrank(USDS_OWNER); _spaBuybackImpl = new SPABuyback(); @@ -83,25 +84,24 @@ contract TestInit is SPABuybackTestSetup { vm.stopPrank(); } - function testCannotInitializeTwice() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_alreadyInitialized() public useKnownActor(USDS_OWNER) { vm.expectRevert("Initializable: contract is already initialized"); spaBuyback.initialize(VESPA_REWARDER, rewardPercentage); } - function testCannotInitializeImplementation() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_initializingImplementation() public useKnownActor(USDS_OWNER) { vm.expectRevert("Initializable: contract is already initialized"); spaBuybackImpl.initialize(VESPA_REWARDER, rewardPercentage); } - function testInit() public { + function test_initParams() public { assertEq(spaBuyback.veSpaRewarder(), VESPA_REWARDER); assertEq(spaBuyback.rewardPercentage(), rewardPercentage); } } -contract TestGetters is SPABuybackTestSetup { +contract Test_GetUSDsOutForSpa is SPABuybackTestSetup { uint256 private usdsAmount; - uint256 private spaReqd; function setUp() public override { super.setUp(); @@ -109,78 +109,81 @@ contract TestGetters is SPABuybackTestSetup { spaIn = 1e23; } - function testGetSpaReqdForUSDs() public mockOracle { - uint256 calculatedSpaReqd = _calculateSpaReqdForUSDs(usdsAmount); - uint256 spaReqdByContract = spaBuyback.getSPAReqdForUSDs(usdsAmount); - assertEq(calculatedSpaReqd, spaReqdByContract); - } - - function testGetUsdsOutForSpa() public mockOracle { + function test_GetUsdsOutForSpa() public mockOracle { uint256 calculateUSDsOut = _calculateUSDsForSpaIn(spaIn); uint256 usdsOutByContract = spaBuyback.getUsdsOutForSpa(spaIn); assertEq(calculateUSDsOut, usdsOutByContract); } - function testCannotIfInvalidAmount() public mockOracle { + function test_RevertWhen_invalidAmount() public mockOracle { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAmount.selector)); spaBuyback.getUsdsOutForSpa(0); } } -contract TestSetters is SPABuybackTestSetup { +contract Test_GetSPAReqdForUSDs is SPABuybackTestSetup { + uint256 private usdsAmount; + + function setUp() public override { + super.setUp(); + usdsAmount = 1e20; + spaIn = 1e23; + } + + function test_getSPAReqdForUSDs() public mockOracle { + uint256 calculatedSpaReqd = _calculateSpaReqdForUSDs(usdsAmount); + uint256 spaReqdByContract = spaBuyback.getSPAReqdForUSDs(usdsAmount); + assertEq(calculatedSpaReqd, spaReqdByContract); + } + + function test_RevertWhen_invalidAmount() public mockOracle { + vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAmount.selector)); + spaBuyback.getSPAReqdForUSDs(0); + } +} + +contract Test_UpdateRewardPercentage is SPABuybackTestSetup { event RewardPercentageUpdated(uint256 newRewardPercentage); - event VeSpaRewarderUpdated(address newVeSpaRewarder); - event OracleUpdated(address newOracle); - function testCannotIfCallerNotOwner() external useActor(0) { + function test_RevertWhen_callerNotOwner() external useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); spaBuyback.updateRewardPercentage(9000); - vm.expectRevert("Ownable: caller is not the owner"); - spaBuyback.updateVeSpaRewarder(actors[0]); - vm.expectRevert("Ownable: caller is not the owner"); - spaBuyback.updateOracle(actors[0]); } // function updateRewardPercentage - function testCannotIfPercentageIsZero() external useKnownActor(USDS_OWNER) { + function test_RevertWhen_percentageIsZero() external useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAmount.selector)); spaBuyback.updateRewardPercentage(0); } - function testCannotIfPercentageMoreThanMax() external useKnownActor(USDS_OWNER) { + function test_RevertWhen_percentageMoreThanMax() external useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.GTMaxPercentage.selector, 10001)); spaBuyback.updateRewardPercentage(10001); } - function testUpdateRewardPercentage() external useKnownActor(USDS_OWNER) { + function test_updateRewardPercentage() external useKnownActor(USDS_OWNER) { uint256 newRewardPercentage = 8000; vm.expectEmit(true, true, true, true, address(spaBuyback)); emit RewardPercentageUpdated(newRewardPercentage); spaBuyback.updateRewardPercentage(8000); assertEq(spaBuyback.rewardPercentage(), newRewardPercentage); } +} - // function updateVeSpaRewarder - function testCannotIfInvalidAddress() external useKnownActor(USDS_OWNER) { - vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); - spaBuyback.updateVeSpaRewarder(address(0)); - } +contract Test_UpdateOracle is SPABuybackTestSetup { + event OracleUpdated(address newOracle); - function testUpdateVeSpaRewarder() external useKnownActor(USDS_OWNER) { - address newRewarder = actors[1]; - vm.expectEmit(true, true, true, true, address(spaBuyback)); - emit VeSpaRewarderUpdated(newRewarder); - spaBuyback.updateVeSpaRewarder(newRewarder); - assertEq(spaBuyback.veSpaRewarder(), newRewarder); + function test_RevertWhen_callerNotOwner() external useActor(0) { + vm.expectRevert("Ownable: caller is not the owner"); + spaBuyback.updateOracle(actors[0]); } - // function updateOracle - function testCannotIfInvalidAddressOracle() external useKnownActor(USDS_OWNER) { + function test_RevertWhen_invalidOracleAddress() external useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); spaBuyback.updateOracle(address(0)); } - function testUpdateOracle() external useKnownActor(USDS_OWNER) { + function test_updateOracle() external useKnownActor(USDS_OWNER) { address newOracle = actors[1]; vm.expectEmit(true, true, true, true, address(spaBuyback)); emit OracleUpdated(newOracle); @@ -189,7 +192,29 @@ contract TestSetters is SPABuybackTestSetup { } } -contract TestWithdraw is SPABuybackTestSetup { +contract Test_UpdateRewarder is SPABuybackTestSetup { + event VeSpaRewarderUpdated(address newVeSpaRewarder); + + function test_RevertWhen_callerNotOwner() external useActor(0) { + vm.expectRevert("Ownable: caller is not the owner"); + spaBuyback.updateVeSpaRewarder(actors[0]); + } + + function test_RevertWhen_invalidRewarderAddress() external useKnownActor(USDS_OWNER) { + vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); + spaBuyback.updateVeSpaRewarder(address(0)); + } + + function test_updateVeSpaRewarder() external useKnownActor(USDS_OWNER) { + address newRewarder = actors[1]; + vm.expectEmit(true, true, true, true, address(spaBuyback)); + emit VeSpaRewarderUpdated(newRewarder); + spaBuyback.updateVeSpaRewarder(newRewarder); + assertEq(spaBuyback.veSpaRewarder(), newRewarder); + } +} + +contract Test_Withdraw is SPABuybackTestSetup { address private token; uint256 private amount; @@ -204,25 +229,25 @@ contract TestWithdraw is SPABuybackTestSetup { IUSDs(USDS).mint(address(spaBuyback), amount); } - function testCannotIfCallerNotOwner() public useActor(0) { + function test_RevertWhen_CallerNotOwner() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); spaBuyback.withdraw(token, user, amount); } - function testCannotWithdrawSPA() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_withdrawSPA() public useKnownActor(USDS_OWNER) { token = SPA; vm.expectRevert(abi.encodeWithSelector(SPABuyback.CannotWithdrawSPA.selector)); spaBuyback.withdraw(token, user, amount); } - function testCannotWithdrawMoreThanBalance() public useKnownActor(USDS_OWNER) { - amount = IERC20(USDS).balanceOf(address(spaBuyback)); - amount = amount + 1e20; - vm.expectRevert("Transfer greater than balance"); + function test_RevertWhen_withdrawMoreThanBalance() public useKnownActor(USDS_OWNER) { + uint256 balance = IERC20(USDS).balanceOf(address(spaBuyback)); + amount = balance + 1e20; + vm.expectRevert(abi.encodeWithSelector(USDs.TransferGreaterThanBal.selector, amount, balance)); spaBuyback.withdraw(token, user, amount); } - function testWithdraw() public useKnownActor(USDS_OWNER) { + function test_withdraw() public useKnownActor(USDS_OWNER) { uint256 balBefore = IERC20(USDS).balanceOf(user); vm.expectEmit(true, true, true, true, address(spaBuyback)); emit Withdrawn(token, user, amount); @@ -232,14 +257,14 @@ contract TestWithdraw is SPABuybackTestSetup { } } -contract TestBuyUSDs is SPABuybackTestSetup { +contract Test_BuyUSDs is SPABuybackTestSetup { struct BalComparison { uint256 balBefore; uint256 balAfter; } BalComparison private spaTotalSupply; - BalComparison private spaBal; + BalComparison private rewarderSPABal; event BoughtBack( address indexed receiverOfUSDs, @@ -258,53 +283,58 @@ contract TestBuyUSDs is SPABuybackTestSetup { minUSDsOut = 1; } - function testCannotIfSpaAmountTooLow() public mockOracle { + function test_RevertWhen_SpaAmountTooLow() public mockOracle { spaIn = 100; vm.expectRevert(abi.encodeWithSelector(Helpers.CustomError.selector, "SPA Amount too low")); spaBuyback.buyUSDs(spaIn, minUSDsOut); } - function testCannotIfSlippageMoreThanExpected() public mockOracle { + function test_RevertWhen_SlippageMoreThanExpected() public mockOracle { minUSDsOut = spaBuyback.getUsdsOutForSpa(spaIn) + 1e20; vm.expectRevert(abi.encodeWithSelector(Helpers.MinSlippageError.selector, minUSDsOut - 1e20, minUSDsOut)); spaBuyback.buyUSDs(spaIn, minUSDsOut); } - function testCannotIfInsufficientUSDsBalance() public mockOracle { + function test_RevertWhen_InsufficientUSDsBalance() public mockOracle { minUSDsOut = spaBuyback.getUsdsOutForSpa(spaIn); vm.expectRevert(abi.encodeWithSelector(SPABuyback.InsufficientUSDsBalance.selector, minUSDsOut, 0)); spaBuyback.buyUSDs(spaIn, minUSDsOut); } - function testBuyUSDs() public mockOracle { + function test_buyUSDs() public mockOracle { minUSDsOut = _calculateUSDsForSpaIn(spaIn); vm.prank(VAULT); IUSDs(USDS).mint(address(spaBuyback), minUSDsOut + 1e19); spaTotalSupply.balBefore = IERC20(SPA).totalSupply(); - spaBal.balBefore = IERC20(SPA).balanceOf(VESPA_REWARDER); + rewarderSPABal.balBefore = IERC20(SPA).balanceOf(VESPA_REWARDER); deal(SPA, user, spaIn); vm.startPrank(user); IERC20(SPA).approve(address(spaBuyback), spaIn); spaData = IOracle(ORACLE).getPrice(SPA); + + // Calculate SPA to be distributed and burnt + uint256 spaRewarded = spaIn * spaBuyback.rewardPercentage() / Helpers.MAX_PERCENTAGE; + uint256 spaBurnt = spaIn - spaRewarded; + uint256 initialRewards = IveSPARewarder(VESPA_REWARDER).rewardsPerWeek(_getWeek(1), SPA); vm.expectEmit(true, true, false, true, address(spaBuyback)); emit BoughtBack(user, user, spaData.price, spaIn, minUSDsOut); vm.expectEmit(true, true, true, true, address(spaBuyback)); - emit SPARewarded(spaIn / 2); + emit SPARewarded(spaRewarded); vm.expectEmit(true, true, true, true, address(spaBuyback)); - emit SPABurned(spaIn / 2); + emit SPABurned(spaBurnt); spaBuyback.buyUSDs(spaIn, minUSDsOut); vm.stopPrank(); uint256 rewardsAfter = IveSPARewarder(VESPA_REWARDER).rewardsPerWeek(_getWeek(1), SPA); spaTotalSupply.balAfter = IERC20(SPA).totalSupply(); - spaBal.balAfter = IERC20(SPA).balanceOf(VESPA_REWARDER); - assertEq(spaBal.balAfter - spaBal.balBefore, spaIn / 2); - assertEq(initialRewards + (spaIn / 2), rewardsAfter); - assertEq(spaTotalSupply.balBefore - spaTotalSupply.balAfter, spaIn / 2); + rewarderSPABal.balAfter = IERC20(SPA).balanceOf(VESPA_REWARDER); + assertEq(rewarderSPABal.balAfter - rewarderSPABal.balBefore, spaRewarded); + assertEq(initialRewards + (spaRewarded), rewardsAfter); + assertEq(spaTotalSupply.balBefore - spaTotalSupply.balAfter, spaBurnt); } // Testing with fuzzing - function testBuyUSDs(uint256 spaIn, uint256 spaPrice, uint256 usdsPrice) public { + function test_buyUSDs(uint256 spaIn, uint256 spaPrice, uint256 usdsPrice) public { usdsPrice = bound(usdsPrice, 7e17, 13e17); spaPrice = bound(spaPrice, 1e15, 1e20); spaIn = bound(spaIn, 1e18, 1e27); diff --git a/test/buyback/YieldReserve.t.sol b/test/buyback/YieldReserve.t.sol index ac285977..ceaeb6bc 100644 --- a/test/buyback/YieldReserve.t.sol +++ b/test/buyback/YieldReserve.t.sol @@ -8,7 +8,7 @@ import {VaultCore} from "../../contracts/vault/VaultCore.sol"; import {IERC20, ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {PreMigrationSetup} from "../utils/DeploymentSetup.sol"; -contract YieldReserveSetup is PreMigrationSetup { +contract YieldReserveTest is PreMigrationSetup { YieldReserve internal yieldReserve; IVault internal vault; uint256 USDCePrecision; @@ -25,7 +25,7 @@ contract YieldReserveSetup is PreMigrationSetup { SPAPrecision = 10 ** ERC20(SPA).decimals(); vm.startPrank(USDS_OWNER); - yieldReserve = new YieldReserve(BUYBACK, VAULT, ORACLE, DRIPPER); + yieldReserve = new YieldReserve(SPA_BUYBACK, VAULT, ORACLE, DRIPPER); vm.stopPrank(); } @@ -45,199 +45,280 @@ contract YieldReserveSetup is PreMigrationSetup { } } -contract YieldReserveTest is YieldReserveSetup { - function test_toggleSrcTokenPermission_auth_error() public useActor(0) { - vm.expectRevert("Ownable: caller is not the owner"); - yieldReserve.toggleSrcTokenPermission(SPA, true); - } +contract ToggleSrcTokenPermissionTest is YieldReserveTest { + event SrcTokenPermissionUpdated(address indexed token, bool isAllowed); - function test_toggleSrcTokenPermission() public useKnownActor(USDS_OWNER) { + function setUp() public override { + super.setUp(); mockPrice(SPA, 10, SPAPrecision); + } + function test_RevertWhen_callerNotOwner() public useActor(0) { + vm.expectRevert("Ownable: caller is not the owner"); yieldReserve.toggleSrcTokenPermission(SPA, true); - YieldReserve.TokenData memory tokenData = getTokenData(SPA); - assertTrue(tokenData.srcAllowed); - assertEq(tokenData.conversionFactor, 1); - - assertTrue(IOracle(ORACLE).priceFeedExists(SPA)); + } + function test_RevertWhen_AlreadyInDesiredState() public useKnownActor(USDS_OWNER) { + yieldReserve.toggleSrcTokenPermission(SPA, true); vm.expectRevert(abi.encodeWithSelector(YieldReserve.AlreadyInDesiredState.selector)); yieldReserve.toggleSrcTokenPermission(SPA, true); + } - // toggle to false case + function test_ToggleSrcTokenFalse() public useKnownActor(USDS_OWNER) { + yieldReserve.toggleSrcTokenPermission(SPA, true); + vm.expectEmit(true, true, true, true, address(yieldReserve)); + emit SrcTokenPermissionUpdated(SPA, false); yieldReserve.toggleSrcTokenPermission(SPA, false); - - tokenData = getTokenData(SPA); + YieldReserve.TokenData memory tokenData = getTokenData(SPA); assertFalse(tokenData.srcAllowed); + assertEq(tokenData.conversionFactor, 1); + } - vm.expectRevert(abi.encodeWithSelector(YieldReserve.AlreadyInDesiredState.selector)); - yieldReserve.toggleSrcTokenPermission(SPA, false); - - // priceFeed doesn't exist case. + function test_RevertWhen_priceFeeDoesNotExist() public useKnownActor(USDS_OWNER) { address randomTokenAddress = address(0x9); vm.expectRevert(abi.encodeWithSelector(YieldReserve.TokenPriceFeedMissing.selector)); yieldReserve.toggleSrcTokenPermission(randomTokenAddress, true); } - function test_toggleDstTokenPermission_auth_error() public useActor(0) { - vm.expectRevert("Ownable: caller is not the owner"); - yieldReserve.toggleDstTokenPermission(SPA, true); + function test_toggleSrcTokenPermission() public useKnownActor(USDS_OWNER) { + assertTrue(IOracle(ORACLE).priceFeedExists(SPA)); + vm.expectEmit(true, true, true, true, address(yieldReserve)); + emit SrcTokenPermissionUpdated(SPA, true); + yieldReserve.toggleSrcTokenPermission(SPA, true); + YieldReserve.TokenData memory tokenData = getTokenData(SPA); + assertTrue(tokenData.srcAllowed); + assertEq(tokenData.conversionFactor, 1); } +} - function test_toggleDstTokenPermission() public useKnownActor(USDS_OWNER) { +contract ToggleDstTokenPermissionTest is YieldReserveTest { + event DstTokenPermissionUpdated(address indexed token, bool isAllowed); + + function setUp() public override { + super.setUp(); mockPrice(SPA, 10, SPAPrecision); + } + function test_RevertWhen_callerNotOwner() public useActor(0) { + vm.expectRevert("Ownable: caller is not the owner"); yieldReserve.toggleDstTokenPermission(SPA, true); + } - YieldReserve.TokenData memory tokenData = getTokenData(SPA); - assertTrue(tokenData.dstAllowed); - assertEq(tokenData.conversionFactor, 1); - - assertTrue(IOracle(ORACLE).priceFeedExists(SPA)); - + function test_RevertWhen_AlreadyInDesiredState() public useKnownActor(USDS_OWNER) { + yieldReserve.toggleDstTokenPermission(SPA, true); vm.expectRevert(abi.encodeWithSelector(YieldReserve.AlreadyInDesiredState.selector)); yieldReserve.toggleDstTokenPermission(SPA, true); + } - // toggle to false case + function test_ToggleDstTokenFalse() public useKnownActor(USDS_OWNER) { + yieldReserve.toggleDstTokenPermission(SPA, true); + vm.expectEmit(true, true, true, true, address(yieldReserve)); + emit DstTokenPermissionUpdated(SPA, false); yieldReserve.toggleDstTokenPermission(SPA, false); - - tokenData = getTokenData(SPA); + YieldReserve.TokenData memory tokenData = getTokenData(SPA); assertFalse(tokenData.dstAllowed); + assertEq(tokenData.conversionFactor, 1); + } - vm.expectRevert(abi.encodeWithSelector(YieldReserve.AlreadyInDesiredState.selector)); - yieldReserve.toggleSrcTokenPermission(SPA, false); - - // priceFeed doesn't exist case. + function test_RevertWhen_priceFeeDoesNotExist() public useKnownActor(USDS_OWNER) { address randomTokenAddress = address(0x9); vm.expectRevert(abi.encodeWithSelector(YieldReserve.TokenPriceFeedMissing.selector)); yieldReserve.toggleDstTokenPermission(randomTokenAddress, true); } - function test_withdraw_auth_error() public useActor(0) { + function test_toggleDstTokenPermission() public useKnownActor(USDS_OWNER) { + assertTrue(IOracle(ORACLE).priceFeedExists(SPA)); + vm.expectEmit(true, true, true, true, address(yieldReserve)); + emit DstTokenPermissionUpdated(SPA, true); + yieldReserve.toggleDstTokenPermission(SPA, true); + YieldReserve.TokenData memory tokenData = getTokenData(SPA); + assertTrue(tokenData.dstAllowed); + assertEq(tokenData.conversionFactor, 1); + } +} + +contract WithdrawTest is YieldReserveTest { + address token; + address receiver; + uint256 amount; + + function setUp() public override { + super.setUp(); + token = SPA; + receiver = USDS_OWNER; + amount = 1e21; + } + + function test_RevertWhen_callerNotOwner() public { vm.expectRevert("Ownable: caller is not the owner"); - yieldReserve.withdraw(SPA, USDS_OWNER, 10); + yieldReserve.withdraw(token, receiver, amount); } - function test_withdraw_inputs() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_invalidToken() public useKnownActor(USDS_OWNER) { vm.expectRevert("Address: call to non-contract"); - yieldReserve.withdraw(address(0), USDS_OWNER, 10); + yieldReserve.withdraw(address(0), receiver, amount); + } + function test_RevertWhen_invalidReceiver() public useKnownActor(USDS_OWNER) { vm.expectRevert("ERC20: transfer to the zero address"); - yieldReserve.withdraw(SPA, address(0), 10); + yieldReserve.withdraw(token, address(0), amount); + } + function test_RevertWhen_invalidAmount() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAmount.selector)); - yieldReserve.withdraw(SPA, USDS_OWNER, 0); + yieldReserve.withdraw(token, receiver, 0); } function test_withdraw() public useKnownActor(USDS_OWNER) { - uint256 inputBal = 10000; - deal(address(SPA), address(yieldReserve), inputBal * SPAPrecision); + deal(address(SPA), address(yieldReserve), amount); uint256 initialBal = IERC20(SPA).balanceOf(USDS_OWNER); - yieldReserve.withdraw(SPA, USDS_OWNER, inputBal); + yieldReserve.withdraw(SPA, USDS_OWNER, amount); uint256 newBal = IERC20(SPA).balanceOf(USDS_OWNER); - assertEq(inputBal + initialBal, newBal); + assertEq(amount + initialBal, newBal); } +} + +contract UpdateBuybackPercentageTest is YieldReserveTest { + event BuybackPercentageUpdated(uint256 toBuyback); - function test_updateBuybackPercentage_auth_error() public useActor(0) { + function test_RevertWhen_callerNotOwner() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); yieldReserve.updateBuybackPercentage(2000); } - function test_updateBuybackPercentage_inputs() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_percentageGTMax() public useKnownActor(USDS_OWNER) { uint256 buybackPercentage = 10001; vm.expectRevert(abi.encodeWithSelector(Helpers.GTMaxPercentage.selector, buybackPercentage)); yieldReserve.updateBuybackPercentage(buybackPercentage); + } - buybackPercentage = 0; + function test_RevertWhen_percentageIsZero() public useKnownActor(USDS_OWNER) { + uint256 buybackPercentage = 0; vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAmount.selector)); yieldReserve.updateBuybackPercentage(buybackPercentage); } function test_updateBuybackPercentage() public useKnownActor(USDS_OWNER) { uint256 buybackPercentage = 2000; + vm.expectEmit(address(yieldReserve)); + emit BuybackPercentageUpdated(buybackPercentage); yieldReserve.updateBuybackPercentage(buybackPercentage); assertEq(yieldReserve.buybackPercentage(), buybackPercentage); } +} + +contract UpdateBuybackAddressTest is YieldReserveTest { + event BuybackUpdated(address newBuyback); - function test_updateBuybackAddress_auth_error() public useActor(0) { + function test_RevertWhen_callerNotOwner() public { vm.expectRevert("Ownable: caller is not the owner"); yieldReserve.updateBuyback(VAULT); } - function test_updateBuybackAddress_inputs() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_InvalidAddress() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); yieldReserve.updateBuyback(address(0)); } function test_updateBuybackAddress() public useKnownActor(USDS_OWNER) { + vm.expectEmit(address(yieldReserve)); + emit BuybackUpdated(VAULT); yieldReserve.updateBuyback(VAULT); assertEq(yieldReserve.buyback(), VAULT); } +} + +contract UpdateOracleAddressTest is YieldReserveTest { + event OracleUpdated(address newOracle); - function test_updateOracleAddress_auth_error() public useActor(0) { + function test_RevertWhen_callerNotOwner() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); yieldReserve.updateOracle(VAULT); } - function test_updateOracleAddress_inputs() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_invalidAddress() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); yieldReserve.updateOracle(address(0)); } function test_updateOracleAddress() public useKnownActor(USDS_OWNER) { + vm.expectEmit(address(yieldReserve)); + emit OracleUpdated(VAULT); yieldReserve.updateOracle(VAULT); assertEq(yieldReserve.oracle(), VAULT); } +} - function test_updateDripperAddress_auth_error() public useActor(0) { +contract UpdateDripperAddressTest is YieldReserveTest { + event DripperUpdated(address newDripper); + + function test_RevertWhen_callerNotOwner() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); yieldReserve.updateDripper(VAULT); } - function test_updateDripperAddress_inputs() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_invalidAddress() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); yieldReserve.updateDripper(address(0)); } function test_updateDripperAddress() public useKnownActor(USDS_OWNER) { + vm.expectEmit(address(yieldReserve)); + emit DripperUpdated(VAULT); yieldReserve.updateDripper(VAULT); assertEq(yieldReserve.dripper(), VAULT); } +} + +contract UpdateVaultAddressTest is YieldReserveTest { + event VaultUpdated(address newVault); - function test_updateVaultAddress_auth_error() public useActor(0) { + function test_RevertWhen_callerNotOwner() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); yieldReserve.updateVault(VAULT); } - function test_updateVaultAddress_inputs() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_invalidAddress() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); yieldReserve.updateVault(address(0)); } function test_updateVaultAddress() public useKnownActor(USDS_OWNER) { + vm.expectEmit(address(yieldReserve)); + emit VaultUpdated(ORACLE); yieldReserve.updateVault(ORACLE); assertEq(yieldReserve.vault(), ORACLE); } +} - function test_getTokenBForTokenA_inputs() public useKnownActor(USDS_OWNER) { +contract GetTokenBForTokenATest is YieldReserveTest { + function setUp() public override { + super.setUp(); mockPrice(USDCe, 1e8, PRICE_PRECISION); mockPrice(USDS, 1e8, PRICE_PRECISION); + } + function test_RevertWhen_invalidSourceToken() public { vm.expectRevert(abi.encodeWithSelector(YieldReserve.InvalidSourceToken.selector)); yieldReserve.getTokenBForTokenA(USDS, USDCe, 10000); - yieldReserve.toggleSrcTokenPermission(USDS, true); + } + function test_RevertWhen_invalidDestinationToken() public useKnownActor(USDS_OWNER) { + yieldReserve.toggleSrcTokenPermission(USDS, true); vm.expectRevert(abi.encodeWithSelector(YieldReserve.InvalidDestinationToken.selector)); yieldReserve.getTokenBForTokenA(USDS, USDCe, 10000); - yieldReserve.toggleDstTokenPermission(USDCe, true); + } + function test_RevertWhen_invalidAmount() public useKnownActor(USDS_OWNER) { + yieldReserve.toggleSrcTokenPermission(USDS, true); + yieldReserve.toggleDstTokenPermission(USDCe, true); vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAmount.selector)); yieldReserve.getTokenBForTokenA(USDS, USDCe, 0); } @@ -275,7 +356,7 @@ contract YieldReserveTest is YieldReserveSetup { } } -contract Test_MintUSDs is YieldReserveSetup { +contract Test_MintUSDs is YieldReserveTest { address buyback; event Minted( @@ -305,7 +386,7 @@ contract Test_MintUSDs is YieldReserveSetup { } } -contract SwapTest is YieldReserveSetup { +contract SwapTest is YieldReserveTest { function setUp() public override { super.setUp(); vm.startPrank(USDS_OWNER); diff --git a/test/oracle/ChainlinkOracle.t.sol b/test/oracle/ChainlinkOracle.t.sol index f17e877f..c6d14e23 100644 --- a/test/oracle/ChainlinkOracle.t.sol +++ b/test/oracle/ChainlinkOracle.t.sol @@ -32,13 +32,13 @@ contract ChainlinkOracleTest is BaseTest { } contract Test_SetTokenData is ChainlinkOracleTest { - function test_revertsWhen_notOwner() public { + function test_RevertWhen_notOwner() public { ChainlinkOracle.SetupTokenData memory tokenData = _getTokenData()[0]; vm.expectRevert("Ownable: caller is not the owner"); chainlinkOracle.setTokenData(tokenData.token, tokenData.data); } - function test_revertsWhen_InvalidPrecision() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_InvalidPrecision() public useKnownActor(USDS_OWNER) { ChainlinkOracle.SetupTokenData memory tokenData = _getTokenData()[0]; tokenData.data.pricePrecision = 1e9; vm.expectRevert(abi.encodeWithSelector(ChainlinkOracle.InvalidPricePrecision.selector)); @@ -47,7 +47,7 @@ contract Test_SetTokenData is ChainlinkOracleTest { function test_setTokenData() public useKnownActor(USDS_OWNER) { ChainlinkOracle.SetupTokenData memory tokenData = _getTokenData()[0]; - vm.expectEmit(true, true, true, true); + vm.expectEmit(address(chainlinkOracle)); emit TokenDataChanged( tokenData.token, tokenData.data.priceFeed, tokenData.data.pricePrecision, tokenData.data.timeout ); @@ -71,12 +71,12 @@ contract Test_GetTokenPrice is ChainlinkOracleTest { vm.stopPrank(); } - function test_revertsWhen_unSupportedCollateral() public { + function test_RevertWhen_unSupportedCollateral() public { vm.expectRevert(abi.encodeWithSelector(ChainlinkOracle.TokenNotSupported.selector, USDS)); chainlinkOracle.getTokenPrice(USDS); } - function test_revertsWhen_sequencerDown() public { + function test_RevertWhen_sequencerDown() public { (uint80 roundId,, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) = AggregatorV3Interface(chainlinkOracle.CHAINLINK_SEQ_UPTIME_FEED()).latestRoundData(); @@ -90,7 +90,7 @@ contract Test_GetTokenPrice is ChainlinkOracleTest { chainlinkOracle.getTokenPrice(USDCe); } - function test_revertsWhen_gracePeriodNotPassed() public { + function test_RevertWhen_gracePeriodNotPassed() public { (uint80 roundId,,, uint256 updatedAt, uint80 answeredInRound) = AggregatorV3Interface(chainlinkOracle.CHAINLINK_SEQ_UPTIME_FEED()).latestRoundData(); @@ -106,7 +106,7 @@ contract Test_GetTokenPrice is ChainlinkOracleTest { chainlinkOracle.getTokenPrice(USDCe); } - function test_revertsWhen_roundNotComplete() public { + function test_RevertWhen_roundNotComplete() public { uint256 updatedAt = 0; uint256 price = 100; vm.mockCall( @@ -118,7 +118,7 @@ contract Test_GetTokenPrice is ChainlinkOracleTest { chainlinkOracle.getTokenPrice(USDCe); } - function testFuzz_revertsWhen_PriceNegative(int256 price) public { + function testFuzz_RevertWhen_PriceNegative(int256 price) public { vm.assume(price < 0); uint256 updatedAt = block.timestamp; vm.mockCall( @@ -130,7 +130,7 @@ contract Test_GetTokenPrice is ChainlinkOracleTest { chainlinkOracle.getTokenPrice(USDCe); } - function test_revertsWhen_stalePrice() public { + function test_RevertWhen_stalePrice() public { uint256 updatedAt = block.timestamp - (25 hours + 1 seconds); vm.mockCall( 0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3, diff --git a/test/oracle/MasterPriceOracle.t.sol b/test/oracle/MasterPriceOracle.t.sol index 36c34a46..33576630 100644 --- a/test/oracle/MasterPriceOracle.t.sol +++ b/test/oracle/MasterPriceOracle.t.sol @@ -3,12 +3,11 @@ pragma solidity 0.8.19; import {BaseTest} from "../utils/BaseTest.sol"; import {MasterPriceOracle} from "../../contracts/oracle/MasterPriceOracle.sol"; +import {SPAOracle} from "../../contracts/oracle/SPAOracle.sol"; +import {USDsOracle} from "../../contracts/oracle/USDsOracle.sol"; import {ChainlinkOracle} from "../../contracts/oracle/ChainlinkOracle.sol"; - import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -// import {console} from "forge-std/console.sol"; - interface ICustomOracle { function updateDIAParams(uint256 _weightDIA, uint128 _maxTime) external; @@ -22,6 +21,19 @@ contract MasterPriceOracleTest is BaseTest { bytes msgData; } + address constant USDCe_PRICE_FEED = 0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3; + address constant FRAX_PRICE_FEED = 0x0809E3d38d1B4214958faf06D8b1B1a2b73f2ab8; + address constant DAI_PRICE_FEED = 0xc5C8E77B397E531B8EC06BFb0048328B30E9eCfB; + uint96 constant TOKEN_DATA_TIMEOUT = 25 hours; + uint256 constant TOKEN_DATA_PRECISION = 1e8; + uint24 constant SPA_ORACLE_USDCe_FEE_TIER = 10000; + uint24 constant USDs_ORACLE_USDCe_FEE_TIER = 500; + uint32 constant USDCe_MA_PERIOD = 600; + uint256 constant USDCe_WEIGHT_DIA = 70; + uint128 constant DIA_MAX_TIME_THRESHOLD = type(uint128).max; + uint256 constant DUMMY_PRICE = 1e7; + uint256 constant DUMMY_PREC = 1e8; + MasterPriceOracle public masterOracle; ChainlinkOracle public chainlinkOracle; address spaOracle; @@ -31,7 +43,7 @@ contract MasterPriceOracleTest is BaseTest { event PriceFeedUpdated(address indexed token, address indexed source, bytes msgData); event PriceFeedRemoved(address indexed token); - function setUp() public override { + function setUp() public virtual override { super.setUp(); setArbitrumFork(); vm.startPrank(USDS_OWNER); @@ -43,35 +55,98 @@ contract MasterPriceOracleTest is BaseTest { masterOracle.updateTokenPriceFeed( USDCe, address(chainlinkOracle), abi.encodeWithSelector(ChainlinkOracle.getTokenPrice.selector, USDCe) ); - spaOracle = deployCode("SPAOracle.sol:SPAOracle", abi.encode(address(masterOracle), USDCe, 10000, 600, 70)); - - ICustomOracle(spaOracle).updateDIAParams(70, type(uint128).max); + spaOracle = address( + new SPAOracle(address(masterOracle), USDCe, SPA_ORACLE_USDCe_FEE_TIER, USDCe_MA_PERIOD, USDCe_WEIGHT_DIA) + ); + ICustomOracle(spaOracle).updateDIAParams(USDCe_WEIGHT_DIA, DIA_MAX_TIME_THRESHOLD); - usdsOracle = deployCode("USDsOracle.sol", abi.encode(address(masterOracle), USDCe, 500, 600)); + usdsOracle = address(new USDsOracle(address(masterOracle), USDCe, USDs_ORACLE_USDCe_FEE_TIER, USDCe_MA_PERIOD)); vm.stopPrank(); } - function test_revertsWhen_unAuthorizedUpdate() public useActor(0) { - vm.expectRevert("Ownable: caller is not the owner"); - masterOracle.updateTokenPriceFeed(SPA, address(this), abi.encode(this.dummyPrice.selector)); + function dummyPrice() public view returns (uint256 price, uint256 prec) { + (price, prec) = ICustomOracle(spaOracle).getPrice(); + } + + function dummySPAPrice() public pure returns (uint256 price, uint256 prec) { + prec = DUMMY_PREC; + price = DUMMY_PRICE; + } + + function dummyInvalidPriceFeed() public pure returns (uint256) { + revert("Invalid Price feed"); + } + + function deployAndConfigureChainlink() private { + ChainlinkOracle.SetupTokenData[] memory chainlinkFeeds = new ChainlinkOracle.SetupTokenData[](3); + chainlinkFeeds[0] = ChainlinkOracle.SetupTokenData( + USDCe, ChainlinkOracle.TokenData(USDCe_PRICE_FEED, TOKEN_DATA_TIMEOUT, TOKEN_DATA_PRECISION) + ); + chainlinkFeeds[1] = ChainlinkOracle.SetupTokenData( + FRAX, ChainlinkOracle.TokenData(FRAX_PRICE_FEED, TOKEN_DATA_TIMEOUT, TOKEN_DATA_PRECISION) + ); + chainlinkFeeds[2] = ChainlinkOracle.SetupTokenData( + DAI, ChainlinkOracle.TokenData(DAI_PRICE_FEED, TOKEN_DATA_TIMEOUT, TOKEN_DATA_PRECISION) + ); + chainlinkOracle = new ChainlinkOracle(chainlinkFeeds); } - function test_revertsWhen_unAuthorizedRemoveRequest() public useActor(0) { + function getPriceFeedConfig() internal view returns (PriceFeedData[] memory) { + PriceFeedData[] memory feedData = new PriceFeedData[](4); + feedData[0] = PriceFeedData( + FRAX, address(chainlinkOracle), abi.encodeWithSelector(ChainlinkOracle.getTokenPrice.selector, FRAX) + ); + feedData[1] = PriceFeedData( + DAI, address(chainlinkOracle), abi.encodeWithSelector(ChainlinkOracle.getTokenPrice.selector, DAI) + ); + feedData[2] = PriceFeedData(SPA, spaOracle, abi.encodeWithSelector(ICustomOracle.getPrice.selector)); + feedData[3] = PriceFeedData(USDS, usdsOracle, abi.encodeWithSelector(ICustomOracle.getPrice.selector)); + return feedData; + } +} + +contract UpdateTokenPriceFeed is MasterPriceOracleTest { + address token; + address source; + bytes msgData; + + function setUp() public virtual override { + super.setUp(); + + token = SPA; + source = address(this); + msgData = abi.encode(this.dummyPrice.selector); + } + + function test_RevertWhen_NotOwner() public useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); - masterOracle.removeTokenPriceFeed(SPA); + masterOracle.updateTokenPriceFeed(token, source, msgData); + } + + function test_RevertWhen_InvalidPriceFeed() public useKnownActor(USDS_OWNER) { + vm.expectRevert(abi.encodeWithSelector(MasterPriceOracle.InvalidPriceFeed.selector, token)); + masterOracle.updateTokenPriceFeed(token, address(0), msgData); } - function test_revertsWhen_removingNonExistingFeed() public useKnownActor(USDS_OWNER) { - vm.expectRevert(abi.encodeWithSelector(MasterPriceOracle.PriceFeedNotFound.selector, SPA)); - masterOracle.removeTokenPriceFeed(SPA); + function test_RevertWhen_UnableToFetchPriceFeed() public useKnownActor(USDS_OWNER) { + vm.expectRevert(abi.encodeWithSelector(MasterPriceOracle.UnableToFetchPriceFeed.selector, token)); + masterOracle.updateTokenPriceFeed(token, source, abi.encode(this.dummyInvalidPriceFeed.selector)); } function test_updateTokenPriceFeed() public useKnownActor(USDS_OWNER) { PriceFeedData[] memory priceFeeds = getPriceFeedConfig(); for (uint8 i = 0; i < priceFeeds.length; ++i) { - vm.expectEmit(true, true, false, true); + assertEq(masterOracle.priceFeedExists(priceFeeds[i].token), false); + + vm.expectEmit(address(masterOracle)); emit PriceFeedUpdated(priceFeeds[i].token, priceFeeds[i].source, priceFeeds[i].msgData); masterOracle.updateTokenPriceFeed(priceFeeds[i].token, priceFeeds[i].source, priceFeeds[i].msgData); + + assertEq(masterOracle.priceFeedExists(priceFeeds[i].token), true); + (address _source, bytes memory _msgData) = masterOracle.tokenPriceFeed(priceFeeds[i].token); + assertEq(_source, priceFeeds[i].source); + assertEq(_msgData, priceFeeds[i].msgData); + MasterPriceOracle.PriceData memory data = masterOracle.getPrice(priceFeeds[i].token); (bool success, bytes memory actualData) = address(priceFeeds[i].source).call(priceFeeds[i].msgData); @@ -86,67 +161,46 @@ contract MasterPriceOracleTest is BaseTest { } function test_getPriceFeed() public useKnownActor(USDS_OWNER) { - masterOracle.updateTokenPriceFeed(SPA, address(this), abi.encode(this.dummySPAPrice.selector)); - MasterPriceOracle.PriceData memory data = masterOracle.getPrice(SPA); - assertEqUint(data.price, 1e7); - assertEqUint(data.precision, 1e8); + masterOracle.updateTokenPriceFeed(token, source, abi.encode(this.dummySPAPrice.selector)); + MasterPriceOracle.PriceData memory data = masterOracle.getPrice(token); + assertEqUint(data.price, DUMMY_PRICE); + assertEqUint(data.precision, DUMMY_PREC); } +} - function test_removeTokenPriceFeed() public useKnownActor(USDS_OWNER) { - masterOracle.updateTokenPriceFeed(SPA, address(this), abi.encode(this.dummyPrice.selector)); +contract RemoveTokenPriceFeed is MasterPriceOracleTest { + address token; + address source; + bytes msgData; - vm.expectEmit(true, false, false, false); - emit PriceFeedRemoved(SPA); - masterOracle.removeTokenPriceFeed(SPA); - } + function setUp() public virtual override { + super.setUp(); - function test_revertsWhen_invalidPriceFeed() public useKnownActor(USDS_OWNER) { - vm.expectRevert(abi.encodeWithSelector(MasterPriceOracle.InvalidPriceFeed.selector, SPA)); - masterOracle.updateTokenPriceFeed(SPA, address(0), abi.encode(this.dummyPrice.selector)); + token = SPA; + source = address(this); + msgData = abi.encode(this.dummyPrice.selector); } - function test_revertsWhen_feedNotFetched() public useKnownActor(USDS_OWNER) { - vm.expectRevert(abi.encodeWithSelector(MasterPriceOracle.UnableToFetchPriceFeed.selector, SPA)); - masterOracle.updateTokenPriceFeed(SPA, address(this), abi.encode(this.dummyInvalidPriceFeed.selector)); + function test_RevertWhen_NotOwner() public useActor(0) { + vm.expectRevert("Ownable: caller is not the owner"); + masterOracle.removeTokenPriceFeed(token); } - function dummyPrice() public view returns (uint256 price, uint256 prec) { - (price, prec) = ICustomOracle(spaOracle).getPrice(); + function test_RevertWhen_PriceFeedNotFound() public useKnownActor(USDS_OWNER) { + vm.expectRevert(abi.encodeWithSelector(MasterPriceOracle.PriceFeedNotFound.selector, token)); + masterOracle.removeTokenPriceFeed(token); } - function dummySPAPrice() public pure returns (uint256 price, uint256 prec) { - prec = 1e8; - price = 1e7; - } + function test_removeTokenPriceFeed() public useKnownActor(USDS_OWNER) { + masterOracle.updateTokenPriceFeed(token, source, msgData); - function dummyInvalidPriceFeed() public pure returns (uint256) { - revert("Invalid Price feed"); - } + vm.expectEmit(address(masterOracle)); + emit PriceFeedRemoved(token); + masterOracle.removeTokenPriceFeed(token); - function deployAndConfigureChainlink() private { - ChainlinkOracle.SetupTokenData[] memory chainlinkFeeds = new ChainlinkOracle.SetupTokenData[](3); - chainlinkFeeds[0] = ChainlinkOracle.SetupTokenData( - USDCe, ChainlinkOracle.TokenData(0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3, 25 hours, 1e8) - ); - chainlinkFeeds[1] = ChainlinkOracle.SetupTokenData( - FRAX, ChainlinkOracle.TokenData(0x0809E3d38d1B4214958faf06D8b1B1a2b73f2ab8, 25 hours, 1e8) - ); - chainlinkFeeds[2] = ChainlinkOracle.SetupTokenData( - DAI, ChainlinkOracle.TokenData(0xc5C8E77B397E531B8EC06BFb0048328B30E9eCfB, 25 hours, 1e8) - ); - chainlinkOracle = new ChainlinkOracle(chainlinkFeeds); - } - - function getPriceFeedConfig() private view returns (PriceFeedData[] memory) { - PriceFeedData[] memory feedData = new PriceFeedData[](4); - feedData[0] = PriceFeedData( - FRAX, address(chainlinkOracle), abi.encodeWithSelector(ChainlinkOracle.getTokenPrice.selector, FRAX) - ); - feedData[1] = PriceFeedData( - DAI, address(chainlinkOracle), abi.encodeWithSelector(ChainlinkOracle.getTokenPrice.selector, DAI) - ); - feedData[2] = PriceFeedData(SPA, spaOracle, abi.encodeWithSelector(ICustomOracle.getPrice.selector)); - feedData[3] = PriceFeedData(USDS, usdsOracle, abi.encodeWithSelector(ICustomOracle.getPrice.selector)); - return feedData; + assertEq(masterOracle.priceFeedExists(token), false); + (address _source, bytes memory _msgData) = masterOracle.tokenPriceFeed(token); + assertEq(_source, address(0)); + assertEq(_msgData, bytes("")); } } diff --git a/test/oracle/SPAOracle.t.sol b/test/oracle/SPAOracle.t.sol index 273b7e20..d512583a 100644 --- a/test/oracle/SPAOracle.t.sol +++ b/test/oracle/SPAOracle.t.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.19; -import {SPAOracle} from "../../contracts/oracle/SPAOracle.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {SPAOracle, IDiaOracle} from "../../contracts/oracle/SPAOracle.sol"; import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; import {BaseTest} from "../utils/BaseTest.sol"; @@ -33,6 +34,7 @@ abstract contract BaseUniOracleTest is BaseTest { error FeedUnavailable(); error InvalidAddress(); + error QuoteTokenFeedMissing(); function setUp() public virtual override { super.setUp(); @@ -66,6 +68,8 @@ contract SPAOracleTest is BaseUniOracleTest { event DIAParamsUpdated(uint256 weightDIA, uint128 maxTime); error InvalidWeight(); + error InvalidTime(); + error PriceTooOld(); function setUp() public override { super.setUp(); @@ -82,16 +86,30 @@ contract Test_Init is SPAOracleTest { assertEq(uint256(spaOracle.maPeriod()), uint256(MA_PERIOD)); assertEq(spaOracle.weightDIA(), WEIGHT_DIA); } + + function test_RevertWhen_QuoteTokenFeedMissing() public { + vm.expectRevert(abi.encodeWithSelector(QuoteTokenFeedMissing.selector)); + new SPAOracle(masterOracle, USDS, FEE_TIER, MA_PERIOD, WEIGHT_DIA); // USDS is not added to master oracle + } } -contract Test_FetchPrice is SPAOracleTest { - function test_fetchPrice() public { +contract Test_GetPrice is SPAOracleTest { + function test_RevertWhen_PriceTooOld() public { + vm.startPrank(USDS_OWNER); + spaOracle.updateDIAParams(WEIGHT_DIA, 121); + vm.stopPrank(); + + vm.expectRevert(abi.encodeWithSelector(PriceTooOld.selector)); + spaOracle.getPrice(); + } + + function test_GetPrice() public { (uint256 price, uint256 precision) = spaOracle.getPrice(); assertEq(precision, SPA_PRICE_PRECISION); assertGt(price, 0); } - function testFuzz_fetchPrice_when_period_value_below_minTwapPeriod(uint256 period) public { + function testFuzz_GetPrice_when_period_value_below_minTwapPeriod(uint256 period) public { // this test is to make sure that even if the twap period value is less than MIN_TWAP_PERIOD (10 mins) // we still get the price based on MIN_TWAP_PERIOD (10 mins) vm.assume(period < 10 minutes); @@ -118,12 +136,12 @@ contract Test_FetchPrice is SPAOracleTest { contract Test_setUniMAPriceData is SPAOracleTest { error InvalidMaPeriod(); - function test_revertsWhen_notOwner() public { + function test_RevertWhen_notOwner() public { vm.expectRevert("Ownable: caller is not the owner"); spaOracle.setUniMAPriceData(SPA, USDCe, 10000, 600); } - function test_revertsWhen_invalidData() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_invalidData() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(FeedUnavailable.selector)); spaOracle.setUniMAPriceData(SPA, FRAX, 3000, 600); } @@ -132,9 +150,12 @@ contract Test_setUniMAPriceData is SPAOracleTest { vm.expectEmit(true, true, true, true); emit UniMAPriceDataChanged(USDCe, 10000, 700); spaOracle.setUniMAPriceData(SPA, USDCe, 10000, 700); + assertEq(spaOracle.quoteToken(), USDCe); + assertEq(spaOracle.maPeriod(), 700); + assertEq(spaOracle.quoteTokenPrecision(), uint128(10) ** ERC20(USDCe).decimals()); } - function test_revertsWhen_invalidMaPeriod() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_invalidMaPeriod() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(InvalidMaPeriod.selector)); spaOracle.setUniMAPriceData(SPA, USDCe, 10000, 599); @@ -144,19 +165,19 @@ contract Test_setUniMAPriceData is SPAOracleTest { } contract Test_updateMasterOracle is SPAOracleTest { - function test_revertsWhen_notOwner() public { + function test_RevertWhen_notOwner() public { vm.expectRevert("Ownable: caller is not the owner"); spaOracle.updateMasterOracle(masterOracle); } - function test_revertsWhen_invalidAddress() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_invalidAddress() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(InvalidAddress.selector)); spaOracle.updateMasterOracle(address(0)); } - function test_revertsWhen_quoteTokenPriceFeedUnavailable() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_quoteTokenPriceFeedUnavailable() public useKnownActor(USDS_OWNER) { IMasterOracle(masterOracle).removeTokenPriceFeed(USDCe); - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(QuoteTokenFeedMissing.selector)); spaOracle.updateMasterOracle(masterOracle); } @@ -164,26 +185,34 @@ contract Test_updateMasterOracle is SPAOracleTest { vm.expectEmit(true, true, true, true); emit MasterOracleUpdated(masterOracle); spaOracle.updateMasterOracle(masterOracle); + assertEq(spaOracle.masterOracle(), masterOracle); } } contract Test_UpdateDIAWeight is SPAOracleTest { - function test_revertsWhen_notOwner() public { + function test_RevertWhen_notOwner() public { vm.expectRevert("Ownable: caller is not the owner"); spaOracle.updateDIAParams(60, 600); } - function test_revertsWhen_invalidWeight() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_invalidWeight() public useKnownActor(USDS_OWNER) { uint256 newWeight = spaOracle.MAX_WEIGHT() + 10; vm.expectRevert(abi.encodeWithSelector(InvalidWeight.selector)); spaOracle.updateDIAParams(newWeight, 600); } + function test_RevertWhen_invalidTime() public useKnownActor(USDS_OWNER) { + vm.expectRevert(abi.encodeWithSelector(InvalidTime.selector)); + spaOracle.updateDIAParams(80, 80); + } + function test_updateDIAParams() public useKnownActor(USDS_OWNER) { uint256 newWeight = 80; uint128 maxTime = 600; vm.expectEmit(true, true, true, true); emit DIAParamsUpdated(newWeight, maxTime); spaOracle.updateDIAParams(newWeight, maxTime); + assertEq(spaOracle.weightDIA(), newWeight); + assertEq(spaOracle.diaMaxTimeThreshold(), maxTime); } } diff --git a/test/rebase/Dripper.t.sol b/test/rebase/Dripper.t.sol index cdd02699..b18f2558 100644 --- a/test/rebase/Dripper.t.sol +++ b/test/rebase/Dripper.t.sol @@ -1,77 +1,95 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.8.19; -import {BaseTest} from ".././utils/BaseTest.sol"; +import {PreMigrationSetup} from ".././utils/DeploymentSetup.sol"; import {Dripper, Helpers} from "../../contracts/rebase/Dripper.sol"; import {IUSDs} from "../../contracts/interfaces/IUSDs.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {USDs} from "../../contracts/token/USDs.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; address constant WHALE_USDS = 0x50450351517117Cb58189edBa6bbaD6284D45902; -contract DripperTest is BaseTest { +contract DripperTest is PreMigrationSetup { + // Constants for the test. + uint256 constant DRIP_DURATION = 7 days; + uint256 MAX_SUPPLY = ~uint128(0); + // Init Variables. Dripper public dripper; + // Events from external contracts + event Transfer(address indexed from, address indexed to, uint256 value); + // Events from the actual contract. event Collected(uint256 amount); event VaultUpdated(address vault); event DripDurationUpdated(uint256 dripDuration); event Recovered(address owner, uint256 amount); + event USDsAdded(uint256 _amount); error NothingToRecover(); function setUp() public override { super.setUp(); - setArbitrumFork(); - dripper = new Dripper(VAULT, (7 days)); - dripper.transferOwnership(USDS_OWNER); + dripper = Dripper(DRIPPER); + } +} + +contract TestConstructor is DripperTest { + function test_Constructor() external { + assertEq(dripper.vault(), VAULT); + assertEq(dripper.dripDuration(), DRIP_DURATION); + assertEq(dripper.lastCollectTS(), block.timestamp); + assertEq(dripper.owner(), USDS_OWNER); } } contract UpdateVault is DripperTest { + function test_RevertWhen_CallerIsNotOwner() external useActor(0) { + address newVaultAddress = address(1); + + vm.expectRevert("Ownable: caller is not the owner"); + dripper.updateVault(newVaultAddress); + } + function test_RevertWhen_VaultIsZeroAddress() external useKnownActor(USDS_OWNER) { address newVaultAddress = address(0); vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); dripper.updateVault(newVaultAddress); } - // Can't set the fuzzer for address type - function test_UpdateVault() external useKnownActor(USDS_OWNER) { - address newVaultAddress = address(1); - vm.expectEmit(true, true, false, true); - emit VaultUpdated(address(1)); + function testFuzz_UpdateVault(address newVaultAddress) external useKnownActor(USDS_OWNER) { + vm.assume(newVaultAddress != address(0)); + vm.expectEmit(address(dripper)); + emit VaultUpdated(newVaultAddress); dripper.updateVault(newVaultAddress); + + assertEq(dripper.vault(), newVaultAddress); } +} +contract UpdateDripDuration is DripperTest { function test_RevertWhen_CallerIsNotOwner() external useActor(0) { - address newVaultAddress = address(1); - vm.expectRevert("Ownable: caller is not the owner"); - dripper.updateVault(newVaultAddress); + dripper.updateDripDuration(0); } -} -contract SetDripDuration is DripperTest { - function test_RevertWhen_InvalidInput(uint256 dripDuration) external useKnownActor(USDS_OWNER) { - vm.assume(dripDuration == 0); + function test_RevertWhen_InvalidInput() external useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAmount.selector)); - dripper.updateDripDuration(dripDuration); + dripper.updateDripDuration(0); } - // Can't set the fuzzer for address type - function test_UpdateDripDuration(uint256 dripDuration) external useKnownActor(USDS_OWNER) { + function testFuzz_UpdateDripDuration(uint256 dripDuration) external useKnownActor(USDS_OWNER) { vm.assume(dripDuration != 0); - vm.expectEmit(true, true, false, true); - emit DripDurationUpdated(dripDuration); + vm.expectEmit(address(dripper)); + emit DripDurationUpdated(dripDuration); dripper.updateDripDuration(dripDuration); - } - - function test_RevertWhen_CallerIsNotOwner(uint256 dripDuration) external useActor(0) { - vm.assume(dripDuration != 0); - vm.expectRevert("Ownable: caller is not the owner"); - dripper.updateDripDuration(dripDuration); + assertEq(dripper.dripDuration(), dripDuration); } } @@ -86,12 +104,14 @@ contract RecoverTokens is DripperTest { dripper.recoverTokens(USDCe); } - function test_RecoverTokens(uint128 amount) external useKnownActor(USDS_OWNER) { + function testFuzz_RecoverTokens(uint128 amount) external useKnownActor(USDS_OWNER) { address[4] memory assets = [USDCe, USDT, FRAX, DAI]; vm.assume(amount != 0); for (uint8 i = 0; i < assets.length; i++) { deal(address(assets[i]), address(dripper), amount, true); - vm.expectEmit(true, true, false, true); + vm.expectEmit(address(assets[i])); + emit Transfer(address(dripper), USDS_OWNER, amount); + vm.expectEmit(address(dripper)); emit Recovered(USDS_OWNER, amount); dripper.recoverTokens((assets[i])); } @@ -99,20 +119,97 @@ contract RecoverTokens is DripperTest { } contract Collect is DripperTest { - function test_CollectZeroBalance() external useActor(0) { + function testFuzz_Collect_ZeroBalance(uint256 _amount) external useActor(0) { + // if _amount is less than 7 days, dripRate is 0 + _amount = bound(_amount, 0, (7 days) - 1); + + if (_amount > 0) { + changePrank(VAULT); + IUSDs(USDS).mint(WHALE_USDS, _amount); + changePrank(WHALE_USDS); + IERC20(USDS).approve(address(dripper), _amount); + dripper.addUSDs(_amount); + skip(8 days); // adding one additional day so that full amount can be collected + } + assertEq(dripper.dripRate(), 0); assertEq(dripper.getCollectableAmt(), 0); - dripper.collect(); + uint256 collectableAmt = dripper.collect(); + assertEq(collectableAmt, 0); + } + + function testFuzz_CollectDripper(uint256 _amount) external { + _amount = bound(_amount, 7 days, MAX_SUPPLY - IUSDs(USDS).totalSupply()); + + changePrank(VAULT); + IUSDs(USDS).mint(WHALE_USDS, _amount); + changePrank(WHALE_USDS); + IERC20(USDS).approve(address(dripper), _amount); + dripper.addUSDs(_amount); + skip(14 days); // adding additional days so that full amount can be collected + + vm.expectEmit(address(USDS)); + emit Transfer(address(dripper), VAULT, _amount); + vm.expectEmit(address(dripper)); + emit Collected(_amount); + uint256 collectableAmt = dripper.collect(); + + assertEq(collectableAmt, _amount); + assertEq(dripper.dripRate(), 0); + assertEq(dripper.lastCollectTS(), block.timestamp); + } +} + +contract AddUSDs is DripperTest { + function test_RevertWhen_InvalidInput() external useActor(0) { + vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAmount.selector)); + dripper.addUSDs(0); } - function test_CollectDripper() external useKnownActor(WHALE_USDS) { + function testFuzz_AddUSDs(uint256 _amount) external { + _amount = bound(_amount, 1, MAX_SUPPLY - IUSDs(USDS).totalSupply()); + + changePrank(VAULT); + IUSDs(USDS).mint(WHALE_USDS, _amount); + changePrank(WHALE_USDS); + IERC20(USDS).approve(address(dripper), _amount); + + uint256 lastCollectTs = dripper.lastCollectTS(); + skip(14 days); + + vm.expectEmit(USDS); + emit Transfer(WHALE_USDS, address(dripper), _amount); + vm.expectEmit(address(dripper)); + emit USDsAdded(_amount); + dripper.addUSDs(_amount); + + assertEq(dripper.dripRate(), _amount / DRIP_DURATION); + assertEq(dripper.lastCollectTS(), block.timestamp); + assert(lastCollectTs < block.timestamp); + } + + function testFuzz_AddUSDs_Collect(uint256 _amount) external { + _amount = bound(_amount, 2 * (7 days), MAX_SUPPLY - IUSDs(USDS).totalSupply()); + changePrank(VAULT); - IUSDs(USDS).mint(WHALE_USDS, 1e24); + IUSDs(USDS).mint(WHALE_USDS, _amount); changePrank(WHALE_USDS); - IERC20(USDS).approve(address(dripper), 1e24); - dripper.addUSDs(1e24); + IERC20(USDS).approve(address(dripper), _amount); + uint256 halfAmount = _amount / 2; + dripper.addUSDs(halfAmount); + + uint256 lastCollectTs = dripper.lastCollectTS(); skip(14 days); - vm.expectEmit(true, true, false, true); - emit Collected(1e24); - dripper.collect(); + + vm.expectEmit(address(dripper)); + emit Collected(halfAmount); + vm.expectEmit(USDS); + emit Transfer(WHALE_USDS, address(dripper), halfAmount); + vm.expectEmit(address(dripper)); + emit USDsAdded(halfAmount); + dripper.addUSDs(halfAmount); + + assertEq(dripper.dripRate(), halfAmount / DRIP_DURATION); + assertEq(dripper.lastCollectTS(), block.timestamp); + assert(lastCollectTs < block.timestamp); } } diff --git a/test/rebase/RebaseManager.t.sol b/test/rebase/RebaseManager.t.sol index b7015e25..c99654e2 100644 --- a/test/rebase/RebaseManager.t.sol +++ b/test/rebase/RebaseManager.t.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {PreMigrationSetup} from ".././utils/DeploymentSetup.sol"; @@ -7,6 +8,7 @@ import {IOracle} from "../../contracts/interfaces/IOracle.sol"; import {IERC20, ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {console} from "forge-std/console.sol"; import {IVault} from "../../contracts/interfaces/IVault.sol"; +import {IUSDs} from "../../contracts/interfaces/IUSDs.sol"; contract RebaseManagerTest is PreMigrationSetup { // Init Variables. @@ -16,6 +18,8 @@ contract RebaseManagerTest is PreMigrationSetup { uint256 USDCePrecision; uint256 USDsPrecision; + uint256 MAX_SUPPLY = ~uint128(0); + // Events from the actual contract. event VaultUpdated(address vault); event DripperUpdated(address dripper); @@ -36,110 +40,174 @@ contract RebaseManagerTest is PreMigrationSetup { } function mintUSDs(uint256 amountIn) public { - vm.startPrank(USDS_OWNER); - - deal(address(USDCe), USDS_OWNER, amountIn); - - IERC20(USDCe).approve(VAULT, amountIn); - IVault(VAULT).mintBySpecifyingCollateralAmt(USDCe, amountIn, 0, 0, block.timestamp + 1200); + vm.startPrank(VAULT); + IUSDs(USDS).mint(USDS_OWNER, amountIn); vm.stopPrank(); } } +contract Constructor is RebaseManagerTest { + function test_Constructor() external { + assertEq(rebaseManager.vault(), VAULT); + assertEq(rebaseManager.dripper(), DRIPPER); + assertEq(rebaseManager.gap(), REBASE_MANAGER_GAP); + assertEq(rebaseManager.aprCap(), REBASE_MANAGER_APR_CAP); + assertEq(rebaseManager.aprBottom(), REBASE_MANAGER_APR_BOTTOM); + assertEq(rebaseManager.lastRebaseTS(), block.timestamp); + } +} + contract UpdateVault is RebaseManagerTest { - function test_RevertWhen_VaultIsZeroAddress() external useKnownActor(USDS_OWNER) { + function test_RevertWhen_CallerIsNotOwner() external useActor(0) { + address newVaultAddress = address(1); + + vm.expectRevert("Ownable: caller is not the owner"); + rebaseManager.updateVault(newVaultAddress); + } + + function test_RevertWhen_InvalidAddress() external useKnownActor(USDS_OWNER) { address newVaultAddress = address(0); vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); rebaseManager.updateVault(newVaultAddress); } - // Can't set the fuzzer for address type - function test_UpdateVault() external useKnownActor(USDS_OWNER) { - address newVaultAddress = address(1); - vm.expectEmit(true, true, false, true); - emit VaultUpdated(address(1)); + function test_UpdateVault(address newVaultAddress) external useKnownActor(USDS_OWNER) { + vm.assume(newVaultAddress != address(0)); + vm.expectEmit(address(rebaseManager)); + emit VaultUpdated(newVaultAddress); rebaseManager.updateVault(newVaultAddress); + + assertEq(rebaseManager.vault(), newVaultAddress); } +} +contract UpdateDripper is RebaseManagerTest { function test_RevertWhen_CallerIsNotOwner() external useActor(0) { - address newVaultAddress = address(1); + address newDripperAddress = address(1); vm.expectRevert("Ownable: caller is not the owner"); - rebaseManager.updateVault(newVaultAddress); + rebaseManager.updateDripper(newDripperAddress); } -} -contract UpdateDripper is RebaseManagerTest { - function test_RevertWhen_DripperIsZeroAddress() external useKnownActor(USDS_OWNER) { + function test_RevertWhen_InvalidAddress() external useKnownActor(USDS_OWNER) { address newDripperAddress = address(0); vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); rebaseManager.updateDripper(newDripperAddress); } - function test_RevertWhen_CallerIsNotOwner() external useActor(0) { - address newDripperAddress = address(1); + function test_UpdateDripper(address newDripperAddress) external useKnownActor(USDS_OWNER) { + vm.assume(newDripperAddress != address(0)); - vm.expectRevert("Ownable: caller is not the owner"); + vm.expectEmit(address(rebaseManager)); + emit DripperUpdated(newDripperAddress); rebaseManager.updateDripper(newDripperAddress); - } - function test_UpdateDripper() external useKnownActor(USDS_OWNER) { - address newDripperAddress = address(1); - vm.expectEmit(true, true, false, true); - emit DripperUpdated(address(1)); - rebaseManager.updateDripper(newDripperAddress); + assertEq(rebaseManager.dripper(), newDripperAddress); } } contract UpdateGap is RebaseManagerTest { - function test_UpdateGap_Zero() external useKnownActor(USDS_OWNER) { - vm.expectEmit(true, true, false, true); - emit GapUpdated(0); - rebaseManager.updateGap(0); - } - function test_RevertWhen_CallerIsNotOwner() external useActor(0) { vm.expectRevert("Ownable: caller is not the owner"); rebaseManager.updateGap(7 days); } function test_UpdateGap(uint256 gap) external useKnownActor(USDS_OWNER) { - vm.assume(gap != 0); - - vm.expectEmit(true, true, false, true); + vm.expectEmit(address(rebaseManager)); emit GapUpdated(gap); rebaseManager.updateGap(gap); + + assertEq(rebaseManager.gap(), gap); } } contract UpdateAPR is RebaseManagerTest { - function test_RevertWhen_InvalidConfig(uint256 aprBottom, uint256 aprCap) external useKnownActor(USDS_OWNER) { - vm.assume(aprBottom > aprCap); + function test_RevertWhen_InvalidAPRConfig() external useKnownActor(USDS_OWNER) { + uint256 aprBottom = 10; + uint256 aprCap = 9; + vm.expectRevert(abi.encodeWithSelector(InvalidAPRConfig.selector, aprBottom, aprCap)); rebaseManager.updateAPR(aprBottom, aprCap); } - function test_RevertWhen_CallerIsNotOwner(uint256 aprBottom, uint256 aprCap) external useActor(0) { - vm.assume(aprBottom <= aprCap); + function test_RevertWhen_CallerIsNotOwner() external useActor(0) { + uint256 aprBottom = 10; + uint256 aprCap = 9; + vm.expectRevert("Ownable: caller is not the owner"); rebaseManager.updateAPR(aprBottom, aprCap); } function test_UpdateAPR(uint256 aprBottom, uint256 aprCap) external useKnownActor(USDS_OWNER) { vm.assume(aprBottom <= aprCap); - vm.expectEmit(true, true, false, true); + + vm.expectEmit(address(rebaseManager)); emit APRUpdated(aprBottom, aprCap); rebaseManager.updateAPR(aprBottom, aprCap); + + assertEq(rebaseManager.aprBottom(), aprBottom); + assertEq(rebaseManager.aprCap(), aprCap); } } contract FetchRebaseAmt is RebaseManagerTest { - function test_RevertWhen_CallerIsNotOwner() external useActor(0) { + event Collected(uint256 amount); + + function test_RevertWhen_CallerNotVault() external useActor(0) { vm.expectRevert(abi.encodeWithSelector(CallerNotVault.selector, actors[0])); rebaseManager.fetchRebaseAmt(); } + function test_FetchRebaseAmt_WhenRebaseAmtIsZero() external useKnownActor(VAULT) { + uint256 rebaseAmt = rebaseManager.fetchRebaseAmt(); + assertEq(rebaseAmt, 0); + } + + function test_FetchRebaseAmt(uint256 _amount, uint256 vaultBalance) external useKnownActor(VAULT) { + _amount = bound(_amount, 7 days, MAX_SUPPLY - IUSDs(USDS).totalSupply()); + vaultBalance = bound(vaultBalance, 1, MAX_SUPPLY - IUSDs(USDS).totalSupply()); + uint256 calculatedRebaseAmt = _amount + vaultBalance; + vm.mockCall(address(USDS), abi.encodeWithSignature("balanceOf(address)", VAULT), abi.encode(vaultBalance)); + + mintUSDs(_amount); + changePrank(USDS_OWNER); + IERC20(USDS).approve(address(dripper), _amount); + dripper.addUSDs(_amount); + + changePrank(VAULT); + skip(14 days); // to collect all the drip + uint256 lastRebaseTS = rebaseManager.lastRebaseTS(); + skip(rebaseManager.gap()); // to allow rebase + + (uint256 minRebaseAmt, uint256 maxRebaseAmt) = rebaseManager.getMinAndMaxRebaseAmt(); + assert(minRebaseAmt > 0); + assert(maxRebaseAmt > 0); + + uint256 availableRebaseAmt = rebaseManager.getAvailableRebaseAmt(); + assertEq(availableRebaseAmt, calculatedRebaseAmt); + + if (calculatedRebaseAmt < minRebaseAmt) { + uint256 rebaseAmt = rebaseManager.fetchRebaseAmt(); + + assertEq(rebaseAmt, 0); + assertEq(rebaseManager.lastRebaseTS(), lastRebaseTS); + } else { + vm.expectEmit(address(dripper)); + emit Collected(_amount); + uint256 rebaseAmt = rebaseManager.fetchRebaseAmt(); + + assertEq(rebaseManager.lastRebaseTS(), block.timestamp); + + if (calculatedRebaseAmt > maxRebaseAmt) { + assertEq(rebaseAmt, maxRebaseAmt); + } else { + assertEq(rebaseAmt, calculatedRebaseAmt); + } + } + } + + // TODO remove this? function test_FetchRebaseAmt_Scenario() external { vm.prank(VAULT); rebaseManager.fetchRebaseAmt(); @@ -155,7 +223,7 @@ contract FetchRebaseAmt is RebaseManagerTest { // Minting USDs mintUSDs(1e11); vm.prank(USDS_OWNER); - IERC20(USDS).transfer(address(dripper), 1e22); + IERC20(USDS).transfer(address(dripper), 1e11); vm.startPrank(VAULT); dripper.collect(); diff --git a/test/strategy/AaveStrategy.t.sol b/test/strategy/AaveStrategy.t.sol index e30dca2e..ce09ca9d 100644 --- a/test/strategy/AaveStrategy.t.sol +++ b/test/strategy/AaveStrategy.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import {BaseStrategy} from "./BaseStrategy.t.sol"; import {BaseTest} from "../utils/BaseTest.sol"; import {UpgradeUtil} from "../utils/UpgradeUtil.sol"; -import {AaveStrategy} from "../../contracts/strategies/aave/AaveStrategy.sol"; +import {AaveStrategy, IAaveLendingPool} from "../../contracts/strategies/aave/AaveStrategy.sol"; import {InitializableAbstractStrategy, Helpers} from "../../contracts/strategies/InitializableAbstractStrategy.sol"; import {IERC20, ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; @@ -103,7 +103,6 @@ contract InitializeTests is AaveStrategyTest { strategy.initialize(address(0), VAULT); vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); - strategy.initialize(AAVE_POOL_PROVIDER, address(0)); } @@ -215,15 +214,79 @@ contract Deposit is AaveStrategyTest { strategy.deposit(ASSET, 0); } - function test_Deposit() public useKnownActor(VAULT) { + function testFuzz_Deposit(uint256 _depositAmount) public useKnownActor(VAULT) { + depositAmount = bound(_depositAmount, 1, 1e4 * 10 ** ERC20(ASSET).decimals()); // keeping a lesser upper limit to avoid supply cap error uint256 initial_bal = strategy.checkBalance(ASSET); + uint256 initialLPBalance = strategy.checkLPTokenBalance(ASSET); + assert(initialLPBalance == 0); deal(address(ASSET), VAULT, depositAmount); IERC20(ASSET).approve(address(strategy), depositAmount); - strategy.deposit(ASSET, 1); + strategy.deposit(ASSET, depositAmount); uint256 new_bal = strategy.checkBalance(ASSET); - assertEq(initial_bal + 1, new_bal); + uint256 newLPBalance = strategy.checkLPTokenBalance(ASSET); + assertEq(initial_bal + depositAmount, new_bal); + assertApproxEqAbs(initialLPBalance + depositAmount, newLPBalance, 1); + } +} + +contract DepositLPTest is AaveStrategyTest { + function setUp() public override { + super.setUp(); + vm.startPrank(USDS_OWNER); + _initializeStrategy(); + _setAssetData(); + vm.stopPrank(); + } + + function test_depositLp_RevertWhen_CollateralNotSupported() public useKnownActor(VAULT) { + vm.expectRevert(abi.encodeWithSelector(CollateralNotSupported.selector, DUMMY_ADDRESS)); + strategy.depositLp(DUMMY_ADDRESS, 1); + } + + function test_depositLp_RevertWhen_InvalidAmount() public useKnownActor(VAULT) { + vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAmount.selector)); + strategy.depositLp(ASSET, 0); + } + + function test_depositLp_RevertWhen_CallerNotVaultOrOwner() public useActor(0) { + vm.expectRevert(abi.encodeWithSelector(CallerNotVaultOrOwner.selector, actors[0])); + strategy.depositLp(ASSET, 1); + } + + function test_depositLp(bool _bool, uint256 _depositAmount) public { + address caller; + _bool ? caller = VAULT : caller = USDS_OWNER; + address pToken = strategy.assetToPToken(ASSET); + IAaveLendingPool aavePool = strategy.aavePool(); + depositAmount = bound(_depositAmount, 1, 1e4 * 10 ** ERC20(pToken).decimals()); + + vm.startPrank(caller); + + deal(ASSET, caller, depositAmount); + IERC20(ASSET).approve(address(aavePool), depositAmount); + aavePool.supply(ASSET, depositAmount, caller, 0); + IERC20(strategy.assetToPToken(ASSET)).approve(address(strategy), depositAmount); + + uint256 initialBalance = strategy.checkBalance(ASSET); + uint256 lpBalanceOfCallerBeforeDeposit = IERC20(pToken).balanceOf(caller); + uint256 lpBalanceOfStrategyBeforeDeposit = IERC20(pToken).balanceOf(address(strategy)); + uint256 allocatedAmountBeforeDeposit = strategy.allocatedAmount(ASSET); + + vm.expectEmit(address(strategy)); + emit Deposit(ASSET, depositAmount); + strategy.depositLp(ASSET, depositAmount); + + uint256 newBalance = strategy.checkBalance(ASSET); + uint256 lpBalanceOfCallerAfterDeposit = IERC20(pToken).balanceOf(caller); + uint256 lpBalanceOfStrategyAfterDeposit = IERC20(pToken).balanceOf(address(strategy)); + uint256 allocatedAmountAfterDeposit = strategy.allocatedAmount(ASSET); + + assertEq(initialBalance + depositAmount, newBalance); + assertApproxEqAbs(lpBalanceOfCallerBeforeDeposit, lpBalanceOfCallerAfterDeposit + depositAmount, 1); + assertApproxEqAbs(lpBalanceOfStrategyBeforeDeposit + depositAmount, lpBalanceOfStrategyAfterDeposit, 1); + assertEq(allocatedAmountBeforeDeposit + depositAmount, allocatedAmountAfterDeposit); } } diff --git a/test/strategy/CompoundStrategy.t.sol b/test/strategy/CompoundStrategy.t.sol index e9d9dff7..7edb5586 100644 --- a/test/strategy/CompoundStrategy.t.sol +++ b/test/strategy/CompoundStrategy.t.sol @@ -208,15 +208,20 @@ contract DepositTest is CompoundStrategyTest { strategy.deposit(ASSET, 0); } - function test_Deposit() public useKnownActor(VAULT) { + function testFuzz_Deposit(uint256 _depositAmount) public useKnownActor(VAULT) { + depositAmount = bound(_depositAmount, 1, 1e10 * 10 ** ERC20(ASSET).decimals()); uint256 initial_bal = strategy.checkBalance(ASSET); + uint256 initialLPBalance = strategy.checkLPTokenBalance(ASSET); + assert(initialLPBalance == 0); deal(ASSET, VAULT, depositAmount); IERC20(ASSET).approve(address(strategy), depositAmount); strategy.deposit(ASSET, depositAmount); uint256 new_bal = strategy.checkBalance(ASSET); + uint256 newLPBalance = strategy.checkLPTokenBalance(ASSET); assertEq(initial_bal + depositAmount, new_bal); + assertApproxEqAbs(initialLPBalance + depositAmount, newLPBalance, 2); } } @@ -338,10 +343,85 @@ contract CheckRewardEarnedTest is CompoundStrategyTest { strategy.deposit(ASSET, depositAmount); changePrank(USDS_OWNER); vm.warp(block.timestamp + 10 days); - vm.mockCall(P_TOKEN, abi.encodeWithSignature("baseTrackingAccrued(address)"), abi.encode(10000000)); + IReward.RewardConfig memory config = strategy.rewardPool().rewardConfig(P_TOKEN); + IReward.RewardOwed memory data = + IReward(address(strategy.rewardPool())).getRewardOwed(P_TOKEN, address(strategy)); + address rwdToken = data.token; + uint256 mockAccrued = 10000000; + vm.mockCall(P_TOKEN, abi.encodeWithSignature("baseTrackingAccrued(address)"), abi.encode(mockAccrued)); + uint256 accrued = mockAccrued; + if (config.shouldUpscale) { + accrued *= config.rescaleFactor; + } else { + accrued /= config.rescaleFactor; + } + accrued = ((accrued * config.multiplier) / 1e18); + uint256 collectibleAmount = accrued - strategy.rewardPool().rewardsClaimed(P_TOKEN, address(strategy)); + + IComet(P_TOKEN).accrueAccount(address(strategy)); + CompoundStrategy.RewardData[] memory rewardData = strategy.checkRewardEarned(); + assert(rewardData.length > 0); + // since P_TOKEN is in index 1 of data array checking for index 1 in rewardData + assertEq(rewardData[1].token, rwdToken); + assertEq(rewardData[1].amount, collectibleAmount); + } + + function test_CheckRewardEarned_shouldUpscaleTrue() public useKnownActor(USDS_OWNER) { + changePrank(VAULT); + deal(ASSET, VAULT, depositAmount); + IERC20(ASSET).approve(address(strategy), depositAmount); + strategy.deposit(ASSET, depositAmount); + changePrank(USDS_OWNER); + + vm.warp(block.timestamp + 10 days); + IReward.RewardConfig memory config = strategy.rewardPool().rewardConfig(P_TOKEN); + config.shouldUpscale = true; + IReward.RewardOwed memory data = + IReward(address(strategy.rewardPool())).getRewardOwed(P_TOKEN, address(strategy)); + address rwdToken = data.token; + uint256 mockAccrued = 10000000; + vm.mockCall(P_TOKEN, abi.encodeWithSignature("baseTrackingAccrued(address)"), abi.encode(mockAccrued)); + vm.mockCall(REWARD_POOL, abi.encodeWithSignature("rewardConfig(address)", P_TOKEN), abi.encode(config)); + uint256 accrued = mockAccrued; + + accrued *= config.rescaleFactor; + accrued = ((accrued * config.multiplier) / 1e18); + uint256 collectibleAmount = accrued - strategy.rewardPool().rewardsClaimed(P_TOKEN, address(strategy)); + IComet(P_TOKEN).accrueAccount(address(strategy)); + CompoundStrategy.RewardData[] memory rewardData = strategy.checkRewardEarned(); + assert(rewardData.length > 0); + // since P_TOKEN is in index 1 of data array checking for index 1 in rewardData + assertEq(rewardData[1].token, rwdToken); + assertEq(rewardData[1].amount, collectibleAmount); + } + + function test_CheckRewardEarned_shouldUpscaleFalse() public useKnownActor(USDS_OWNER) { + changePrank(VAULT); + deal(ASSET, VAULT, depositAmount); + IERC20(ASSET).approve(address(strategy), depositAmount); + strategy.deposit(ASSET, depositAmount); + changePrank(USDS_OWNER); + + vm.warp(block.timestamp + 10 days); + IReward.RewardConfig memory config = strategy.rewardPool().rewardConfig(P_TOKEN); + config.shouldUpscale = false; + IReward.RewardOwed memory data = + IReward(address(strategy.rewardPool())).getRewardOwed(P_TOKEN, address(strategy)); + address rwdToken = data.token; + uint256 mockAccrued = 10000000; + vm.mockCall(P_TOKEN, abi.encodeWithSignature("baseTrackingAccrued(address)"), abi.encode(mockAccrued)); + vm.mockCall(REWARD_POOL, abi.encodeWithSignature("rewardConfig(address)", P_TOKEN), abi.encode(config)); + uint256 accrued = mockAccrued; + + accrued /= config.rescaleFactor; + accrued = ((accrued * config.multiplier) / 1e18); + uint256 collectibleAmount = accrued - strategy.rewardPool().rewardsClaimed(P_TOKEN, address(strategy)); IComet(P_TOKEN).accrueAccount(address(strategy)); CompoundStrategy.RewardData[] memory rewardData = strategy.checkRewardEarned(); assert(rewardData.length > 0); + // since P_TOKEN is in index 1 of data array checking for index 1 in rewardData + assertEq(rewardData[1].token, rwdToken); + assertEq(rewardData[1].amount, collectibleAmount); } } diff --git a/test/strategy/StargateStrategy.t.sol b/test/strategy/StargateStrategy.t.sol index 8c3e95c6..13fd2d34 100644 --- a/test/strategy/StargateStrategy.t.sol +++ b/test/strategy/StargateStrategy.t.sol @@ -125,7 +125,7 @@ contract StargateStrategyTest is BaseStrategy, BaseTest { } } -contract InitializationTest is StargateStrategyTest { +contract Test_Initialization is StargateStrategyTest { function test_ValidInitialization() public useKnownActor(USDS_OWNER) { // Test state variables pre initialization assertEq(impl.owner(), address(0)); @@ -156,7 +156,7 @@ contract InitializationTest is StargateStrategyTest { _initializeStrategy(); address newVault = address(1); - vm.expectEmit(true, true, false, true); + vm.expectEmit(address(strategy)); emit VaultUpdated(newVault); strategy.updateVault(newVault); } @@ -165,7 +165,7 @@ contract InitializationTest is StargateStrategyTest { uint16 newRate = 100; _initializeStrategy(); - vm.expectEmit(true, false, false, true); + vm.expectEmit(address(strategy)); emit HarvestIncentiveRateUpdated(newRate); strategy.updateHarvestIncentiveRate(newRate); @@ -175,7 +175,7 @@ contract InitializationTest is StargateStrategyTest { } } -contract SetPToken is StargateStrategyTest { +contract Test_SetPToken is StargateStrategyTest { function setUp() public override { super.setUp(); vm.startPrank(USDS_OWNER); @@ -188,7 +188,7 @@ contract SetPToken is StargateStrategyTest { assertEq(strategy.assetToPToken(assetData[i].asset), address(0)); assertFalse(strategy.supportsCollateral(assetData[i].asset)); - vm.expectEmit(true, true, false, true); + vm.expectEmit(address(strategy)); emit PTokenAdded(assetData[i].asset, assetData[i].pToken); strategy.setPTokenAddress(assetData[i].asset, assetData[i].pToken, assetData[i].pid, assetData[i].rewardPid); @@ -241,7 +241,7 @@ contract SetPToken is StargateStrategyTest { } } -contract RemovePToken is StargateStrategyTest { +contract Test_RemovePToken is StargateStrategyTest { using stdStorage for StdStorage; function setUp() public override { @@ -255,7 +255,7 @@ contract RemovePToken is StargateStrategyTest { function test_RemovePToken() public useKnownActor(USDS_OWNER) { AssetData memory data = assetData[0]; assertTrue(strategy.supportsCollateral(data.asset)); - vm.expectEmit(true, true, false, true); + vm.expectEmit(address(strategy)); emit PTokenRemoved(data.asset, data.pToken); strategy.removePToken(0); assertFalse(strategy.supportsCollateral(data.asset)); @@ -285,7 +285,7 @@ contract RemovePToken is StargateStrategyTest { } } -contract ChangeSlippage is StargateStrategyTest { +contract Test_ChangeSlippage is StargateStrategyTest { uint16 public updatedDepositSlippage = 100; uint16 public updatedWithdrawSlippage = 200; @@ -297,7 +297,7 @@ contract ChangeSlippage is StargateStrategyTest { } function test_UpdateSlippage() public useKnownActor(USDS_OWNER) { - vm.expectEmit(true, false, false, true); + vm.expectEmit(address(strategy)); emit SlippageUpdated(updatedDepositSlippage, updatedWithdrawSlippage); strategy.updateSlippage(updatedDepositSlippage, updatedWithdrawSlippage); assertEq(strategy.depositSlippage(), updatedDepositSlippage); @@ -315,7 +315,7 @@ contract ChangeSlippage is StargateStrategyTest { } } -contract Deposit is StargateStrategyTest { +contract Test_Deposit is StargateStrategyTest { function setUp() public override { super.setUp(); vm.startPrank(USDS_OWNER); @@ -405,7 +405,7 @@ contract Deposit is StargateStrategyTest { } } -contract HarvestTest is StargateStrategyTest { +contract Test_Harvest is StargateStrategyTest { address public yieldReceiver; function setUp() public override { @@ -420,7 +420,7 @@ contract HarvestTest is StargateStrategyTest { } } -contract CollectReward is HarvestTest { +contract Test_CollectReward is Test_Harvest { function test_CollectReward(uint16 _harvestIncentiveRate) public { _harvestIncentiveRate = uint16(bound(_harvestIncentiveRate, 0, 10000)); vm.prank(USDS_OWNER); @@ -441,10 +441,10 @@ contract CollectReward is HarvestTest { address caller = actors[1]; if (incentiveAmt > 0) { - vm.expectEmit(true, true, false, true); + vm.expectEmit(address(strategy)); emit HarvestIncentiveCollected(E_TOKEN, caller, incentiveAmt); } - vm.expectEmit(true, true, false, true); + vm.expectEmit(address(strategy)); emit RewardTokenCollected(E_TOKEN, yieldReceiver, harvestAmt); vm.prank(caller); strategy.collectReward(); @@ -457,7 +457,7 @@ contract CollectReward is HarvestTest { } } -contract CollectInterest is HarvestTest { +contract Test_CollectInterest is Test_Harvest { using stdStorage for StdStorage; function test_CollectInterest() public { @@ -481,11 +481,31 @@ contract CollectInterest is HarvestTest { assertEq(strategy.checkLPTokenBalance(assetData[i].asset), mockBal); assertTrue(interestEarned > 0); - vm.expectEmit(true, false, false, false); - emit InterestCollected(assetData[i].asset, yieldReceiver, interestEarned); + vm.expectEmit(true, true, false, false); + emit InterestCollected(assetData[i].asset, yieldReceiver, interestEarned); // used vm.recordLogs to test the interestEarned part due to precision error + + vm.recordLogs(); + strategy.collectInterest(assetData[i].asset); + + VmSafe.Log[] memory logs = vm.getRecordedLogs(); + + uint256 amt; + for (uint8 j = 0; j < logs.length; ++j) { + if (logs[j].topics[0] == keccak256("InterestCollected(address,address,uint256)")) { + (amt) = abi.decode(logs[j].data, (uint256)); + } + } + + uint256 incentiveAmt = (interestEarned * 10) / 10000; + uint256 harvestAmount = interestEarned - incentiveAmt; + + // TODO need to check why there is a big delta + assertApproxEqAbs(amt, harvestAmount, (harvestAmount / 1e7)); + /// @note precision Error from stargate assertApproxEqAbs(strategy.checkLPTokenBalance(assetData[i].asset), initialLPBal, 1); + assertEq(strategy.checkInterestEarned(assetData[i].asset), 0); } } @@ -495,7 +515,7 @@ contract CollectInterest is HarvestTest { } } -contract Withdraw is StargateStrategyTest { +contract Test_Withdraw is StargateStrategyTest { using stdStorage for StdStorage; function setUp() public override { @@ -512,9 +532,25 @@ contract Withdraw is StargateStrategyTest { uint256 initialBal = strategy.checkBalance(assetData[i].asset); uint256 initialVaultBal = collateral.balanceOf(VAULT); - vm.expectEmit(true, false, false, false); + vm.expectEmit(true, false, false, false); // used vm.recordLogs to test the initialBal part due to precision error emit Withdrawal(assetData[i].asset, initialBal); + + vm.recordLogs(); + strategy.withdraw(VAULT, assetData[i].asset, initialBal); + + VmSafe.Log[] memory logs = vm.getRecordedLogs(); + + uint256 amt; + for (uint8 j = 0; j < logs.length; ++j) { + if (logs[j].topics[0] == keccak256("Withdrawal(address,uint256)")) { + (amt) = abi.decode(logs[j].data, (uint256)); + } + } + + // TODO need to check why there is a big delta + assertApproxEqAbs(amt, initialBal, (initialBal / 1e7)); + assertApproxEqAbs( collateral.balanceOf(VAULT), initialVaultBal + initialBal, @@ -544,9 +580,25 @@ contract Withdraw is StargateStrategyTest { uint256 initialBal = strategy.checkBalance(assetData[i].asset); uint256 initialVaultBal = collateral.balanceOf(VAULT); - vm.expectEmit(true, false, false, false); + vm.expectEmit(true, false, false, false); // used vm.recordLogs to test the initialBal part due to precision error emit Withdrawal(assetData[i].asset, initialBal); + + vm.recordLogs(); + strategy.withdrawToVault(assetData[i].asset, initialBal); + + VmSafe.Log[] memory logs = vm.getRecordedLogs(); + + uint256 amt; + for (uint8 j = 0; j < logs.length; ++j) { + if (logs[j].topics[0] == keccak256("Withdrawal(address,uint256)")) { + (amt) = abi.decode(logs[j].data, (uint256)); + } + } + + // TODO need to check why there is a big delta + assertApproxEqAbs(amt, initialBal, (initialBal / 1e7)); + assertApproxEqAbs( collateral.balanceOf(VAULT), initialVaultBal + initialBal, @@ -602,7 +654,41 @@ contract Withdraw is StargateStrategyTest { } } -contract EdgeCases is StargateStrategyTest { +contract Test_EmergencyWithdrawToVault is StargateStrategyTest { + function setUp() public override { + super.setUp(); + vm.startPrank(USDS_OWNER); + _initializeStrategy(); + _createDeposits(); + vm.stopPrank(); + } + + function test_emergencyWithdrawToVault() public useKnownActor(USDS_OWNER) { + for (uint8 i = 0; i < assetData.length; ++i) { + uint256 initialBal = strategy.checkBalance(assetData[i].asset); + uint256 initialVaultBal = ERC20(assetData[i].asset).balanceOf(VAULT); + + (uint256 allocatedAmtBeforeEmergencyWithdraw,,) = strategy.assetInfo(assetData[i].asset); + + vm.expectEmit(address(strategy)); + emit Withdrawal(assetData[i].asset, initialBal); + strategy.emergencyWithdrawToVault(assetData[i].asset); + + (uint256 allocatedAmtAfterEmergencyWithdraw,,) = strategy.assetInfo(assetData[i].asset); + + assertEq(strategy.checkLPTokenBalance(assetData[i].asset), 0); + assertEq(strategy.checkBalance(assetData[i].asset), 0); + assertEq(allocatedAmtAfterEmergencyWithdraw, allocatedAmtBeforeEmergencyWithdraw - initialBal); + assertApproxEqAbs( + ERC20(assetData[i].asset).balanceOf(VAULT), + initialVaultBal + initialBal, + 1 * IStargatePool(assetData[i].pToken).convertRate() + ); + } + } +} + +contract Test_EdgeCases is StargateStrategyTest { function setUp() public override { super.setUp(); vm.startPrank(USDS_OWNER); @@ -624,7 +710,7 @@ contract EdgeCases is StargateStrategyTest { } } -contract TestRecoverERC20 is StargateStrategyTest { +contract Test_RecoverERC20 is StargateStrategyTest { address token; address receiver; uint256 amount; @@ -640,12 +726,12 @@ contract TestRecoverERC20 is StargateStrategyTest { amount = 1000 * 10 ** ERC20(token).decimals(); } - function test_RevertsWhen_CallerIsNotOwner() public { + function test_RevertWhen_CallerIsNotOwner() public { vm.expectRevert("Ownable: caller is not the owner"); strategy.recoverERC20(token, receiver, amount); } - function test_RevertsWhen_AmountMoreThanBalance() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_AmountMoreThanBalance() public useKnownActor(USDS_OWNER) { vm.expectRevert(); strategy.recoverERC20(token, receiver, amount); } @@ -659,7 +745,7 @@ contract TestRecoverERC20 is StargateStrategyTest { } } -contract TestUpdateFarm is StargateStrategyTest { +contract Test_UpdateFarm is StargateStrategyTest { address _newFarm; function setUp() public override { @@ -671,7 +757,7 @@ contract TestUpdateFarm is StargateStrategyTest { _newFarm = 0xeA8DfEE1898a7e0a59f7527F076106d7e44c2176; } - function test_revertsWhen_CallerIsNotOwner() public { + function test_RevertWhen_CallerIsNotOwner() public { vm.expectRevert("Ownable: caller is not the owner"); strategy.updateFarm(_newFarm, address(0)); } @@ -683,7 +769,7 @@ contract TestUpdateFarm is StargateStrategyTest { for (uint8 i = 0; i < _length; ++i) { (oldFarmBalances[i],) = ILPStaking(STARGATE_FARM).userInfo(assetData[i].rewardPid, address(strategy)); } - vm.expectEmit(true, true, true, true); + vm.expectEmit(address(strategy)); emit FarmUpdated(_newFarm); strategy.updateFarm(_newFarm, STARGATE); for (uint8 i = 0; i < assetData.length; ++i) { diff --git a/test/token/USDs.t.sol b/test/token/USDs.t.sol index 42c8a9d9..cb86b9e7 100644 --- a/test/token/USDs.t.sol +++ b/test/token/USDs.t.sol @@ -42,6 +42,10 @@ contract USDsUpgradabilityTest is BaseTest { contract USDsTest is BaseTest { using StableMath for uint256; + uint256 constant APPROX_ERROR_MARGIN = 1; + uint256 constant FULL_SCALE = 1e18; + uint256 MAX_SUPPLY = ~uint128(0); + uint256 USDsPrecision; USDs internal usds; USDs internal impl; @@ -51,6 +55,12 @@ contract USDsTest is BaseTest { address internal USER1; address internal USER2; + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + event TotalSupplyUpdated(uint256 totalSupply, uint256 rebasingCredits, uint256 rebasingCreditsPerToken); + event Paused(bool isPaused); + event VaultUpdated(address newVault); event RebaseOptIn(address indexed account); event RebaseOptOut(address indexed account); @@ -60,8 +70,8 @@ contract USDsTest is BaseTest { _; // @note account for precision error in USDs calculation for rebasing accounts - assertApproxEqAbs(prevBalUser1 - amountToTransfer, usds.balanceOf(USER1), 1); - assertApproxEqAbs(prevBalUser2 + amountToTransfer, usds.balanceOf(USER2), 1); + assertApproxEqAbs(prevBalUser1 - amountToTransfer, usds.balanceOf(USER1), APPROX_ERROR_MARGIN); + assertApproxEqAbs(prevBalUser2 + amountToTransfer, usds.balanceOf(USER2), APPROX_ERROR_MARGIN); } function setUp() public virtual override { @@ -70,7 +80,6 @@ contract USDsTest is BaseTest { USER1 = actors[0]; USER2 = actors[1]; upgradeUtil = new UpgradeUtil(); - USDsPrecision = 10 ** ERC20(USDS).decimals(); USDs usdsImpl = new USDs(); @@ -82,11 +91,6 @@ contract USDsTest is BaseTest { usds.updateVault(VAULT); vm.stopPrank(); } - - function test_change_vault() public useKnownActor(USDS_OWNER) { - usds.updateVault(USER1); - assertEq(USER1, usds.vault()); - } } contract TestInitialize is USDsTest { @@ -104,7 +108,7 @@ contract TestInitialize is USDsTest { newUsds = USDs(upgradeUtil.deployErc1967Proxy(address(usdsImpl))); } - function test_revertWhen_InvalidAddress() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_InvalidAddress() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); newUsds.initialize(tokenName, tokenSymbol, address(0)); } @@ -119,7 +123,7 @@ contract TestInitialize is USDsTest { assertEq(currentActor, newUsds.owner()); } - function test_revertWhen_AlreadyInitialized() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_AlreadyInitialized() public useKnownActor(USDS_OWNER) { vm.expectRevert("Initializable: contract is already initialized"); usds.initialize(tokenName, tokenSymbol, VAULT); } @@ -137,28 +141,14 @@ contract TestTransferFrom is USDsTest { vm.stopPrank(); } - function test_transfer_from(uint256 amount1) public useKnownActor(USER1) { - amount1 = bound(amount1, 1, usds.balanceOf(USER1)); - uint256 prevBalUser1 = usds.balanceOf(USER1); - uint256 prevBalUser2 = usds.balanceOf(USER2); - usds.approve(VAULT, amount1); - - changePrank(VAULT); - usds.transferFrom(USER1, USER2, amount1); - - // @note account for precision error in USDs calculation for rebasing accounts - assertApproxEqAbs(prevBalUser1 - amount1, usds.balanceOf(USER1), 1); - assertApproxEqAbs(prevBalUser2 + amount1, usds.balanceOf(USER2), 1); - } - - function test_transfer_from_without_approval() public useKnownActor(VAULT) { + function test_RevertWhen_InsufficientAllowance() public useKnownActor(VAULT) { uint256 amountToTransfer = usds.balanceOf(USER1); vm.expectRevert(bytes("Insufficient allowance")); usds.transferFrom(USER1, USER2, amountToTransfer); } - function test_revert_balance() public useKnownActor(VAULT) { + function test_RevertWhen_TransferGreaterThanBal() public useKnownActor(VAULT) { uint256 amountToTransfer = usds.balanceOf(USER1) + 1; vm.expectRevert( @@ -167,13 +157,45 @@ contract TestTransferFrom is USDsTest { usds.transferFrom(USER1, USER2, amountToTransfer); } - function test_revert_invalid_input() public useKnownActor(USER1) { + function test_RevertWhen_TransferToZeroAddr() public useKnownActor(USER1) { uint256 amountToTransfer = usds.balanceOf(USER1); vm.expectRevert(abi.encodeWithSelector(USDs.TransferToZeroAddr.selector)); usds.transferFrom(USER1, address(0), amountToTransfer); } + function test_RevertWhen_ContractPaused() public useKnownActor(USER1) { + changePrank(USDS_OWNER); + usds.pauseSwitch(true); + changePrank(USER1); + + usds.approve(VAULT, amount); + changePrank(VAULT); + + vm.expectRevert(abi.encodeWithSelector(USDs.ContractPaused.selector)); + usds.transferFrom(USER1, USER2, amount); + } + + function testFuzz_transferFrom(uint256 amount1) public useKnownActor(USER1) { + amount1 = bound(amount1, 1, usds.balanceOf(USER1)); + uint256 prevBalUser1 = usds.balanceOf(USER1); + uint256 prevBalUser2 = usds.balanceOf(USER2); + + vm.expectEmit(address(usds)); + emit Approval(USER1, VAULT, amount1); + usds.approve(VAULT, amount1); + + changePrank(VAULT); + vm.expectEmit(address(usds)); + emit Transfer(USER1, USER2, amount1); + usds.transferFrom(USER1, USER2, amount1); + + // @note account for precision error in USDs calculation for rebasing accounts + assertApproxEqAbs(prevBalUser1 - amount1, usds.balanceOf(USER1), APPROX_ERROR_MARGIN); + assertApproxEqAbs(prevBalUser2 + amount1, usds.balanceOf(USER2), APPROX_ERROR_MARGIN); + assertEq(usds.allowance(USER1, VAULT), 0); + } + function test_increaseAllowance() public useKnownActor(USER1) { uint256 currentAllowance = usds.allowance(USER1, VAULT); usds.increaseAllowance(VAULT, amount); @@ -197,10 +219,6 @@ contract TestTransferFrom is USDsTest { assertEq(0, usds.allowance(USER1, VAULT)); } - - function test_allowance() public useKnownActor(USER1) { - usds.allowance(USER1, VAULT); - } } contract TestTransfer is USDsTest { @@ -214,16 +232,42 @@ contract TestTransfer is USDsTest { vm.stopPrank(); } - function test_transfer(uint256 amount1) public useKnownActor(USER1) { + function test_RevertWhen_TransferGreaterThanBal() public useKnownActor(USER1) { + uint256 bal = usds.balanceOf(USER1); + uint256 amountToTransfer = bal + 1; + + vm.expectRevert(abi.encodeWithSelector(USDs.TransferGreaterThanBal.selector, amountToTransfer, bal)); + usds.transfer(USER2, amountToTransfer); + } + + function test_RevertWhen_TransferToZeroAddr() public useKnownActor(USER1) { + uint256 amountToTransfer = usds.balanceOf(USER1); + + vm.expectRevert(abi.encodeWithSelector(USDs.TransferToZeroAddr.selector)); + usds.transfer(address(0), amountToTransfer); + } + + function test_RevertWhen_ContractPaused() public { + changePrank(USDS_OWNER); + usds.pauseSwitch(true); + changePrank(USER1); + + vm.expectRevert(abi.encodeWithSelector(USDs.ContractPaused.selector)); + usds.transfer(USER2, amount); + } + + function testFuzz_transfer(uint256 amount1) public useKnownActor(USER1) { amount1 = bound(amount1, 1, usds.balanceOf(USER1)); uint256 prevBalUser1 = usds.balanceOf(USER1); uint256 prevBalUser2 = usds.balanceOf(USER2); + vm.expectEmit(address(usds)); + emit Transfer(USER1, USER2, amount1); usds.transfer(USER2, amount1); // @note account for precision error in USDs calculation for rebasing accounts - assertApproxEqAbs(prevBalUser1 - amount1, usds.balanceOf(USER1), 1); - assertApproxEqAbs(prevBalUser2 + amount1, usds.balanceOf(USER2), 1); + assertApproxEqAbs(prevBalUser1 - amount1, usds.balanceOf(USER1), APPROX_ERROR_MARGIN); + assertApproxEqAbs(prevBalUser2 + amount1, usds.balanceOf(USER2), APPROX_ERROR_MARGIN); } function test_transfer_sender_non_rebasing_from() @@ -268,23 +312,14 @@ contract TestTransfer is USDsTest { usds.transfer(USER2, usds.balanceOf(USER1)); } - function test_revert_balance() public useKnownActor(USER1) { - uint256 bal = usds.balanceOf(USER1); - uint256 amountToTransfer = bal + 1; - - vm.expectRevert(abi.encodeWithSelector(USDs.TransferGreaterThanBal.selector, amountToTransfer, bal)); - usds.transfer(USER2, amountToTransfer); - } - - function test_revert_invalid_input() public useKnownActor(USER1) { - uint256 amountToTransfer = usds.balanceOf(USER1); + function test_transfer_both_non_rebasing() public useKnownActor(USDS_OWNER) testTransfer(usds.balanceOf(USER1)) { + changePrank(USER2); + usds.rebaseOptOut(); - vm.expectRevert(abi.encodeWithSelector(USDs.TransferToZeroAddr.selector)); - usds.transfer(address(0), amountToTransfer); - } + changePrank(USER1); + usds.rebaseOptOut(); - function test_creditsBalanceOf() public useKnownActor(USER1) { - usds.creditsBalanceOf(USER1); + usds.transfer(USER2, usds.balanceOf(USER1)); } } @@ -296,41 +331,71 @@ contract TestMint is USDsTest { amount = 10 * USDsPrecision; } - function test_mint_owner_check() public useActor(0) { - vm.expectRevert(abi.encodeWithSelector(USDs.CallerNotVault.selector, actors[0])); + function test_RevertWhen_CallerNotVault() public useKnownActor(USER1) { + vm.expectRevert(abi.encodeWithSelector(USDs.CallerNotVault.selector, USER1)); + usds.mint(USDS_OWNER, amount); + } + + function test_RevertWhen_ContractPaused() public useKnownActor(USDS_OWNER) { + usds.pauseSwitch(true); + changePrank(VAULT); + + vm.expectRevert(abi.encodeWithSelector(USDs.ContractPaused.selector)); usds.mint(USDS_OWNER, amount); } - function test_mint_to_the_zero() public useKnownActor(VAULT) { + function test_RevertWhen_MintToZeroAddr() public useKnownActor(VAULT) { vm.expectRevert(abi.encodeWithSelector(USDs.MintToZeroAddr.selector)); usds.mint(address(0), amount); } - function test_max_supply() public useKnownActor(VAULT) { - uint256 MAX_SUPPLY = ~uint128(0); + function test_RevertWhen_MaxSupplyReached() public useKnownActor(VAULT) { vm.expectRevert(abi.encodeWithSelector(USDs.MaxSupplyReached.selector, MAX_SUPPLY + usds.totalSupply())); usds.mint(USDS_OWNER, MAX_SUPPLY); } - function test_mint_paused() public useKnownActor(USDS_OWNER) { - usds.pauseSwitch(true); - changePrank(VAULT); + function testFuzz_mint_nonRebasing(uint256 _amount) public useKnownActor(VAULT) { + amount = bound(_amount, 1, MAX_SUPPLY - usds.totalSupply()); - vm.expectRevert(abi.encodeWithSelector(USDs.ContractPaused.selector)); - usds.mint(USDS_OWNER, amount); + address account = USDS_OWNER; // USDS_OWNER is non-rebasing + uint256 prevTotalSupply = usds.totalSupply(); + uint256 prevNonRebasingSupply = usds.nonRebasingSupply(); - changePrank(USDS_OWNER); - usds.pauseSwitch(false); + vm.expectEmit(address(usds)); + emit Transfer(address(0), account, amount); + usds.mint(account, amount); - changePrank(VAULT); - usds.mint(USDS_OWNER, amount); - assertEq(usds.balanceOf(USDS_OWNER), amount); + assertEq(usds.balanceOf(account), amount); + assertEq(usds.totalSupply(), prevTotalSupply + amount); + + (uint256 creditBalance, uint256 creditPerToken) = usds.creditsBalanceOf(account); + assertEq(usds.nonRebasingCreditsPerToken(account), 1); + assertEq(usds.nonRebasingSupply(), prevNonRebasingSupply + amount); + assertEq(creditBalance, amount); + assertEq(creditPerToken, 1); } - function test_mint() public useKnownActor(VAULT) { - usds.mint(USDS_OWNER, amount); + function testFuzz_mint_rebasing(uint256 _amount) public useKnownActor(VAULT) { + amount = bound(_amount, 1, MAX_SUPPLY - usds.totalSupply()); + address account = USER1; - assertEq(usds.balanceOf(USDS_OWNER), amount); + uint256 prevTotalSupply = usds.totalSupply(); + uint256 prevNonRebasingSupply = usds.nonRebasingSupply(); + uint256 rebasingCreditsPerToken = usds.rebasingCreditsPerToken(); + + vm.expectEmit(address(usds)); + emit Transfer(address(0), account, amount); + usds.mint(account, amount); + + assertApproxEqAbs(usds.balanceOf(account), amount, APPROX_ERROR_MARGIN); + assertEq(usds.totalSupply(), prevTotalSupply + amount); + + // Checks as USDS_OWNER is rebasing + (uint256 creditBalance, uint256 creditPerToken) = usds.creditsBalanceOf(account); + assertEq(usds.nonRebasingCreditsPerToken(account), 0); + assertEq(usds.nonRebasingSupply(), prevNonRebasingSupply); + assertApproxEqAbs(creditBalance, (amount * rebasingCreditsPerToken) / FULL_SCALE, APPROX_ERROR_MARGIN); + assertEq(creditPerToken, rebasingCreditsPerToken); } } @@ -344,79 +409,98 @@ contract TestBurn is USDsTest { amount = 100000; } - function test_burn_opt_in() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_ContractPaused() public useKnownActor(USDS_OWNER) { + usds.pauseSwitch(true); changePrank(VAULT); - usds.rebaseOptIn(); - usds.rebaseOptOut(); - - usds.mint(VAULT, amount); - - uint256 prevSupply = usds.totalSupply(); - uint256 prevNonRebasingSupply = usds.nonRebasingSupply(); - uint256 preBalance = usds.balanceOf(VAULT); + vm.expectRevert(abi.encodeWithSelector(USDs.ContractPaused.selector)); usds.burn(amount); - assertEq(usds.totalSupply(), prevSupply - amount); - assertEq(usds.nonRebasingSupply(), prevNonRebasingSupply - amount); - assertEq(usds.balanceOf(VAULT), preBalance - amount); } - function test_credit_amount_changes_case1() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_InsufficientBalance() public useKnownActor(USDS_OWNER) { changePrank(VAULT); usds.rebaseOptIn(); - usds.mint(VAULT, amount); - uint256 creditAmount = amount.mulTruncate(usds.rebasingCreditsPerToken()); - (uint256 currentCredits,) = usds.creditsBalanceOf(VAULT); + vm.expectRevert("Insufficient balance"); + amount = 1000000000 * USDsPrecision; usds.burn(amount); + } - (uint256 newCredits,) = usds.creditsBalanceOf(VAULT); - - assertEq(newCredits, currentCredits - creditAmount); + function test_burn_noChange() public useKnownActor(USDS_OWNER) { + uint256 prevSupply = usds.totalSupply(); + usds.burn(0); + assertEq(usds.totalSupply(), prevSupply); } - function test_burn_case2() public useKnownActor(USDS_OWNER) { - changePrank(VAULT); - usds.rebaseOptIn(); + function test_burn_nonRebasing(uint256 _amount) public useKnownActor(USDS_OWNER) { + amount = bound(_amount, 1, MAX_SUPPLY - usds.totalSupply()); + address account = VAULT; // VAULT is rebasing and has some existing USDs. - usds.transfer(USER1, usds.balanceOf(VAULT)); - usds.mint(VAULT, amount); + changePrank(account); + usds.mint(account, amount); + + uint256 prevSupply = usds.totalSupply(); + uint256 prevNonRebasingSupply = usds.nonRebasingSupply(); + uint256 preBalance = usds.balanceOf(account); + (uint256 prevCreditBalance,) = usds.creditsBalanceOf(account); - uint256 bal = usds.balanceOf(VAULT); + vm.expectEmit(address(usds)); + emit Transfer(account, address(0), amount); usds.burn(amount); - // account for mathematical - assertApproxEqAbs(bal - amount, usds.balanceOf(VAULT), 1); + assertEq(usds.balanceOf(account), preBalance - amount); + assertEq(usds.totalSupply(), prevSupply - amount); + (uint256 creditBalance, uint256 creditPerToken) = usds.creditsBalanceOf(account); + assertEq(usds.nonRebasingCreditsPerToken(account), 1); + assertEq(usds.nonRebasingSupply(), prevNonRebasingSupply - amount); + assertEq(creditBalance, prevCreditBalance - amount); + assertEq(creditPerToken, 1); } - function test_burn_case3() public useKnownActor(USDS_OWNER) { - changePrank(VAULT); + function test_burn_rebasing(uint256 _amount) public useKnownActor(USDS_OWNER) { + amount = bound(_amount, 1, MAX_SUPPLY - usds.totalSupply()); + address account = VAULT; // VAULT is non-rebasing and has some existing USDs. + changePrank(account); + usds.burn(usds.balanceOf(account)); // burns off any exiting balance for upcoming calculations. usds.rebaseOptIn(); + usds.mint(account, amount); - vm.expectRevert("Insufficient balance"); - amount = 1000000000 * USDsPrecision; - usds.burn(amount); - } - - function test_burn() public useKnownActor(VAULT) { - usds.mint(VAULT, amount); uint256 prevSupply = usds.totalSupply(); + uint256 prevNonRebasingSupply = usds.nonRebasingSupply(); + uint256 preBalance = usds.balanceOf(account); + (uint256 prevCreditBalance,) = usds.creditsBalanceOf(account); + uint256 rebasingCreditsPerToken = usds.rebasingCreditsPerToken(); + uint256 creditAmount = amount.mulTruncate(rebasingCreditsPerToken); + + vm.expectEmit(address(usds)); + emit Transfer(account, address(0), amount); usds.burn(amount); + + assertApproxEqAbs(usds.balanceOf(account), amount - preBalance, APPROX_ERROR_MARGIN); assertEq(usds.totalSupply(), prevSupply - amount); + (uint256 creditBalance, uint256 creditPerToken) = usds.creditsBalanceOf(account); + assertEq(usds.nonRebasingCreditsPerToken(account), 0); + assertEq(usds.nonRebasingSupply(), prevNonRebasingSupply); + assertApproxEqAbs( + creditBalance, prevCreditBalance - (amount * rebasingCreditsPerToken) / FULL_SCALE, APPROX_ERROR_MARGIN + ); + assertEq(creditBalance, prevCreditBalance - creditAmount); + assertEq(creditPerToken, rebasingCreditsPerToken); } } -contract TestRebase is USDsTest { - function setUp() public override { - super.setUp(); - } - +contract TestRebaseOptIn is USDsTest { function test_revertIf_IsAlreadyRebasingAccount() public useKnownActor(USDS_OWNER) { usds.rebaseOptIn(); vm.expectRevert(abi.encodeWithSelector(USDs.IsAlreadyRebasingAccount.selector, USDS_OWNER)); usds.rebaseOptIn(); } + function test_RevertWhen_CallerNotOwner() public useKnownActor(VAULT) { + vm.expectRevert("Ownable: caller is not the owner"); + usds.rebaseOptIn(currentActor); + } + function test_rebaseOptIn() public useKnownActor(USDS_OWNER) { vm.expectEmit(address(usds)); emit RebaseOptIn(USDS_OWNER); @@ -425,12 +509,27 @@ contract TestRebase is USDsTest { assertEq(usds.nonRebasingCreditsPerToken(USDS_OWNER), 0); } + function test_rebaseOptIn_with_account_param() public useKnownActor(USDS_OWNER) { + address account = VAULT; + vm.expectEmit(address(usds)); + emit RebaseOptIn(account); + usds.rebaseOptIn(account); + + assertEq(usds.nonRebasingCreditsPerToken(account), 0); + } +} + +contract TestRebaseOptOut is USDsTest { function test_revertIf_IsAlreadyNonRebasingAccount() public useKnownActor(USDS_OWNER) { - // usds.rebaseOptOut(); vm.expectRevert(abi.encodeWithSelector(USDs.IsAlreadyNonRebasingAccount.selector, USDS_OWNER)); usds.rebaseOptOut(); } + function test_RevertWhen_CallerNotOwner() public useKnownActor(USER1) { + vm.expectRevert("Ownable: caller is not the owner"); + usds.rebaseOptOut(currentActor); + } + function test_rebaseOptOut() public useKnownActor(USDS_OWNER) { usds.rebaseOptIn(); @@ -441,31 +540,80 @@ contract TestRebase is USDsTest { assertEq(usds.nonRebasingCreditsPerToken(USDS_OWNER), 1); } - function test_pauseSwitch() public useKnownActor(USDS_OWNER) { - usds.pauseSwitch(true); - assertEq(usds.paused(), true); + function test_rebaseOptOut_with_account_param() public useKnownActor(USDS_OWNER) { + address account = VAULT; + changePrank(account); + usds.rebaseOptIn(); + changePrank(USDS_OWNER); + + vm.expectEmit(address(usds)); + emit RebaseOptOut(account); + usds.rebaseOptOut(account); + + assertEq(usds.nonRebasingCreditsPerToken(account), 1); } +} - function test_rebase() public useKnownActor(VAULT) { - uint256 amount = 1000000000 * USDsPrecision; - usds.mint(VAULT, amount); +contract TestRebase is USDsTest { + using StableMath for uint256; + + function test_RevertWhen_CallerNotVault() public useKnownActor(USER1) { + vm.expectRevert(abi.encodeWithSelector(USDs.CallerNotVault.selector, USER1)); + usds.rebase(1); + } + + function test_RevertWhen_CannotIncreaseZeroSupply() public { + address account = VAULT; + changePrank(account); + USDs usdsImpl = new USDs(); + USDs newUsds = USDs(upgradeUtil.deployErc1967Proxy(address(usdsImpl))); + newUsds.initialize("a", "b", account); + uint256 amount = 100000 * USDsPrecision; + newUsds.mint(account, amount); + assert(newUsds.totalSupply() == amount); + assert(newUsds.balanceOf(account) == amount); + + vm.expectRevert(abi.encodeWithSelector(USDs.CannotIncreaseZeroSupply.selector)); + newUsds.rebase(amount); + } + + function test_rebase(uint256 amount) public useKnownActor(VAULT) { + amount = bound(amount, 1, MAX_SUPPLY - usds.totalSupply()); + address account = VAULT; + usds.mint(account, amount); uint256 prevSupply = usds.totalSupply(); - usds.rebase(100000 * USDsPrecision); + uint256 prevNonRebasingSupply = usds.nonRebasingSupply(); + uint256 newNonRebasingSupply = prevNonRebasingSupply - amount; + uint256 rebasingCredits = (prevSupply - prevNonRebasingSupply).mulTruncate(usds.rebasingCreditsPerToken()); + uint256 rebasingCreditsPerToken = rebasingCredits.divPrecisely(prevSupply - newNonRebasingSupply); + + vm.expectEmit(address(usds)); + emit Transfer(account, address(0), amount); + vm.expectEmit(address(usds)); + emit TotalSupplyUpdated(prevSupply, rebasingCredits, rebasingCreditsPerToken); + usds.rebase(amount); assertEq(prevSupply, usds.totalSupply()); + assertEq(rebasingCreditsPerToken, usds.rebasingCreditsPerToken()); + assertEq(newNonRebasingSupply, usds.nonRebasingSupply()); } function test_rebase_no_supply_change() public useKnownActor(VAULT) { + uint256 amount = 0; uint256 prevSupply = usds.totalSupply(); + uint256 nonRebasingSupply = usds.nonRebasingSupply(); + uint256 rebasingCreditsPerToken = usds.rebasingCreditsPerToken(); + uint256 rebasingCredits = (prevSupply - nonRebasingSupply).mulTruncate(rebasingCreditsPerToken); - uint256 amount = 0; + vm.expectEmit(address(usds)); + emit TotalSupplyUpdated(prevSupply, rebasingCredits, rebasingCreditsPerToken); usds.rebase(amount); assertEq(prevSupply, usds.totalSupply()); } - function test_rebase_opt_in() public useKnownActor(USDS_OWNER) { + function test_rebase_opt_in() public { changePrank(VAULT); usds.rebaseOptIn(); uint256 amount = 100000; @@ -475,7 +623,7 @@ contract TestRebase is USDsTest { assertEq(prevSupply, usds.totalSupply()); } - function test_rebase_opt_out() public useKnownActor(USDS_OWNER) { + function test_rebase_opt_out() public { changePrank(VAULT); usds.rebaseOptIn(); usds.rebaseOptOut(); @@ -487,6 +635,35 @@ contract TestRebase is USDsTest { } } +contract TestUpdateVault is USDsTest { + function test_revert_CallerNotOwner() public useKnownActor(USER1) { + vm.expectRevert("Ownable: caller is not the owner"); + usds.updateVault(USER1); + } + + function testFuzz_change_vault(address _newVault) public useKnownActor(USDS_OWNER) { + vm.assume(_newVault != address(0)); + vm.expectEmit(address(usds)); + emit VaultUpdated(_newVault); + usds.updateVault(_newVault); + assertEq(_newVault, usds.vault()); + } +} + +contract TestPauseSwitch is USDsTest { + function test_RevertWhen_CallerNotOwner() public useKnownActor(USER1) { + vm.expectRevert("Ownable: caller is not the owner"); + usds.pauseSwitch(true); + } + + function testFuzz_pauseSwitch(bool _bool) public useKnownActor(USDS_OWNER) { + vm.expectEmit(address(usds)); + emit Paused(_bool); + usds.pauseSwitch(_bool); + assertEq(usds.paused(), _bool); + } +} + contract TestEnsureRebasingMigration is USDsTest { using StableMath for uint256; diff --git a/test/utils/BaseTest.sol b/test/utils/BaseTest.sol index bf8344aa..e806259f 100644 --- a/test/utils/BaseTest.sol +++ b/test/utils/BaseTest.sol @@ -29,7 +29,6 @@ abstract contract Setup is Test { address internal ORACLE; address internal DRIPPER; address internal REBASE_MANAGER; - address internal BUYBACK; address internal STARGATE_STRATEGY; address internal AAVE_STRATEGY; address internal FEE_VAULT; @@ -89,11 +88,10 @@ abstract contract BaseTest is Setup { SPA = 0x5575552988A3A80504bBaeB1311674fCFd40aD4B; USDS_OWNER = 0x5b12d9846F8612E439730d18E1C12634753B1bF1; PROXY_ADMIN = 0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25; - DRIPPER = 0xf3f98086f7B61a32be4EdF8d8A4b964eC886BBcd; // dummy addr - REBASE_MANAGER = 0xf3f98086f7B61a32be4EdF8d8A4b964eC886BBcd; // dummy addr + DRIPPER = 0xd50193e8fFb00beA274bD2b11d0a7Ea08dA044c1; + REBASE_MANAGER = 0x297331A0155B1e30bBFA85CF3609eC0fF037BEEC; SPA_BUYBACK = 0xFbc0d3cA777722d234FE01dba94DeDeDb277AFe3; - BUYBACK = 0xf3f98086f7B61a32be4EdF8d8A4b964eC886BBcd; - VAULT = 0xF783DD830A4650D2A8594423F123250652340E3f; - ORACLE = 0xf3f98086f7B61a32be4EdF8d8A4b964eC886BBcd; + VAULT = 0x6Bbc476Ee35CBA9e9c3A59fc5b10d7a0BC6f74Ca; + ORACLE = 0x14D99412dAB1878dC01Fe7a1664cdE85896e8E50; } } diff --git a/test/utils/DeploymentSetup.sol b/test/utils/DeploymentSetup.sol index 8cd03b9e..ebbbf85a 100644 --- a/test/utils/DeploymentSetup.sol +++ b/test/utils/DeploymentSetup.sol @@ -35,6 +35,10 @@ abstract contract PreMigrationSetup is Setup { bytes msgData; } + uint256 constant REBASE_MANAGER_GAP = 1 days; + uint256 constant REBASE_MANAGER_APR_CAP = 1000; + uint256 constant REBASE_MANAGER_APR_BOTTOM = 800; + UpgradeUtil internal upgradeUtil; MasterPriceOracle internal masterOracle; ChainlinkOracle chainlinkOracle; @@ -54,7 +58,6 @@ abstract contract PreMigrationSetup is Setup { USDS_OWNER = 0x5b12d9846F8612E439730d18E1C12634753B1bF1; PROXY_ADMIN = 0x3E49925A79CbFb68BAa5bc9DFb4f7D955D1ddF25; SPA_BUYBACK = 0xFbc0d3cA777722d234FE01dba94DeDeDb277AFe3; - BUYBACK = 0xf3f98086f7B61a32be4EdF8d8A4b964eC886BBcd; upgradeUtil = new UpgradeUtil(); @@ -79,7 +82,9 @@ abstract contract PreMigrationSetup is Setup { COLLATERAL_MANAGER = address(collateralManager); FEE_VAULT = 0xFbc0d3cA777722d234FE01dba94DeDeDb277AFe3; DRIPPER = address(new Dripper(VAULT, 7 days)); - REBASE_MANAGER = address(new RebaseManager(VAULT, DRIPPER, 1 days, 1000, 800)); + REBASE_MANAGER = address( + new RebaseManager(VAULT, DRIPPER, REBASE_MANAGER_GAP, REBASE_MANAGER_APR_CAP, REBASE_MANAGER_APR_BOTTOM) + ); vault.updateCollateralManager(COLLATERAL_MANAGER); vault.updateFeeCalculator(FEE_CALCULATOR); diff --git a/test/vault/CollateralManager.t.sol b/test/vault/CollateralManager.t.sol index 3fb357d7..6f2fd4d1 100644 --- a/test/vault/CollateralManager.t.sol +++ b/test/vault/CollateralManager.t.sol @@ -79,7 +79,7 @@ contract Constructor is CollateralManagerTest { } contract CollateralManager_AddCollateral_Test is CollateralManagerTest { - function test_revertsWhen_downsidePegExceedsMax( + function test_RevertWhen_downsidePegExceedsMax( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -93,7 +93,7 @@ contract CollateralManager_AddCollateral_Test is CollateralManagerTest { collateralSetUp(USDCe, _colComp, _baseMintFee, _baseRedeemFee, _downsidePeg); } - function test_revertsWhen_baseMintFeeExceedsMax( + function test_RevertWhen_baseMintFeeExceedsMax( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -107,7 +107,7 @@ contract CollateralManager_AddCollateral_Test is CollateralManagerTest { collateralSetUp(USDCe, _colComp, _baseMintFee, _baseRedeemFee, _downsidePeg); } - function test_revertsWhen_baseRedeemFeeExceedsMax( + function test_RevertWhen_baseRedeemFeeExceedsMax( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -121,7 +121,7 @@ contract CollateralManager_AddCollateral_Test is CollateralManagerTest { collateralSetUp(USDCe, _colComp, _baseMintFee, _baseRedeemFee, _downsidePeg); } - function test_revertsWhen_addSameCollateral( + function test_RevertWhen_addSameCollateral( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -137,7 +137,7 @@ contract CollateralManager_AddCollateral_Test is CollateralManagerTest { collateralSetUp(USDCe, _colComp, _baseMintFee, _baseRedeemFee, _downsidePeg); } - function test_revertsWhen_collateralCompositionExceeded( + function test_RevertWhen_collateralCompositionExceeded( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg @@ -218,7 +218,7 @@ contract CollateralManager_AddCollateral_Test is CollateralManagerTest { } contract CollateralManager_updateCollateral_Test is CollateralManagerTest { - function test_revertsWhen_updateNonExistingCollateral( + function test_RevertWhen_updateNonExistingCollateral( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -233,7 +233,7 @@ contract CollateralManager_updateCollateral_Test is CollateralManagerTest { collateralUpdate(USDT, _colComp, _baseMintFee, _baseRedeemFee, _downsidePeg); } - function test_revertsWhen_collateralCompositionExceeded( + function test_RevertWhen_collateralCompositionExceeded( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -333,7 +333,7 @@ contract CollateralManager_updateCollateral_Test is CollateralManagerTest { } contract CollateralManager_removeCollateral_Test is CollateralManagerTest { - function test_revertsWhen_removeNonExistingCollateral() external useKnownActor(USDS_OWNER) { + function test_RevertWhen_removeNonExistingCollateral() external useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(CollateralManager.CollateralDoesNotExist.selector)); manager.removeCollateral(USDCe); } @@ -377,7 +377,7 @@ contract CollateralManager_removeCollateral_Test is CollateralManagerTest { } } - function test_revertsWhen_removeStrategyCollateralStrategyExists( + function test_RevertWhen_removeStrategyCollateralStrategyExists( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -396,13 +396,13 @@ contract CollateralManager_removeCollateral_Test is CollateralManagerTest { } contract CollateralManager_addCollateralStrategy_Test is CollateralManagerTest { - function test_revertsWhen_collateralDoesntExist(uint16 _collateralComposition) external useKnownActor(USDS_OWNER) { + function test_RevertWhen_collateralDoesntExist(uint16 _collateralComposition) external useKnownActor(USDS_OWNER) { vm.assume(_collateralComposition <= Helpers.MAX_PERCENTAGE); vm.expectRevert(abi.encodeWithSelector(CollateralManager.CollateralDoesNotExist.selector)); manager.addCollateralStrategy(USDCe, STARGATE, 1000); } - function test_revertsWhen_addCollateralstrategyWhenAlreadyMapped( + function test_RevertWhen_addCollateralstrategyWhenAlreadyMapped( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -419,7 +419,7 @@ contract CollateralManager_addCollateralStrategy_Test is CollateralManagerTest { manager.addCollateralStrategy(USDCe, STARGATE, _colComp); } - function test_revertsWhen_addCollateralstrategyNotSupported( + function test_RevertWhen_addCollateralstrategyNotSupported( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -435,7 +435,7 @@ contract CollateralManager_addCollateralStrategy_Test is CollateralManagerTest { manager.addCollateralStrategy(FRAX, STARGATE, _colComp); } - function test_revertsWhen_addCollateralstrategyAllocationPerExceeded( + function test_RevertWhen_addCollateralstrategyAllocationPerExceeded( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -504,7 +504,7 @@ contract CollateralManager_addCollateralStrategy_Test is CollateralManagerTest { } contract CollateralManager_updateCollateralStrategy_Test is CollateralManagerTest { - function test_revertsWhen_updateCollateralstrategyWhenNotMapped( + function test_RevertWhen_updateCollateralstrategyWhenNotMapped( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -520,7 +520,7 @@ contract CollateralManager_updateCollateralStrategy_Test is CollateralManagerTes manager.updateCollateralStrategy(USDCe, STARGATE, 2000); } - function test_revertsWhen_updateCollateralstrategyAllocationPerExceeded( + function test_RevertWhen_updateCollateralstrategyAllocationPerExceeded( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -537,7 +537,7 @@ contract CollateralManager_updateCollateralStrategy_Test is CollateralManagerTes manager.updateCollateralStrategy(USDCe, STARGATE, 10001); } - function test_revertsWhen_updateCollateralstrategyAllocationNotValid( + function test_RevertWhen_updateCollateralstrategyAllocationNotValid( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -624,7 +624,7 @@ contract CollateralManager_updateCollateralStrategy_Test is CollateralManagerTes } contract CollateralManager_removeCollateralStrategy_Test is CollateralManagerTest { - function test_revertsWhen_strategyNotMapped( + function test_RevertWhen_strategyNotMapped( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -661,7 +661,7 @@ contract CollateralManager_removeCollateralStrategy_Test is CollateralManagerTes manager.removeCollateralStrategy(USDCe, AAVE); } - function test_revertsWhen_strategyInUse( + function test_RevertWhen_strategyInUse( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -678,7 +678,7 @@ contract CollateralManager_removeCollateralStrategy_Test is CollateralManagerTes manager.removeCollateralStrategy(USDCe, STARGATE); } - function test_revertsWhen_DefaultStrategy( + function test_RevertWhen_DefaultStrategy( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -696,7 +696,7 @@ contract CollateralManager_removeCollateralStrategy_Test is CollateralManagerTes manager.removeCollateralStrategy(USDCe, STARGATE); } - function test_revertsWhen_DefaultStrategyNotExist( + function test_RevertWhen_DefaultStrategyNotExist( uint16 _baseMintFee, uint16 _baseRedeemFee, uint16 _downsidePeg, @@ -877,7 +877,7 @@ contract CollateralManager_mintRedeemParams_test is CollateralManagerTest { assertEq(mintData.desiredCollateralComposition, _data.desiredCollateralComposition); } - function test_revertsWhen_getMintParams_collateralDoesntExist() external useKnownActor(USDS_OWNER) { + function test_RevertWhen_getMintParams_collateralDoesntExist() external useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(CollateralManager.CollateralDoesNotExist.selector)); manager.getMintParams(USDT); } @@ -915,7 +915,7 @@ contract CollateralManager_mintRedeemParams_test is CollateralManagerTest { assertEq(redeemData.desiredCollateralComposition, _data.desiredCollateralComposition); } - function test_revertsWhen_getRedeemParams_collateralDoesntExist() external useKnownActor(USDS_OWNER) { + function test_RevertWhen_getRedeemParams_collateralDoesntExist() external useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(CollateralManager.CollateralDoesNotExist.selector)); manager.getRedeemParams(FRAX); } diff --git a/test/vault/FeeCalculator.t.sol b/test/vault/FeeCalculator.t.sol index faa9b869..99fcee81 100644 --- a/test/vault/FeeCalculator.t.sol +++ b/test/vault/FeeCalculator.t.sol @@ -23,10 +23,10 @@ contract FeeCalculatorTestSetup is PreMigrationSetup { } } -contract TestFeeCalculatorInit is FeeCalculatorTestSetup { +contract Test_FeeCalculatorInit is FeeCalculatorTestSetup { FeeCalculator _feeCalculator; - function testInitialization() public { + function test_Initialization() public { _feeCalculator = new FeeCalculator(address(collateralManager)); vm.warp(block.timestamp + 1 days); _feeCalculator.calibrateFeeForAll(); @@ -45,9 +45,11 @@ contract TestFeeCalculatorInit is FeeCalculatorTestSetup { } } -contract TestCalibrateFee is FeeCalculatorTestSetup { +contract Test_CalibrateFee is FeeCalculatorTestSetup { uint256 availableCollateral; + event FeeCalibrated(address indexed collateral, uint16 mintFee, uint16 redeemFee); + function setUp() public override { super.setUp(); availableCollateral = 1 * 10 ** ERC20(_collateral).decimals(); @@ -75,6 +77,8 @@ contract TestCalibrateFee is FeeCalculatorTestSetup { vm.warp(block.timestamp + 1 days); // Collateral composition calls are mocked to return lesser than lower limit mockCollateralCalls(availableCollateral / 2); + vm.expectEmit(address(feeCalculator)); + emit FeeCalibrated(_collateral, uint16(oldMintFee / 2), uint16(oldRedeemFee * 2)); feeCalculator.calibrateFee(_collateral); vm.clearMockedCalls(); uint256 newMintFee = feeCalculator.getMintFee(_collateral); @@ -100,6 +104,8 @@ contract TestCalibrateFee is FeeCalculatorTestSetup { vm.warp(block.timestamp + 1 days); // Ratio is changed but still in desired range mockCollateralCalls((IUSDs(USDS).totalSupply() * 600) / 10000); + vm.expectEmit(address(feeCalculator)); + emit FeeCalibrated(_collateral, uint16(oldMintFee), uint16(oldRedeemFee)); feeCalculator.calibrateFee(_collateral); vm.clearMockedCalls(); uint256 newMintFee = feeCalculator.getMintFee(_collateral); @@ -125,6 +131,8 @@ contract TestCalibrateFee is FeeCalculatorTestSetup { vm.warp(block.timestamp + 1 days); // Collateral composition calls are mocked to return higher than upper limit mockCollateralCalls((IUSDs(USDS).totalSupply() * 1600) / 10000); + vm.expectEmit(address(feeCalculator)); + emit FeeCalibrated(_collateral, uint16(oldMintFee * 4), uint16(oldRedeemFee / 4)); feeCalculator.calibrateFee(_collateral); vm.clearMockedCalls(); uint256 newMintFee = feeCalculator.getMintFee(_collateral); @@ -159,7 +167,7 @@ contract TestCalibrateFee is FeeCalculatorTestSetup { } } -contract TestFeeCalculator is FeeCalculatorTestSetup { +contract Test_FeeCalculator is FeeCalculatorTestSetup { IOracle.PriceData private priceData; uint16 private baseMintFee; uint16 private baseRedeemFee; @@ -167,13 +175,13 @@ contract TestFeeCalculator is FeeCalculatorTestSetup { uint16 private constant LOWER_THRESHOLD = 5000; uint16 private constant UPPER_THRESHOLD = 15000; - function testGetMintFee() public { + function test_GetMintFee() public { baseMintFee = getMintFee(); uint256 mintFee = feeCalculator.getMintFee(USDCe); assertEq(mintFee, baseMintFee, "Fee in mismatch"); } - function testGetRedeemFee() public { + function test_GetRedeemFee() public { baseRedeemFee = getRedeemFee(); uint256 redeemFee = feeCalculator.getRedeemFee(USDT); assertEq(redeemFee, baseRedeemFee, "Fee out mismatch"); diff --git a/test/vault/VaultCore.t.sol b/test/vault/VaultCore.t.sol index 178469fa..09cb79f0 100644 --- a/test/vault/VaultCore.t.sol +++ b/test/vault/VaultCore.t.sol @@ -18,6 +18,7 @@ import {CollateralManager} from "../../contracts/vault/CollateralManager.sol"; contract VaultCoreTest is PreMigrationSetup { uint256 internal USDC_PRECISION; + uint256 internal slippageFactor; address internal _collateral; address internal defaultStrategy; address internal otherStrategy; @@ -30,19 +31,23 @@ contract VaultCoreTest is PreMigrationSetup { function setUp() public virtual override { super.setUp(); + slippageFactor = 10; USDC_PRECISION = 10 ** ERC20(USDCe).decimals(); _collateral = USDCe; defaultStrategy = STARGATE_STRATEGY; otherStrategy = AAVE_STRATEGY; } + function _slippageCorrectedAmt(uint256 _expectedAmt) internal view returns (uint256 correctedAmt) { + correctedAmt = _expectedAmt - (_expectedAmt * slippageFactor / Helpers.MAX_PERCENTAGE); + } + function _updateCollateralData(ICollateralManager.CollateralBaseData memory _data) internal { vm.prank(USDS_OWNER); ICollateralManager(COLLATERAL_MANAGER).updateCollateralData(USDCe, _data); } function _allocateIntoStrategy(address __collateral, address _strategy, uint256 _amount) internal useActor(1) { - deal(USDCe, VAULT, _amount * 4); IVault(VAULT).allocate(__collateral, _strategy, _amount); } @@ -87,9 +92,26 @@ contract VaultCoreTest is PreMigrationSetup { _vaultAmt = _calculatedCollateralAmt; } } + + function _calibrateCollateral() internal { + ICollateralManager.CollateralBaseData memory _data = ICollateralManager.CollateralBaseData({ + mintAllowed: true, + redeemAllowed: true, + allocationAllowed: true, + baseMintFee: 450, + baseRedeemFee: 450, + downsidePeg: 9800, + desiredCollateralComposition: 1000 + }); + changePrank(USDS_OWNER); + ICollateralManager(COLLATERAL_MANAGER).updateCollateralData(_collateral, _data); + vm.warp(block.timestamp + 2 days); + FeeCalculator(FEE_CALCULATOR).calibrateFee(_collateral); + changePrank(currentActor); + } } -contract TestInit is VaultCoreTest { +contract Test_Init is VaultCoreTest { function test_Initialization() public useKnownActor(USDS_OWNER) { address _VAULT; // Deploy @@ -103,7 +125,7 @@ contract TestInit is VaultCoreTest { } } -contract TestSetters is VaultCoreTest { +contract Test_Setters is VaultCoreTest { address private _newFeeVault; address private _newYieldReceiver; address private _newCollateralManager; @@ -128,7 +150,7 @@ contract TestSetters is VaultCoreTest { _newOracle = makeAddr("_newOracle"); } - function test_revertIf_callerIsNotOwner() public useActor(1) { + function test_RevertWhen_callerIsNotOwner() public useActor(1) { vm.expectRevert("Ownable: caller is not the owner"); IVault(VAULT).updateFeeVault(_newFeeVault); vm.expectRevert("Ownable: caller is not the owner"); @@ -143,7 +165,7 @@ contract TestSetters is VaultCoreTest { IVault(VAULT).updateOracle(_newOracle); } - function test_revertIf_InvalidAddress() public useKnownActor(USDS_OWNER) { + function test_RevertWhen_InvalidAddress() public useKnownActor(USDS_OWNER) { vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); IVault(VAULT).updateFeeVault(address(0)); vm.expectRevert(abi.encodeWithSelector(Helpers.InvalidAddress.selector)); @@ -159,49 +181,49 @@ contract TestSetters is VaultCoreTest { } function test_updateFeeVault() public useKnownActor(USDS_OWNER) { - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit FeeVaultUpdated(_newFeeVault); IVault(VAULT).updateFeeVault(_newFeeVault); assertEq(_newFeeVault, IVault(VAULT).feeVault()); } function test_updateYieldReceiver() public useKnownActor(USDS_OWNER) { - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit YieldReceiverUpdated(_newYieldReceiver); IVault(VAULT).updateYieldReceiver(_newYieldReceiver); assertEq(_newYieldReceiver, IVault(VAULT).yieldReceiver()); } function test_updateCollateralManager() public useKnownActor(USDS_OWNER) { - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit CollateralManagerUpdated(_newCollateralManager); IVault(VAULT).updateCollateralManager(_newCollateralManager); assertEq(_newCollateralManager, IVault(VAULT).collateralManager()); } function test_updateRebaseManager() public useKnownActor(USDS_OWNER) { - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit RebaseManagerUpdated(_newRebaseManager); IVault(VAULT).updateRebaseManager(_newRebaseManager); assertEq(_newRebaseManager, IVault(VAULT).rebaseManager()); } function test_updateFeeCalculator() public useKnownActor(USDS_OWNER) { - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit FeeCalculatorUpdated(_newFeeCalculator); IVault(VAULT).updateFeeCalculator(_newFeeCalculator); assertEq(_newFeeCalculator, IVault(VAULT).feeCalculator()); } function test_updateOracle() public useKnownActor(USDS_OWNER) { - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit OracleUpdated(_newOracle); IVault(VAULT).updateOracle(_newOracle); assertEq(_newOracle, IVault(VAULT).oracle()); } } -contract TestAllocate is VaultCoreTest { +contract Test_Allocate is VaultCoreTest { address private _strategy; uint256 private _amount; @@ -213,14 +235,14 @@ contract TestAllocate is VaultCoreTest { _strategy = AAVE_STRATEGY; } - function test_revertIf_CollateralAllocationPaused() public useActor(1) { + function test_RevertWhen_CollateralAllocationPaused() public useActor(1) { // DAI _collateral = 0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1; vm.expectRevert(abi.encodeWithSelector(CollateralManager.CollateralAllocationPaused.selector)); IVault(VAULT).allocate(_collateral, _strategy, _amount); } - function test_revertIf_AllocationNotAllowed() public useActor(1) { + function test_RevertWhen_AllocationNotAllowed() public useActor(1) { uint16 cap = 3000; uint256 maxCollateralUsage = ( cap @@ -245,7 +267,7 @@ contract TestAllocate is VaultCoreTest { console.log("Value returned", _possibleAllocation()); if (__amount > 0) { uint256 balBefore = ERC20(_collateral).balanceOf(VAULT); - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit Allocated(_collateral, _strategy, __amount); IVault(VAULT).allocate(_collateral, _strategy, __amount); uint256 balAfter = ERC20(_collateral).balanceOf(VAULT); @@ -257,7 +279,7 @@ contract TestAllocate is VaultCoreTest { _amount = 10000e6; deal(USDCe, VAULT, _amount * 4); uint256 balBefore = ERC20(_collateral).balanceOf(VAULT); - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit Allocated(_collateral, _strategy, _amount); IVault(VAULT).allocate(_collateral, _strategy, _amount); uint256 balAfter = ERC20(_collateral).balanceOf(VAULT); @@ -283,7 +305,7 @@ contract TestAllocate is VaultCoreTest { } } -contract TestMint is VaultCoreTest { +contract Test_Mint is VaultCoreTest { address private minter; uint256 private _collateralAmt; uint256 private _minUSDSAmt; @@ -305,20 +327,20 @@ contract TestMint is VaultCoreTest { IVault(VAULT).updateFeeVault(feeVault); } - function testFuzz_RevertsIf_DeadlinePassed(uint256 __deadline) public useKnownActor(minter) { + function testFuzz_RevertWhen_DeadlinePassed(uint256 __deadline) public useKnownActor(minter) { uint256 _latestDeadline = block.timestamp - 1; _deadline = bound(__deadline, 0, _latestDeadline); vm.expectRevert(abi.encodeWithSelector(Helpers.CustomError.selector, "Deadline passed")); IVault(VAULT).mint(_collateral, _collateralAmt, _minUSDSAmt, _deadline); } - function test_RevertsIf_MintFailed() public useKnownActor(minter) { + function test_RevertWhen_MintFailed() public useKnownActor(minter) { _collateralAmt = 0; vm.expectRevert(abi.encodeWithSelector(VaultCore.MintFailed.selector)); IVault(VAULT).mint(_collateral, _collateralAmt, _minUSDSAmt, _deadline); } - function test_RevertsIf_SlippageScrewsYou() public useKnownActor(minter) { + function test_RevertWhen_SlippageScrewsYou() public useKnownActor(minter) { (_minUSDSAmt,) = IVault(VAULT).mintView(_collateral, _collateralAmt); uint256 _expectedMinAmt = _minUSDSAmt + 10e18; vm.expectRevert(abi.encodeWithSelector(Helpers.MinSlippageError.selector, _minUSDSAmt, _expectedMinAmt)); @@ -332,13 +354,35 @@ contract TestMint is VaultCoreTest { ERC20(USDCe).approve(VAULT, _collateralAmt); uint256 feeAmt; (_minUSDSAmt, feeAmt) = IVault(VAULT).mintView(_collateral, _collateralAmt); - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit Minted(minter, USDCe, _minUSDSAmt, _collateralAmt, feeAmt); IVault(VAULT).mint(_collateral, _collateralAmt, _minUSDSAmt, _deadline); // _minUSDSAmt -= 1; //@todo report precision bug assertApproxEqAbs(ERC20(USDS).balanceOf(minter), _minUSDSAmt, 1); } + function test_Mint_WithFee() public useKnownActor(minter) mockOracle(99e6) { + deal(USDCe, currentActor, _collateralAmt); + assertEq(ERC20(USDCe).balanceOf(currentActor), _collateralAmt); + assertEq(ERC20(USDS).balanceOf(currentActor), 0); + uint256 feeAmt; + ERC20(USDCe).approve(VAULT, _collateralAmt); + + _calibrateCollateral(); + _deadline = block.timestamp + 300; + + (_minUSDSAmt, feeAmt) = IVault(VAULT).mintView(_collateral, _collateralAmt); + assertTrue(feeAmt > 0); + + vm.expectEmit(VAULT); + emit Minted(currentActor, USDCe, _minUSDSAmt, _collateralAmt, feeAmt); + IVault(VAULT).mint(_collateral, _collateralAmt, _minUSDSAmt, _deadline); + + assertEq(ERC20(USDCe).balanceOf(currentActor), 0); + assertApproxEqAbs(ERC20(USDS).balanceOf(currentActor), _minUSDSAmt, 1); + assertApproxEqAbs(ERC20(USDS).balanceOf(feeVault), feeAmt, 1); + } + function test_MintBySpecifyingCollateralAmt() public { ICollateralManager.CollateralBaseData memory _data = ICollateralManager.CollateralBaseData({ mintAllowed: true, @@ -358,7 +402,7 @@ contract TestMint is VaultCoreTest { ERC20(USDCe).approve(VAULT, _collateralAmt); uint256 feeAmt; (_minUSDSAmt, feeAmt) = IVault(VAULT).mintView(_collateral, _collateralAmt); - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit Minted(minter, USDCe, _minUSDSAmt, _collateralAmt, feeAmt); vm.prank(minter); IVault(VAULT).mintBySpecifyingCollateralAmt(_collateral, _collateralAmt, _minUSDSAmt, _maxSPAburnt, _deadline); @@ -366,7 +410,7 @@ contract TestMint is VaultCoreTest { } } -contract TestMintView is VaultCoreTest { +contract Test_MintView is VaultCoreTest { uint256 private _collateralAmt; uint256 private _toMinter; uint256 private _fee; @@ -475,7 +519,7 @@ contract TestMintView is VaultCoreTest { } } -contract TestRebase is VaultCoreTest { +contract Test_Rebase is VaultCoreTest { event RebasedUSDs(uint256 rebaseAmt); function test_Rebase() public { @@ -491,7 +535,7 @@ contract TestRebase is VaultCoreTest { IDripper(DRIPPER).collect(); skip(1 days); (uint256 min, uint256 max) = IRebaseManager(REBASE_MANAGER).getMinAndMaxRebaseAmt(); - vm.expectEmit(true, true, true, true, VAULT); + vm.expectEmit(VAULT); emit RebasedUSDs(max); IVault(VAULT).rebase(); (min, max) = IRebaseManager(REBASE_MANAGER).getMinAndMaxRebaseAmt(); @@ -505,17 +549,17 @@ contract TestRebase is VaultCoreTest { } } -contract TestRedeemView is VaultCoreTest { +contract Test_RedeemView is VaultCoreTest { address private redeemer; - uint256 private usdsAmt; + uint256 private _usdsAmt; function setUp() public override { super.setUp(); redeemer = actors[1]; - usdsAmt = 1000e18; + _usdsAmt = 1000e18; _collateral = USDCe; vm.prank(VAULT); - IUSDs(USDS).mint(redeemer, usdsAmt); + IUSDs(USDS).mint(redeemer, _usdsAmt); ICollateralManager.CollateralBaseData memory _data = ICollateralManager.CollateralBaseData({ mintAllowed: true, redeemAllowed: true, @@ -528,7 +572,7 @@ contract TestRedeemView is VaultCoreTest { _updateCollateralData(_data); } - function test_RevertsIf_RedeemNotAllowed() public { + function test_RevertWhen_RedeemNotAllowed() public { ICollateralManager.CollateralBaseData memory _data = ICollateralManager.CollateralBaseData({ mintAllowed: true, redeemAllowed: false, @@ -541,38 +585,38 @@ contract TestRedeemView is VaultCoreTest { _updateCollateralData(_data); vm.expectRevert(abi.encodeWithSelector(VaultCore.RedemptionPausedForCollateral.selector, _collateral)); vm.prank(redeemer); - IVault(VAULT).redeemView(_collateral, usdsAmt); + IVault(VAULT).redeemView(_collateral, _usdsAmt); } function test_RedeemViewFee0IfCallerIsFacilitator() public { - deal(USDCe, VAULT, (usdsAmt * 2) / 1e12); + deal(USDCe, VAULT, (_usdsAmt * 2) / 1e12); vm.prank(USDS_OWNER); - (,, uint256 fee,,) = IVault(VAULT).redeemView(_collateral, usdsAmt); + (,, uint256 fee,,) = IVault(VAULT).redeemView(_collateral, _usdsAmt); assertEq(fee, 0); } function test_RedeemViewFee0AndCollAmtDownsidePegged() public mockOracle(99e6) { - deal(USDCe, VAULT, (usdsAmt * 2) / 1e12); + deal(USDCe, VAULT, (_usdsAmt * 2) / 1e12); vm.prank(USDS_OWNER); - (uint256 calculatedCollateralAmt,, uint256 fee,,) = IVault(VAULT).redeemView(_collateral, usdsAmt); + (uint256 calculatedCollateralAmt,, uint256 fee,,) = IVault(VAULT).redeemView(_collateral, _usdsAmt); assertEq(fee, 0); - assertEq(calculatedCollateralAmt, usdsAmt / 1e12); + assertEq(calculatedCollateralAmt, _usdsAmt / 1e12); } function test_RedeemViewFee0AndCollAmtNotDownsidePegged() public mockOracle(101e4) { - deal(USDCe, VAULT, (usdsAmt * 2) / 1e12); + deal(USDCe, VAULT, (_usdsAmt * 2) / 1e12); vm.prank(USDS_OWNER); - (uint256 calculatedCollateralAmt,, uint256 fee,,) = IVault(VAULT).redeemView(_collateral, usdsAmt); + (uint256 calculatedCollateralAmt,, uint256 fee,,) = IVault(VAULT).redeemView(_collateral, _usdsAmt); assertEq(fee, 0); - assertGe(calculatedCollateralAmt, ((usdsAmt * 1e8) / 101e6) / 1e12); + assertGe(calculatedCollateralAmt, ((_usdsAmt * 1e8) / 101e6) / 1e12); } function test_RedeemViewApplyDownsidePeg() public mockOracle(101e6) { - deal(USDCe, VAULT, (usdsAmt * 2) / 1e12); + deal(USDCe, VAULT, (_usdsAmt * 2) / 1e12); (uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt, uint256 _vaultAmt,) = - _redeemViewTest(usdsAmt, address(0)); + _redeemViewTest(_usdsAmt, address(0)); (uint256 calculatedCollateralAmt, uint256 usdsBurnAmt, uint256 feeAmt, uint256 vaultAmt, uint256 strategyAmt) = - IVault(VAULT).redeemView(_collateral, usdsAmt); + IVault(VAULT).redeemView(_collateral, _usdsAmt); assertEq(_calculatedCollateralAmt, calculatedCollateralAmt); assertEq(_usdsBurnAmt, usdsBurnAmt); assertEq(_feeAmt, feeAmt); @@ -581,11 +625,11 @@ contract TestRedeemView is VaultCoreTest { } function test_RedeemViewWithoutDownsidePeg() public mockOracle(99e6) { - deal(USDCe, VAULT, (usdsAmt * 2) / 1e12); + deal(USDCe, VAULT, (_usdsAmt * 2) / 1e12); (uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt, uint256 _vaultAmt,) = - _redeemViewTest(usdsAmt, address(0)); + _redeemViewTest(_usdsAmt, address(0)); (uint256 calculatedCollateralAmt, uint256 usdsBurnAmt, uint256 feeAmt, uint256 vaultAmt, uint256 strategyAmt) = - IVault(VAULT).redeemView(_collateral, usdsAmt); + IVault(VAULT).redeemView(_collateral, _usdsAmt); assertEq(_calculatedCollateralAmt, calculatedCollateralAmt); assertEq(_usdsBurnAmt, usdsBurnAmt); assertEq(_feeAmt, feeAmt); @@ -593,11 +637,11 @@ contract TestRedeemView is VaultCoreTest { assertEq(strategyAmt, 0); } - function test_RevertsIf_CollateralAmtMoreThanVaultAmtAndDefaultStrategyNotSet() public { - deal(USDCe, VAULT, (usdsAmt / 2) / 1e12); + function test_RevertWhen_CollateralAmtMoreThanVaultAmtAndDefaultStrategyNotSet() public { + deal(USDCe, VAULT, (_usdsAmt / 2) / 1e12); vm.prank(USDS_OWNER); ICollateralManager(COLLATERAL_MANAGER).updateCollateralDefaultStrategy(USDCe, address(0)); - (uint256 _calculatedCollateralAmt,,,,) = _redeemViewTest(usdsAmt, defaultStrategy); + (uint256 _calculatedCollateralAmt,,,,) = _redeemViewTest(_usdsAmt, defaultStrategy); uint256 _availableAmount = ERC20(_collateral).balanceOf(VAULT) + IStrategy(defaultStrategy).checkAvailableBalance(_collateral); vm.expectRevert( @@ -609,12 +653,12 @@ contract TestRedeemView is VaultCoreTest { _availableAmount ) ); - IVault(VAULT).redeemView(_collateral, usdsAmt); + IVault(VAULT).redeemView(_collateral, _usdsAmt); } - function test_RedeemView_WhenDefaultStrategySetButBalanceIsNotAvailable() public { - deal(USDCe, VAULT, (usdsAmt / 2) / 1e12); - (uint256 _calculatedCollateralAmt,,,,) = _redeemViewTest(usdsAmt, defaultStrategy); + function test_RedeemView_RevertWhen_DefaultStrategySetButBalanceIsNotAvailable() public { + deal(USDCe, VAULT, (_usdsAmt / 2) / 1e12); + (uint256 _calculatedCollateralAmt,,,,) = _redeemViewTest(_usdsAmt, defaultStrategy); uint256 _availableAmount = ERC20(_collateral).balanceOf(VAULT) + IStrategy(defaultStrategy).checkAvailableBalance(_collateral); vm.expectRevert( @@ -626,21 +670,21 @@ contract TestRedeemView is VaultCoreTest { _availableAmount ) ); - IVault(VAULT).redeemView(_collateral, usdsAmt, defaultStrategy); + IVault(VAULT).redeemView(_collateral, _usdsAmt, defaultStrategy); } function test_RedeemView_FromDefaultStrategy() public { - deal(USDCe, VAULT, (usdsAmt / 2) / 1e12); - _allocateIntoStrategy(_collateral, defaultStrategy, (usdsAmt / 2) / 1e12); + deal(USDCe, VAULT, (_usdsAmt) / 1e12); + _allocateIntoStrategy(_collateral, defaultStrategy, (_usdsAmt / 5) / 1e12); ( uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt, uint256 _vaultAmt, uint256 _strategyAmt - ) = _redeemViewTest(usdsAmt, address(0)); + ) = _redeemViewTest(_usdsAmt, address(0)); (uint256 calculatedCollateralAmt, uint256 usdsBurnAmt, uint256 feeAmt, uint256 vaultAmt, uint256 strategyAmt) = - IVault(VAULT).redeemView(_collateral, usdsAmt); + IVault(VAULT).redeemView(_collateral, _usdsAmt); assertEq(_calculatedCollateralAmt, calculatedCollateralAmt); assertEq(_usdsBurnAmt, usdsBurnAmt); assertEq(_feeAmt, feeAmt); @@ -649,16 +693,16 @@ contract TestRedeemView is VaultCoreTest { } function test_RedeemView_valueLessThanVaultBal() public { - deal(USDCe, VAULT, (usdsAmt + 100e18) / 1e12); + deal(USDCe, VAULT, (_usdsAmt + 100e18) / 1e12); ( uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt, uint256 _vaultAmt, uint256 _strategyAmt - ) = _redeemViewTest(usdsAmt, address(0)); + ) = _redeemViewTest(_usdsAmt, address(0)); (uint256 calculatedCollateralAmt, uint256 usdsBurnAmt, uint256 feeAmt,,) = - IVault(VAULT).redeemView(_collateral, usdsAmt); + IVault(VAULT).redeemView(_collateral, _usdsAmt); assertEq(_calculatedCollateralAmt, calculatedCollateralAmt); assertEq(_usdsBurnAmt, usdsBurnAmt); assertEq(_feeAmt, feeAmt); @@ -666,13 +710,13 @@ contract TestRedeemView is VaultCoreTest { assertEq(_strategyAmt, 0); } - function test_RedeemView_RevertsIf_InvalidStrategy() public { + function test_RedeemView_RevertWhen_InvalidStrategy() public { vm.expectRevert(abi.encodeWithSelector(VaultCore.InvalidStrategy.selector, _collateral, COLLATERAL_MANAGER)); - IVault(VAULT).redeemView(_collateral, usdsAmt, COLLATERAL_MANAGER); + IVault(VAULT).redeemView(_collateral, _usdsAmt, COLLATERAL_MANAGER); } - function test_RedeemView_RevertsIf_InsufficientCollateral() public { - (uint256 _calculatedCollateralAmt,,,,) = _redeemViewTest(usdsAmt, otherStrategy); + function test_RedeemView_RevertWhen_InsufficientCollateral() public { + (uint256 _calculatedCollateralAmt,,,,) = _redeemViewTest(_usdsAmt, otherStrategy); uint256 _availableAmount = ERC20(_collateral).balanceOf(VAULT) + IStrategy(otherStrategy).checkAvailableBalance(_collateral); vm.expectRevert( @@ -684,21 +728,21 @@ contract TestRedeemView is VaultCoreTest { _availableAmount ) ); - IVault(VAULT).redeemView(_collateral, usdsAmt, otherStrategy); + IVault(VAULT).redeemView(_collateral, _usdsAmt, otherStrategy); } function test_RedeemView_FromOtherStrategy() public { - deal(USDCe, VAULT, (usdsAmt / 2) / 1e12); - _allocateIntoStrategy(_collateral, otherStrategy, (usdsAmt / 2) / 1e12); + deal(USDCe, VAULT, (_usdsAmt) / 1e12); + _allocateIntoStrategy(_collateral, otherStrategy, (_usdsAmt / 5) / 1e12); (uint256 calculatedCollateralAmt, uint256 usdsBurnAmt, uint256 feeAmt, uint256 vaultAmt, uint256 strategyAmt) = - IVault(VAULT).redeemView(_collateral, usdsAmt, otherStrategy); + IVault(VAULT).redeemView(_collateral, _usdsAmt, otherStrategy); ( uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt, uint256 _vaultAmt, uint256 _strategyAmt - ) = _redeemViewTest(usdsAmt, otherStrategy); + ) = _redeemViewTest(_usdsAmt, otherStrategy); assertEq(_calculatedCollateralAmt, calculatedCollateralAmt); assertEq(_usdsBurnAmt, usdsBurnAmt); assertEq(_feeAmt, feeAmt); @@ -707,7 +751,7 @@ contract TestRedeemView is VaultCoreTest { } } -contract TestRedeem is VaultCoreTest { +contract Test_Redeem is VaultCoreTest { address private redeemer; uint256 private _usdsAmt; uint256 private _minCollAmt; @@ -725,7 +769,7 @@ contract TestRedeem is VaultCoreTest { _deadline = block.timestamp + 120; } - function test_RedeemFromVault_RevertsIf_SlippageMoreThanExpected() public useKnownActor(redeemer) { + function test_RedeemFromVault_RevertWhen_SlippageMoreThanExpected() public useKnownActor(redeemer) { deal(_collateral, VAULT, (_usdsAmt * 2) / 1e12); (_minCollAmt,,,,) = _redeemViewTest(_usdsAmt, address(0)); emit log_named_uint("_minCollAmt", _minCollAmt); @@ -742,90 +786,128 @@ contract TestRedeem is VaultCoreTest { ERC20(USDS).approve(VAULT, _usdsAmt); (uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt,,) = _redeemViewTest(_usdsAmt, otherStrategy); - uint256 balBeforeFeeVault = ERC20(USDS).balanceOf(FEE_VAULT); - uint256 balBeforeUSDsRedeemer = ERC20(USDS).balanceOf(redeemer); - uint256 balBeforeUSDCeRedeemer = ERC20(USDCe).balanceOf(redeemer); - vm.expectEmit(true, true, true, true, VAULT); + uint256 usdsBalanceOfFeeVaultBeforeRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerBeforeRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerBeforeRedeem = ERC20(USDCe).balanceOf(redeemer); + vm.expectEmit(true, true, true, false, VAULT); emit Redeemed(redeemer, _collateral, _usdsBurnAmt, _calculatedCollateralAmt, _feeAmt); vm.prank(redeemer); + IVault(VAULT).redeem(_collateral, _usdsAmt, _slippageCorrectedAmt(_calculatedCollateralAmt), _deadline); + uint256 usdsBalanceOfFeeVaultAfterRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerAfterRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerAfterRedeem = ERC20(USDCe).balanceOf(redeemer); + assertEq(usdsBalanceOfFeeVaultAfterRedeem - usdsBalanceOfFeeVaultBeforeRedeem, _feeAmt); + assertEq(usdsBalanceOfRedeemerBeforeRedeem - usdsBalanceOfRedeemerAfterRedeem, _usdsAmt); + assertApproxEqAbs( + usdceBalanceOfRedeemerAfterRedeem - usdceBalanceOfRedeemerBeforeRedeem, _calculatedCollateralAmt, 10 + ); + } + + function test_RedeemFromVault_WithFee() public useKnownActor(redeemer) mockOracle(99e6) { + deal(_collateral, VAULT, (_usdsAmt * 2) / 1e12); + changePrank(VAULT); + IUSDs(USDS).mint(redeemer, _usdsAmt); + changePrank(redeemer); + ERC20(USDS).approve(VAULT, _usdsAmt); + + _calibrateCollateral(); + _deadline = block.timestamp + 300; + + (uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt,,) = + IVault(VAULT).redeemView(_collateral, _usdsAmt); + assertTrue(_feeAmt > 0); + + uint256 usdsBalanceOfFeeVaultBeforeRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerBeforeRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerBeforeRedeem = ERC20(USDCe).balanceOf(redeemer); + vm.expectEmit(VAULT); + emit Redeemed(redeemer, _collateral, _usdsBurnAmt, _calculatedCollateralAmt, _feeAmt); + changePrank(redeemer); IVault(VAULT).redeem(_collateral, _usdsAmt, _calculatedCollateralAmt, _deadline); - uint256 balAfterFeeVault = ERC20(USDS).balanceOf(FEE_VAULT); - uint256 balAfterUSDsRedeemer = ERC20(USDS).balanceOf(redeemer); - uint256 balAfterUSDCeRedeemer = ERC20(USDCe).balanceOf(redeemer); - assertEq(balAfterFeeVault - balBeforeFeeVault, _feeAmt); - assertEq(balBeforeUSDsRedeemer - balAfterUSDsRedeemer, _usdsAmt); - assertEq(balAfterUSDCeRedeemer - balBeforeUSDCeRedeemer, _calculatedCollateralAmt); + uint256 usdsBalanceOfFeeVaultAfterRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerAfterRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerAfterRedeem = ERC20(USDCe).balanceOf(redeemer); + assertEq(usdsBalanceOfFeeVaultAfterRedeem - usdsBalanceOfFeeVaultBeforeRedeem, _feeAmt); + assertEq(usdsBalanceOfRedeemerBeforeRedeem - usdsBalanceOfRedeemerAfterRedeem, _usdsAmt); + assertEq(usdceBalanceOfRedeemerAfterRedeem - usdceBalanceOfRedeemerBeforeRedeem, _calculatedCollateralAmt); } function test_RedeemFromDefaultStrategy() public { - deal(USDCe, VAULT, (_usdsAmt / 2) / 1e12); - _allocateIntoStrategy(_collateral, defaultStrategy, (_usdsAmt / 2) / 1e12); - (uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt,,) = - _redeemViewTest(_usdsAmt, address(0)); + deal(USDCe, VAULT, (_usdsAmt) / 1e12); + _allocateIntoStrategy(_collateral, defaultStrategy, (_usdsAmt / 5) / 1e12); + (, uint256 _usdsBurnAmt, uint256 _feeAmt,,) = _redeemViewTest(_usdsAmt, address(0)); vm.prank(VAULT); IUSDs(USDS).mint(redeemer, _usdsAmt); vm.prank(redeemer); ERC20(USDS).approve(VAULT, _usdsAmt); - uint256 balBeforeFeeVault = ERC20(USDS).balanceOf(FEE_VAULT); - uint256 balBeforeUSDsRedeemer = ERC20(USDS).balanceOf(redeemer); - uint256 balBeforeUSDCeRedeemer = ERC20(USDCe).balanceOf(redeemer); - vm.expectEmit(true, true, true, true, VAULT); - emit Redeemed(redeemer, _collateral, _usdsBurnAmt, _calculatedCollateralAmt, _feeAmt); + uint256 usdsBalanceOfFeeVaultBeforeRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerBeforeRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerBeforeRedeem = ERC20(USDCe).balanceOf(redeemer); + + (uint256 expectedReturn,,,,) = IVault(VAULT).redeemView(_collateral, _usdsAmt, address(0)); vm.prank(redeemer); - IVault(VAULT).redeem(_collateral, _usdsAmt, _calculatedCollateralAmt, _deadline); - uint256 balAfterFeeVault = ERC20(USDS).balanceOf(FEE_VAULT); - uint256 balAfterUSDsRedeemer = ERC20(USDS).balanceOf(redeemer); - uint256 balAfterUSDCeRedeemer = ERC20(USDCe).balanceOf(redeemer); - assertEq(balAfterFeeVault - balBeforeFeeVault, _feeAmt); - assertEq(balBeforeUSDsRedeemer - balAfterUSDsRedeemer, _usdsAmt); - assertEq(balAfterUSDCeRedeemer - balBeforeUSDCeRedeemer, _calculatedCollateralAmt); + vm.expectEmit(true, true, true, false, VAULT); + emit Redeemed(redeemer, _collateral, _usdsBurnAmt, expectedReturn, _feeAmt); + IVault(VAULT).redeem(_collateral, _usdsAmt, _slippageCorrectedAmt(expectedReturn), _deadline); + uint256 usdsBalanceOfFeeVaultAfterRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerAfterRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerAfterRedeem = ERC20(USDCe).balanceOf(redeemer); + assertEq(usdsBalanceOfFeeVaultAfterRedeem - usdsBalanceOfFeeVaultBeforeRedeem, _feeAmt); + assertEq(usdsBalanceOfRedeemerBeforeRedeem - usdsBalanceOfRedeemerAfterRedeem, _usdsAmt); + assertApproxEqAbs(usdceBalanceOfRedeemerAfterRedeem - usdceBalanceOfRedeemerBeforeRedeem, expectedReturn, 10); } function test_RedeemFromSpecificOtherStrategy() public { - deal(USDCe, VAULT, (_usdsAmt / 2) / 1e12); - _allocateIntoStrategy(_collateral, otherStrategy, (_usdsAmt / 2) / 1e12); + deal(USDCe, VAULT, (_usdsAmt) / 1e12); + _allocateIntoStrategy(_collateral, otherStrategy, (_usdsAmt / 5) / 1e12); (uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt,,) = _redeemViewTest(_usdsAmt, otherStrategy); vm.prank(VAULT); IUSDs(USDS).mint(redeemer, _usdsAmt); vm.prank(redeemer); ERC20(USDS).approve(VAULT, _usdsAmt); - uint256 balBeforeFeeVault = ERC20(USDS).balanceOf(FEE_VAULT); - uint256 balBeforeUSDsRedeemer = ERC20(USDS).balanceOf(redeemer); - uint256 balBeforeUSDCeRedeemer = ERC20(USDCe).balanceOf(redeemer); - vm.expectEmit(true, true, true, true, VAULT); + uint256 usdsBalanceOfFeeVaultBeforeRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerBeforeRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerBeforeRedeem = ERC20(USDCe).balanceOf(redeemer); + vm.expectEmit(true, true, true, false, VAULT); emit Redeemed(redeemer, _collateral, _usdsBurnAmt, _calculatedCollateralAmt, _feeAmt); vm.prank(redeemer); - IVault(VAULT).redeem(_collateral, _usdsAmt, _calculatedCollateralAmt, _deadline, otherStrategy); - uint256 balAfterFeeVault = ERC20(USDS).balanceOf(FEE_VAULT); - uint256 balAfterUSDsRedeemer = ERC20(USDS).balanceOf(redeemer); - uint256 balAfterUSDCeRedeemer = ERC20(USDCe).balanceOf(redeemer); - assertEq(balAfterFeeVault - balBeforeFeeVault, _feeAmt); - assertEq(balBeforeUSDsRedeemer - balAfterUSDsRedeemer, _usdsAmt); - assertEq(balAfterUSDCeRedeemer - balBeforeUSDCeRedeemer, _calculatedCollateralAmt); + IVault(VAULT).redeem( + _collateral, _usdsAmt, _slippageCorrectedAmt(_calculatedCollateralAmt), _deadline, otherStrategy + ); + uint256 usdsBalanceOfFeeVaultAfterRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerAfterRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerAfterRedeem = ERC20(USDCe).balanceOf(redeemer); + assertEq(usdsBalanceOfFeeVaultAfterRedeem - usdsBalanceOfFeeVaultBeforeRedeem, _feeAmt); + assertEq(usdsBalanceOfRedeemerBeforeRedeem - usdsBalanceOfRedeemerAfterRedeem, _usdsAmt); + assertEq(usdceBalanceOfRedeemerAfterRedeem - usdceBalanceOfRedeemerBeforeRedeem, _calculatedCollateralAmt); } function test_RedeemFromSpecifiedDefaultStrategy() public { - deal(USDCe, VAULT, (_usdsAmt / 2) / 1e12); - _allocateIntoStrategy(_collateral, defaultStrategy, (_usdsAmt / 2) / 1e12); + deal(USDCe, VAULT, (_usdsAmt) / 1e12); + _allocateIntoStrategy(_collateral, defaultStrategy, (_usdsAmt / 5) / 1e12); (uint256 _calculatedCollateralAmt, uint256 _usdsBurnAmt, uint256 _feeAmt,,) = _redeemViewTest(_usdsAmt, defaultStrategy); vm.prank(VAULT); IUSDs(USDS).mint(redeemer, _usdsAmt); vm.prank(redeemer); ERC20(USDS).approve(VAULT, _usdsAmt); - uint256 balBeforeFeeVault = ERC20(USDS).balanceOf(FEE_VAULT); - uint256 balBeforeUSDsRedeemer = ERC20(USDS).balanceOf(redeemer); - uint256 balBeforeUSDCeRedeemer = ERC20(USDCe).balanceOf(redeemer); - vm.expectEmit(true, true, true, true, VAULT); + uint256 usdsBalanceOfFeeVaultBeforeRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerBeforeRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerBeforeRedeem = ERC20(USDCe).balanceOf(redeemer); + vm.expectEmit(true, true, true, false, VAULT); emit Redeemed(redeemer, _collateral, _usdsBurnAmt, _calculatedCollateralAmt, _feeAmt); vm.prank(redeemer); - IVault(VAULT).redeem(_collateral, _usdsAmt, _calculatedCollateralAmt, _deadline, defaultStrategy); - uint256 balAfterFeeVault = ERC20(USDS).balanceOf(FEE_VAULT); - uint256 balAfterUSDsRedeemer = ERC20(USDS).balanceOf(redeemer); - uint256 balAfterUSDCeRedeemer = ERC20(USDCe).balanceOf(redeemer); - assertEq(balAfterFeeVault - balBeforeFeeVault, _feeAmt); - assertEq(balBeforeUSDsRedeemer - balAfterUSDsRedeemer, _usdsAmt); - assertEq(balAfterUSDCeRedeemer - balBeforeUSDCeRedeemer, _calculatedCollateralAmt); + IVault(VAULT).redeem( + _collateral, _usdsAmt, _slippageCorrectedAmt(_calculatedCollateralAmt), _deadline, defaultStrategy + ); + uint256 usdsBalanceOfFeeVaultAfterRedeem = ERC20(USDS).balanceOf(FEE_VAULT); + uint256 usdsBalanceOfRedeemerAfterRedeem = ERC20(USDS).balanceOf(redeemer); + uint256 usdceBalanceOfRedeemerAfterRedeem = ERC20(USDCe).balanceOf(redeemer); + assertEq(usdsBalanceOfFeeVaultAfterRedeem - usdsBalanceOfFeeVaultBeforeRedeem, _feeAmt); + assertEq(usdsBalanceOfRedeemerBeforeRedeem - usdsBalanceOfRedeemerAfterRedeem, _usdsAmt); + assertApproxEqAbs( + usdceBalanceOfRedeemerAfterRedeem - usdceBalanceOfRedeemerBeforeRedeem, _calculatedCollateralAmt, 100 + ); } } From 2e2b8c6d6bd4d16c390b1df486874b8135e4b48c Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 19 Jan 2024 13:14:33 +0530 Subject: [PATCH 62/64] test(strategy): Fix collectInterest test on AaveStrategy --- test/strategy/AaveStrategy.t.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/strategy/AaveStrategy.t.sol b/test/strategy/AaveStrategy.t.sol index ce09ca9d..23fb7299 100644 --- a/test/strategy/AaveStrategy.t.sol +++ b/test/strategy/AaveStrategy.t.sol @@ -327,10 +327,8 @@ contract CollectInterest is AaveStrategyTest { strategy.collectInterest(ASSET); + assertEq(strategy.checkInterestEarned(ASSET), 0); uint256 current_bal = IERC20(ASSET).balanceOf(yieldReceiver); - uint256 newInterestEarned = strategy.checkInterestEarned(ASSET); - - assertEq(newInterestEarned, 0); assertEq(current_bal, (initial_bal + harvestAmount)); } } From d0a69046ffeb1e4c6c02759c04db1694ba65d5cb Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 19 Jan 2024 15:07:19 +0530 Subject: [PATCH 63/64] test(strategy): Fix test_collectInterest() update assert statement --- test/strategy/AaveStrategy.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/strategy/AaveStrategy.t.sol b/test/strategy/AaveStrategy.t.sol index 23fb7299..e7848bcc 100644 --- a/test/strategy/AaveStrategy.t.sol +++ b/test/strategy/AaveStrategy.t.sol @@ -326,8 +326,7 @@ contract CollectInterest is AaveStrategyTest { emit InterestCollected(ASSET, yieldReceiver, harvestAmount); strategy.collectInterest(ASSET); - - assertEq(strategy.checkInterestEarned(ASSET), 0); + assertApproxEqAbs(strategy.checkInterestEarned(ASSET), 0, 1); uint256 current_bal = IERC20(ASSET).balanceOf(yieldReceiver); assertEq(current_bal, (initial_bal + harvestAmount)); } From 64b48d8579b1fc6ae69ab6cf1a6931ef4f0b34d2 Mon Sep 17 00:00:00 2001 From: YashP16 Date: Fri, 19 Jan 2024 15:20:47 +0530 Subject: [PATCH 64/64] test(strategy): Update assertion in test_collectInterest() for all strategy tests --- test/strategy/CompoundStrategy.t.sol | 4 +--- test/strategy/StargateStrategy.t.sol | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/strategy/CompoundStrategy.t.sol b/test/strategy/CompoundStrategy.t.sol index 7edb5586..c6c7bf01 100644 --- a/test/strategy/CompoundStrategy.t.sol +++ b/test/strategy/CompoundStrategy.t.sol @@ -257,9 +257,7 @@ contract CollectInterestTest is CompoundStrategyTest { strategy.collectInterest(ASSET); uint256 current_bal = IERC20(ASSET).balanceOf(yieldReceiver); - uint256 newInterestEarned = strategy.checkInterestEarned(ASSET); - - assertEq(newInterestEarned, 0); + assertApproxEqAbs(strategy.checkInterestEarned(ASSET), 0, 1); assertEq(current_bal, (initial_bal + harvestAmount)); } } diff --git a/test/strategy/StargateStrategy.t.sol b/test/strategy/StargateStrategy.t.sol index 13fd2d34..76c5b81d 100644 --- a/test/strategy/StargateStrategy.t.sol +++ b/test/strategy/StargateStrategy.t.sol @@ -487,6 +487,7 @@ contract Test_CollectInterest is Test_Harvest { vm.recordLogs(); strategy.collectInterest(assetData[i].asset); + assertApproxEqAbs(strategy.checkInterestEarned(assetData[i].asset), 0, 1); VmSafe.Log[] memory logs = vm.getRecordedLogs(); @@ -505,7 +506,6 @@ contract Test_CollectInterest is Test_Harvest { /// @note precision Error from stargate assertApproxEqAbs(strategy.checkLPTokenBalance(assetData[i].asset), initialLPBal, 1); - assertEq(strategy.checkInterestEarned(assetData[i].asset), 0); } }