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

integrative unit test #39

Merged
merged 6 commits into from
Sep 24, 2024
Merged
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
173 changes: 173 additions & 0 deletions src/test/TestUSDCIntegration.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
pragma solidity ^0.8.18;

import "forge-std/console2.sol";
import {ERC20Mock} from "@openzeppelin/contracts/mocks/ERC20Mock.sol";
import {MockTermRepoToken} from "./mocks/MockTermRepoToken.sol";
import {MockTermAuction} from "./mocks/MockTermAuction.sol";
import {MockUSDC} from "./mocks/MockUSDC.sol";
import {Setup, ERC20, IStrategyInterface} from "./utils/Setup.sol";
import {Strategy} from "../Strategy.sol";

contract TestUSDCIntegration is Setup {
uint256 internal constant TEST_REPO_TOKEN_RATE = 0.05e18;
uint256 public constant THREESIXTY_DAYCOUNT_SECONDS = 360 days;
uint256 public constant RATE_PRECISION = 1e18;

MockUSDC internal mockUSDC;
ERC20Mock internal mockCollateral;
MockTermRepoToken internal repoToken1Week;
MockTermRepoToken internal repoToken1Month;
MockTermAuction internal repoToken1WeekAuction;
MockTermAuction internal repoToken1MonthAuction;
Strategy internal termStrategy;
StrategySnapshot internal initialState;

function setUp() public override {
mockUSDC = new MockUSDC();
mockCollateral = new ERC20Mock();

_setUp(ERC20(address(mockUSDC)));

repoToken1Week = new MockTermRepoToken(
bytes32("test repo token 1"), address(mockUSDC), address(mockCollateral), 1e18, 1 weeks
);
repoToken1Month = new MockTermRepoToken(
bytes32("test repo token 2"), address(mockUSDC), address(mockCollateral), 1e18, 4 weeks
);
termController.setOracleRate(MockTermRepoToken(repoToken1Week).termRepoId(), TEST_REPO_TOKEN_RATE);
termController.setOracleRate(MockTermRepoToken(repoToken1Month).termRepoId(), TEST_REPO_TOKEN_RATE);


termStrategy = Strategy(address(strategy));

repoToken1WeekAuction = new MockTermAuction(repoToken1Week);
repoToken1MonthAuction = new MockTermAuction(repoToken1Month);

vm.startPrank(management);
termStrategy.setCollateralTokenParams(address(mockCollateral), 0.5e18);
termStrategy.setTimeToMaturityThreshold(3 weeks);
termStrategy.setRepoTokenConcentrationLimit(1e18);
termStrategy.setRequiredReserveRatio(0);
termStrategy.setDiscountRateMarkup(0);
vm.stopPrank();

// start with some initial funds
mockUSDC.mint(address(strategy), 100e6);

initialState.totalAssetValue = termStrategy.totalAssetValue();
initialState.totalLiquidBalance = termStrategy.totalLiquidBalance();
}

function _submitOffer(bytes32 idHash, uint256 offerAmount, MockTermAuction auction, MockTermRepoToken repoToken) private returns (bytes32) {
// test: only management can submit offers
vm.expectRevert("!management");
bytes32[] memory offerIds = termStrategy.submitAuctionOffer(
auction, address(repoToken), idHash, bytes32("test price"), offerAmount
);

vm.prank(management);
offerIds = termStrategy.submitAuctionOffer(
auction, address(repoToken), idHash, bytes32("test price"), offerAmount
);

assertEq(offerIds.length, 1);

return offerIds[0];
}

function testSellRepoTokenSubmitOfferAndCloseAuction() public {
address testUser = vm.addr(0x11111);
mockUSDC.mint(testUser, 1e18);
repoToken1Month.mint(testUser, 1000e18);

vm.startPrank(testUser);
mockUSDC.approve(address(mockYearnVault), type(uint256).max);
mockYearnVault.deposit(1e18, testUser);
repoToken1Month.approve(address(strategy), type(uint256).max);
termStrategy.sellRepoToken(address(repoToken1Month), 1e6);
mockYearnVault.withdraw(1e18, testUser, testUser);
vm.stopPrank();

address[] memory holdings = termStrategy.repoTokenHoldings();

assertEq(holdings.length, 1);


bytes32 offerId1 = _submitOffer(bytes32("offer id hash 1"), 1e6, repoToken1WeekAuction, repoToken1Week);
bytes32[] memory offerIds = new bytes32[](1);
offerIds[0] = offerId1;
uint256[] memory fillAmounts = new uint256[](1);
fillAmounts[0] = 1e6;
uint256[] memory repoTokenAmounts = new uint256[](1);
repoTokenAmounts[0] = _getRepoTokenAmountGivenPurchaseTokenAmount(
1e6, repoToken1Week, TEST_REPO_TOKEN_RATE
);


repoToken1WeekAuction.auctionSuccess(offerIds, fillAmounts, repoTokenAmounts);

holdings = termStrategy.repoTokenHoldings();

// test: 1 holding because auctionClosed not yet called
assertEq(holdings.length, 1);

termStrategy.auctionClosed();
holdings = termStrategy.repoTokenHoldings();
assertEq(holdings.length, 2);
aazhou1 marked this conversation as resolved.
Show resolved Hide resolved
(uint256 holdings0Maturity, , ,) = MockTermRepoToken(holdings[0]).config();
(uint256 holdings1Maturity, , ,) = MockTermRepoToken(holdings[1]).config();
assertTrue(holdings0Maturity <= holdings1Maturity);
bytes32[] memory offers = termStrategy.pendingOffers();

assertEq(offers.length, 0);

assertEq(termStrategy.totalLiquidBalance(), initialState.totalLiquidBalance - 1e6);
// test: totalAssetValue = total liquid balance + pending offer amount
assertEq(termStrategy.totalAssetValue(), termStrategy.totalLiquidBalance() + 1e6);
}

function testSubmittingOffersToMultipleAuctions() public {
address testUser = vm.addr(0x11111);
mockUSDC.mint(testUser, 1e18);
repoToken1Month.mint(testUser, 1000e18);

vm.startPrank(testUser);
mockUSDC.approve(address(mockYearnVault), type(uint256).max);
mockYearnVault.deposit(1e18, testUser);
repoToken1Month.approve(address(strategy), type(uint256).max);
termStrategy.sellRepoToken(address(repoToken1Month), 1e6);
mockYearnVault.withdraw(1e18, testUser, testUser);
vm.stopPrank();

bytes32 offerId1 = _submitOffer(bytes32("offer id hash 1"), 1e6, repoToken1WeekAuction, repoToken1Week);

bytes32 offerId2 = _submitOffer(bytes32("offer id hash 2"), 1e6, repoToken1MonthAuction, repoToken1Month);

bytes32[] memory offers = termStrategy.pendingOffers();

assertEq(offers.length, 2);

assertTrue(offers[0] == offerId1 ? address(repoToken1WeekAuction) <= address(repoToken1MonthAuction) : address(repoToken1MonthAuction) <= address(repoToken1WeekAuction));
}

function _getRepoTokenAmountGivenPurchaseTokenAmount(
uint256 purchaseTokenAmount,
MockTermRepoToken termRepoToken,
uint256 discountRate
) private view returns (uint256) {
(uint256 redemptionTimestamp, address purchaseToken, ,) = termRepoToken.config();

uint256 purchaseTokenPrecision = 10**ERC20(purchaseToken).decimals();
uint256 repoTokenPrecision = 10**ERC20(address(termRepoToken)).decimals();

uint256 timeLeftToMaturityDayFraction =
((redemptionTimestamp - block.timestamp) * purchaseTokenPrecision) / THREESIXTY_DAYCOUNT_SECONDS;

// purchaseTokenAmount * (1 + r * days / 360) = repoTokenAmountInBaseAssetPrecision
uint256 repoTokenAmountInBaseAssetPrecision =
purchaseTokenAmount * (purchaseTokenPrecision + (discountRate * timeLeftToMaturityDayFraction / RATE_PRECISION)) / purchaseTokenPrecision;

return repoTokenAmountInBaseAssetPrecision * repoTokenPrecision / purchaseTokenPrecision;
}

}
Loading