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

Disable commitlint checks #7

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bac6a0e
term auction offer locker interface
aazhou1 Apr 2, 2024
55cfa77
Merge pull request #1 from term-finance/offer-locker-interface
aazhou1 Apr 9, 2024
41f910a
strategy prototype
0xddong Apr 12, 2024
d847bd6
updating interfaces
0xddong Apr 12, 2024
3ec7656
working on offer
0xddong Apr 25, 2024
2a5f722
update foundry
0xddong Apr 25, 2024
f34023c
forge install: openzeppelin-contracts-upgradeable
0xddong Apr 25, 2024
c51c0c3
fixing build
0xddong Apr 25, 2024
ca369dc
forge install: openzeppelin-contracts-upgradeable
0xddong Apr 25, 2024
f1893e9
offerOnNewAuction impl
0xddong Apr 26, 2024
7c110aa
adding yearn vault
0xddong Apr 26, 2024
14571ff
adding pending offers
0xddong Apr 29, 2024
c86b8b4
refactor
0xddong Apr 30, 2024
7d61f28
wrapping up submitAuctionOffer
0xddong May 3, 2024
076c3f5
adding unit tests
0xddong May 12, 2024
30cc45e
more tests
0xddong May 16, 2024
7b50e6f
more tests
0xddong May 17, 2024
a0ef8d9
adding more tess
0xddong May 23, 2024
d82d387
adding more checks
0xddong May 23, 2024
63a9dee
adding auth to event emitter
0xddong Jun 6, 2024
fea769d
adding repo token redemption tests
0xddong Jun 18, 2024
d0e9b50
fixing tests
0xddong Jun 20, 2024
2723b3d
fix edit offer
0xddong Jun 27, 2024
7168068
fixing edit offer
0xddong Jun 27, 2024
df97726
adding pending offers to weighted maturity math
0xddong Jun 27, 2024
3194600
fixing weighted time to maturity
0xddong Jun 27, 2024
9011155
fix edit offer
0xddong Jun 27, 2024
47d843d
fixing repo token PV math
0xddong Jul 4, 2024
044c80d
Merge pull request #2 from term-finance/strategy-prototype
0xddong Jul 4, 2024
77eec73
Merge pull request #3 from term-finance/adding-unit-tests
0xddong Jul 4, 2024
2e8dae0
fix getCumulativeOfferData
0xddong Jul 4, 2024
0070809
fixing repo token double counting issue
0xddong Jul 4, 2024
bb351bf
Merge pull request #4 from term-finance/fix-edit-auction-offer
0xddong Jul 19, 2024
c286adc
Disable commitlint checks
robotoer Aug 5, 2024
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
18 changes: 9 additions & 9 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ jobs:
- name: run linter check on *.sol file
run: yarn lint

commits:
runs-on: ubuntu-latest
# commits:
# runs-on: ubuntu-latest

steps:
- name: Check out github repository
uses: actions/checkout@v3
with:
fetch-depth: 0
# steps:
# - name: Check out github repository
# uses: actions/checkout@v3
# with:
# fetch-depth: 0

- name: Run commitlint
uses: wagoid/commitlint-github-action@v5
# - name: Run commitlint
# uses: wagoid/commitlint-github-action@v5
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ docs/
node_modules/
.gas-snapshot

yarn.lock
yarn.lock

temp/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ libs = ['lib']
solc = "0.8.18"

