Skip to content

Commit

Permalink
Merge pull request #6 from reservoir-protocol/morpho-rusd-adapter
Browse files Browse the repository at this point in the history
Audit fixes
  • Loading branch information
eukadish authored Nov 14, 2024
2 parents 93a82e5 + cbfee96 commit 361d13f
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 53 deletions.
16 changes: 11 additions & 5 deletions src/adapters/MorphoRUSDAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {AccessControl} from "openzeppelin-contracts/contracts/access/AccessContr

import {IERC4626} from "openzeppelin-contracts/contracts/interfaces/IERC4626.sol";
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeCast} from "openzeppelin-contracts/contracts/utils/math/SafeCast.sol";

import {Stablecoin} from "../Stablecoin.sol";
import {IOracle} from "src/interfaces/IOracle.sol";
Expand Down Expand Up @@ -60,10 +61,15 @@ contract MorphoRUSDAdapter is IAssetAdapter, AccessControl {

function redeem(uint256 shares) public onlyRole(CONTROLLER) {
Stablecoin underlyingStablecoin = Stablecoin(address(underlying));

uint256 initialBalance = underlyingStablecoin.balanceOf(address(this));

vault.redeem(shares, address(this), address(this));
underlyingStablecoin.burn(
underlyingStablecoin.balanceOf(address(this))
);

uint256 receivedAmount = underlyingStablecoin.balanceOf(address(this)) -
initialBalance;

underlyingStablecoin.burn(receivedAmount);

emit Redeem(msg.sender, shares, block.timestamp);
}
Expand Down Expand Up @@ -187,13 +193,13 @@ contract MorphoRUSDAdapter is IAssetAdapter, AccessControl {
{
int256 latestAnswer = underlyingPriceOracle.latestAnswer();

return latestAnswer > 0 ? uint256(latestAnswer) : 0;
return latestAnswer > 0 ? SafeCast.toUint256(latestAnswer) : 0;
}

function _fundPriceOracleLatestAnswer() private view returns (uint256) {
int256 latestAnswer = fundPriceOracle.latestAnswer();

return latestAnswer > 0 ? uint256(latestAnswer) : 0;
return latestAnswer > 0 ? SafeCast.toUint256(latestAnswer) : 0;
}

function recover(address _token) external onlyRole(MANAGER) {
Expand Down
177 changes: 129 additions & 48 deletions test/morpho/MorphoRUSDAdapter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ contract MorphoRUSDAdapterTest is Test {

string MAINNET_RPC_URL = vm.envString("MAINNET_RPC_URL");

// avoid stack too deep errors for testDepositRedeemFlow test
uint256 depositShares1;
uint256 redeemShares1;
uint256 depositShares2;
uint256 redeemShares2;

function setUp() external {
vm.createSelectFork(MAINNET_RPC_URL);

Expand Down Expand Up @@ -112,25 +118,36 @@ contract MorphoRUSDAdapterTest is Test {
emit Deposit(address(this), depositAmount1, block.timestamp);
adapter.deposit(depositAmount1);

assertEq(
depositShares1 = metamorpho.convertToShares(depositAmount1);

assertApproxEqAbs(
metamorpho.convertToAssets(metamorpho.balanceOf(address(adapter))),
depositAmount1
depositAmount1,
10
);
assertEq(rusd.balanceOf(address(adapter)), 0);
assertEq(rusd.balanceOf(address(this)), 0);

assertEq(rusd.totalSupply(), initialRusdTotalSupply + depositAmount1);
assertApproxEqAbs(
rusd.totalSupply(),
initialRusdTotalSupply + depositAmount1,
10
);

assertEq(
assertApproxEqAbs(
adapter.totalValue(),
(uint256(vaultSharesOracleV2.latestAnswer()) * depositAmount1) / 1e8
(uint256(vaultSharesOracleV2.latestAnswer()) * depositShares1) /
1e8,
10
);
assertEq(adapter.fundTotalValue(), adapter.totalValue());
assertEq(

assertApproxEqAbs(
adapter.totalRiskValue(),
(riskWeight *
((uint256(vaultSharesOracleV2.latestAnswer()) *
depositAmount1) / 1e8)) / 1e6
depositShares1) / 1e8)) / 1e6,
10
);
assertEq(adapter.fundTotalRiskValue(), adapter.totalRiskValue());
assertEq(adapter.underlyingTotalRiskValue(), 0);
Expand All @@ -139,32 +156,43 @@ contract MorphoRUSDAdapterTest is Test {
assertEq(adapter.fundBalance(), metamorpho.balanceOf(address(adapter)));

vm.expectEmit(true, true, true, true);
emit Redeem(address(this), redeemAmount1, block.timestamp);
adapter.redeem(redeemAmount1);
emit Redeem(
address(this),
metamorpho.convertToShares(redeemAmount1),
block.timestamp
);
adapter.redeem(metamorpho.convertToShares(redeemAmount1));

assertEq(
depositShares1 = metamorpho.convertToShares(depositAmount1);
redeemShares1 = metamorpho.convertToShares(redeemAmount1);

assertApproxEqAbs(
metamorpho.convertToAssets(metamorpho.balanceOf(address(adapter))),
depositAmount1 - redeemAmount1
depositAmount1 - redeemAmount1,
10
);
assertEq(rusd.balanceOf(address(adapter)), 0);
assertEq(rusd.balanceOf(address(this)), 0);

assertEq(
assertApproxEqAbs(
rusd.totalSupply(),
initialRusdTotalSupply + depositAmount1 - redeemAmount1
initialRusdTotalSupply + depositAmount1 - redeemAmount1,
10
);

assertEq(
assertApproxEqAbs(
adapter.totalValue(),
(uint256(vaultSharesOracleV2.latestAnswer()) *
(depositAmount1 - redeemAmount1)) / 1e8
(depositShares1 - redeemShares1)) / 1e8,
10
);
assertEq(adapter.fundTotalValue(), adapter.totalValue());
assertEq(
assertApproxEqAbs(
adapter.totalRiskValue(),
(riskWeight *
((uint256(vaultSharesOracleV2.latestAnswer()) *
(depositAmount1 - redeemAmount1)) / 1e8)) / 1e6
(depositShares1 - redeemShares1)) / 1e8)) / 1e6,
10
);
assertEq(adapter.fundTotalRiskValue(), adapter.totalRiskValue());
assertEq(adapter.underlyingTotalRiskValue(), 0);
Expand All @@ -176,33 +204,41 @@ contract MorphoRUSDAdapterTest is Test {
emit Deposit(address(this), depositAmount2, block.timestamp);
adapter.deposit(depositAmount2);

assertEq(
depositShares1 = metamorpho.convertToShares(depositAmount1);
redeemShares1 = metamorpho.convertToShares(redeemAmount1);
depositShares2 = metamorpho.convertToShares(depositAmount2);

assertApproxEqAbs(
metamorpho.convertToAssets(metamorpho.balanceOf(address(adapter))),
depositAmount1 - redeemAmount1 + depositAmount2
depositAmount1 - redeemAmount1 + depositAmount2,
10
);
assertEq(rusd.balanceOf(address(adapter)), 0);
assertEq(rusd.balanceOf(address(this)), 0);

assertEq(
assertApproxEqAbs(
rusd.totalSupply(),
initialRusdTotalSupply +
depositAmount1 -
redeemAmount1 +
depositAmount2
depositAmount2,
10
);

assertEq(
assertApproxEqAbs(
adapter.totalValue(),
(uint256(vaultSharesOracleV2.latestAnswer()) *
(depositAmount1 - redeemAmount1 + depositAmount2)) / 1e8
(depositShares1 - redeemShares1 + depositShares2)) / 1e8,
10
);
assertEq(adapter.fundTotalValue(), adapter.totalValue());
assertEq(
assertApproxEqAbs(
adapter.totalRiskValue(),
(riskWeight *
((uint256(vaultSharesOracleV2.latestAnswer()) *
(depositAmount1 - redeemAmount1 + depositAmount2)) / 1e8)) /
1e6
(depositShares1 - redeemShares1 + depositShares2)) / 1e8)) /
1e6,
10
);
assertEq(adapter.fundTotalRiskValue(), adapter.totalRiskValue());
assertEq(adapter.underlyingTotalRiskValue(), 0);
Expand All @@ -211,42 +247,55 @@ contract MorphoRUSDAdapterTest is Test {
assertEq(adapter.fundBalance(), metamorpho.balanceOf(address(adapter)));

vm.expectEmit(true, true, true, true);
emit Redeem(address(this), redeemAmount2, block.timestamp);
adapter.redeem(redeemAmount2);
emit Redeem(
address(this),
metamorpho.convertToShares(redeemAmount2),
block.timestamp
);
adapter.redeem(metamorpho.convertToShares(redeemAmount2));

assertEq(
depositShares1 = metamorpho.convertToShares(depositAmount1);
redeemShares1 = metamorpho.convertToShares(redeemAmount1);
depositShares2 = metamorpho.convertToShares(depositAmount2);
redeemShares2 = metamorpho.convertToShares(redeemAmount2);

assertApproxEqAbs(
metamorpho.convertToAssets(metamorpho.balanceOf(address(adapter))),
depositAmount1 - redeemAmount1 + depositAmount2 - redeemAmount2
depositAmount1 - redeemAmount1 + depositAmount2 - redeemAmount2,
10
);
assertEq(rusd.balanceOf(address(adapter)), 0);
assertEq(rusd.balanceOf(address(this)), 0);

assertEq(
assertApproxEqAbs(
rusd.totalSupply(),
initialRusdTotalSupply +
depositAmount1 -
redeemAmount1 +
depositAmount2 -
redeemAmount2
redeemAmount2,
10
);

assertEq(
assertApproxEqAbs(
adapter.totalValue(),
(uint256(vaultSharesOracleV2.latestAnswer()) *
(depositAmount1 -
redeemAmount1 +
depositAmount2 -
redeemAmount2)) / 1e8
(depositShares1 -
redeemShares1 +
depositShares2 -
redeemShares2)) / 1e8,
10
);
assertEq(adapter.fundTotalValue(), adapter.totalValue());
assertEq(
assertApproxEqAbs(
adapter.totalRiskValue(),
(riskWeight *
((uint256(vaultSharesOracleV2.latestAnswer()) *
(depositAmount1 -
redeemAmount1 +
depositAmount2 -
redeemAmount2)) / 1e8)) / 1e6
(depositShares1 -
redeemShares1 +
depositShares2 -
redeemShares2)) / 1e8)) / 1e6,
10
);
assertEq(adapter.fundTotalRiskValue(), adapter.totalRiskValue());
assertEq(adapter.underlyingTotalRiskValue(), 0);
Expand All @@ -255,13 +304,24 @@ contract MorphoRUSDAdapterTest is Test {
assertEq(adapter.fundBalance(), metamorpho.balanceOf(address(adapter)));
}

//! DONT KNOW WHATS THE REAL CAP
// function testDepositMoreThenCap(uint256 amount) external {
// vm.assume(amount > CAP);
function test_redeem_with_accidental_sent_tokens(
uint256 depositAmount,
uint256 redeemAmount,
uint256 accidentalySentAmount
) external {
vm.assume(depositAmount <= 1_000_000_000e18);
vm.assume(redeemAmount <= depositAmount);
vm.assume(accidentalySentAmount <= 1_000_000_000e18);

adapter.deposit(depositAmount);

deal(address(rusd), address(this), accidentalySentAmount, true);
rusd.transfer(address(adapter), accidentalySentAmount);

// vm.expectRevert();
// adapter.deposit(amount);
// }
adapter.redeem(redeemAmount);

assertEq(rusd.balanceOf(address(adapter)), accidentalySentAmount);
}

function testDepositUnauthorized(uint256 amount) external {
adapter.revokeRole(adapter.CONTROLLER(), address(this));
Expand Down Expand Up @@ -330,4 +390,25 @@ contract MorphoRUSDAdapterTest is Test {
vm.expectRevert();
adapter.setFundRiskWeight(riskWeight);
}

function test_recover(uint256 _amount) external {
ERC20 testToken = new ERC20("Test Token", "TTT");

deal(address(testToken), address(adapter), _amount);

assertEq(testToken.balanceOf(address(this)), 0);
assertEq(testToken.balanceOf(address(adapter)), _amount);

adapter.recover(address(testToken));

assertEq(testToken.balanceOf(address(this)), _amount);
assertEq(testToken.balanceOf(address(adapter)), 0);
}

function test_recover_as_non_owner() external {
adapter.revokeRole(adapter.MANAGER(), address(this));

vm.expectRevert();
adapter.recover(address(rusd));
}
}

0 comments on commit 361d13f

Please sign in to comment.