Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit fixes #6

Merged
merged 5 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}
Loading