remappings = [
"@openzeppelin/=lib/openzeppelin-contracts/",
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"forge-std/=lib/forge-std/src/",
"@tokenized-strategy/=lib/tokenized-strategy/src/",
"@periphery/=lib/tokenized-strategy-periphery/src/",
Expand Down
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
313 changes: 313 additions & 0 deletions src/RepoTokenList.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.18;

import "forge-std/console.sol";
import {ITermRepoToken} from "./interfaces/term/ITermRepoToken.sol";
import {ITermRepoServicer} from "./interfaces/term/ITermRepoServicer.sol";
import {ITermRepoCollateralManager} from "./interfaces/term/ITermRepoCollateralManager.sol";
import {ITermController, AuctionMetadata} from "./interfaces/term/ITermController.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {RepoTokenUtils} from "./RepoTokenUtils.sol";

struct RepoTokenListNode {
address next;
}

struct RepoTokenListData {
address head;
mapping(address => RepoTokenListNode) nodes;
mapping(address => uint256) auctionRates;
/// @notice keyed by collateral token
mapping(address => uint256) collateralTokenParams;
}

library RepoTokenList {
address public constant NULL_NODE = address(0);
uint256 internal constant INVALID_AUCTION_RATE = 0;

error InvalidRepoToken(address token);

function getRepoTokenMaturity(address repoToken) internal view returns (uint256 redemptionTimestamp) {
(redemptionTimestamp, , ,) = ITermRepoToken(repoToken).config();
}

function _getRepoTokenTimeToMaturity(uint256 redemptionTimestamp, address repoToken) private view returns (uint256) {
return redemptionTimestamp - block.timestamp;
}

function _getNext(RepoTokenListData storage listData, address current) private view returns (address) {
return listData.nodes[current].next;
}

function _count(RepoTokenListData storage listData) private view returns (uint256 count) {
if (listData.head == NULL_NODE) return 0;
address current = listData.head;
while (current != NULL_NODE) {
count++;
current = _getNext(listData, current);
}
}

function holdings(RepoTokenListData storage listData) internal view returns (address[] memory holdings) {
uint256 count = _count(listData);
if (count > 0) {
holdings = new address[](count);
uint256 i;
address current = listData.head;
while (current != NULL_NODE) {
holdings[i++] = current;
current = _getNext(listData, current);
}
}
}

function getRepoTokenWeightedTimeToMaturity(
address repoToken, uint256 repoTokenBalanceInBaseAssetPrecision
) internal view returns (uint256 weightedTimeToMaturity) {
uint256 currentMaturity = getRepoTokenMaturity(repoToken);

if (currentMaturity > block.timestamp) {
uint256 timeToMaturity = _getRepoTokenTimeToMaturity(currentMaturity, repoToken);
// Not matured yet
weightedTimeToMaturity = timeToMaturity * repoTokenBalanceInBaseAssetPrecision;
}
}

function getCumulativeRepoTokenData(
RepoTokenListData storage listData,
address repoToken,
uint256 repoTokenAmount,
uint256 purchaseTokenPrecision,
uint256 liquidBalance
) internal view returns (uint256 cumulativeWeightedTimeToMaturity, uint256 cumulativeRepoTokenAmount, bool found) {
if (listData.head == NULL_NODE) return (0, 0, false);

address current = listData.head;
while (current != NULL_NODE) {
uint256 repoTokenBalance = ITermRepoToken(current).balanceOf(address(this));

if (repoTokenBalance > 0) {
if (repoToken == current) {
repoTokenBalance += repoTokenAmount;
found = true;
}

uint256 redemptionValue = ITermRepoToken(current).redemptionValue();
uint256 repoTokenPrecision = 10**ERC20(current).decimals();

uint256 repoTokenBalanceInBaseAssetPrecision =
(redemptionValue * repoTokenBalance * purchaseTokenPrecision) /
(repoTokenPrecision * RepoTokenUtils.RATE_PRECISION);

uint256 weightedTimeToMaturity = getRepoTokenWeightedTimeToMaturity(
current, repoTokenBalanceInBaseAssetPrecision
);

cumulativeWeightedTimeToMaturity += weightedTimeToMaturity;
cumulativeRepoTokenAmount += repoTokenBalanceInBaseAssetPrecision;
}

current = _getNext(listData, current);
}
}

function removeAndRedeemMaturedTokens(RepoTokenListData storage listData) internal {
if (listData.head == NULL_NODE) return;

address current = listData.head;
address prev = current;
while (current != NULL_NODE) {
address next;
if (getRepoTokenMaturity(current) < block.timestamp) {
bool removeMaturedToken;
uint256 repoTokenBalance = ITermRepoToken(current).balanceOf(address(this));

if (repoTokenBalance > 0) {
(, , address termRepoServicer,) = ITermRepoToken(current).config();
try ITermRepoServicer(termRepoServicer).redeemTermRepoTokens(
address(this),
repoTokenBalance
) {
removeMaturedToken = true;
} catch {
// redemption failed, do not remove token from the list
}
} else {
// already redeemed
removeMaturedToken = true;
}

next = _getNext(listData, current);

if (removeMaturedToken) {
if (current == listData.head) {
listData.head = next;
}

listData.nodes[prev].next = next;
delete listData.nodes[current];
delete listData.auctionRates[current];
}
} else {
/// @dev early exit because list is sorted
break;
}

prev = current;
current = next;
}
}

function getAuctionRate(ITermController termController, ITermRepoToken repoToken) internal view returns (uint256) {
(AuctionMetadata[] memory auctionMetadata, ) = termController.getTermAuctionResults(repoToken.termRepoId());

uint256 len = auctionMetadata.length;

if (len == 0) {
revert InvalidRepoToken(address(repoToken));
}

return auctionMetadata[len - 1].auctionClearingRate;
}

function validateRepoToken(
RepoTokenListData storage listData,
ITermRepoToken repoToken,
ITermController termController,
address asset
) internal view returns (uint256 redemptionTimestamp) {
if (!termController.isTermDeployed(address(repoToken))) {
revert InvalidRepoToken(address(repoToken));
}

address purchaseToken;
address collateralManager;
(redemptionTimestamp, purchaseToken, , collateralManager) = repoToken.config();
if (purchaseToken != address(asset)) {
revert InvalidRepoToken(address(repoToken));
}

// skip matured repo tokens
if (redemptionTimestamp < block.timestamp) {
revert InvalidRepoToken(address(repoToken));
}

uint256 numTokens = ITermRepoCollateralManager(collateralManager).numOfAcceptedCollateralTokens();

for (uint256 i; i < numTokens; i++) {
address currentToken = ITermRepoCollateralManager(collateralManager).collateralTokens(i);
uint256 minCollateralRatio = listData.collateralTokenParams[currentToken];

if (minCollateralRatio == 0) {
revert InvalidRepoToken(address(repoToken));
} else if (
ITermRepoCollateralManager(collateralManager).maintenanceCollateralRatios(currentToken) < minCollateralRatio
) {
revert InvalidRepoToken(address(repoToken));
}
}
}

function validateAndInsertRepoToken(
RepoTokenListData storage listData,
ITermRepoToken repoToken,
ITermController termController,
address asset
) internal returns (uint256 auctionRate, uint256 redemptionTimestamp)
{
auctionRate = listData.auctionRates[address(repoToken)];
if (auctionRate != INVALID_AUCTION_RATE) {
(redemptionTimestamp, , ,) = repoToken.config();

// skip matured repo tokens
if (redemptionTimestamp < block.timestamp) {
revert InvalidRepoToken(address(repoToken));
}

uint256 oracleRate = getAuctionRate(termController, repoToken);
if (oracleRate != INVALID_AUCTION_RATE) {
if (auctionRate != oracleRate) {
listData.auctionRates[address(repoToken)] = oracleRate;
}
}
} else {
auctionRate = getAuctionRate(termController, repoToken);

redemptionTimestamp = validateRepoToken(listData, repoToken, termController, asset);

insertSorted(listData, address(repoToken));
listData.auctionRates[address(repoToken)] = auctionRate;
}
}

function insertSorted(RepoTokenListData storage listData, address repoToken) internal {
address current = listData.head;

if (current == NULL_NODE) {
listData.head = repoToken;
return;
}

address prev;
while (current != NULL_NODE) {

// already in list
if (current == repoToken) {
break;
}

uint256 currentMaturity = getRepoTokenMaturity(current);
uint256 maturityToInsert = getRepoTokenMaturity(repoToken);

if (maturityToInsert <= currentMaturity) {
if (prev == NULL_NODE) {
listData.head = repoToken;
} else {
listData.nodes[prev].next = repoToken;
}
listData.nodes[repoToken].next = current;
break;
}

address next = _getNext(listData, current);

if (next == NULL_NODE) {
listData.nodes[current].next = repoToken;
break;
}

prev = current;
current = next;
}
}

function getPresentValue(
RepoTokenListData storage listData,
uint256 purchaseTokenPrecision
) internal view returns (uint256 totalPresentValue) {
if (listData.head == NULL_NODE) return 0;

address current = listData.head;
while (current != NULL_NODE) {
uint256 currentMaturity = getRepoTokenMaturity(current);
uint256 repoTokenBalance = ITermRepoToken(current).balanceOf(address(this));
uint256 repoTokenPrecision = 10**ERC20(current).decimals();
uint256 auctionRate = listData.auctionRates[current];

// (ratePrecision * repoPrecision * purchasePrecision) / (repoPrecision * ratePrecision) = purchasePrecision
uint256 repoTokenBalanceInBaseAssetPrecision =
(ITermRepoToken(current).redemptionValue() * repoTokenBalance * purchaseTokenPrecision) /
(repoTokenPrecision * RepoTokenUtils.RATE_PRECISION);

if (currentMaturity > block.timestamp) {
totalPresentValue += RepoTokenUtils.calculatePresentValue(
repoTokenBalanceInBaseAssetPrecision, purchaseTokenPrecision, currentMaturity, auctionRate
);
} else {
totalPresentValue += repoTokenBalanceInBaseAssetPrecision;
}

current = _getNext(listData, current);
}
}
}
Loading
Loading