Skip to content

Commit

Permalink
fixing repo token double counting issue
Browse files Browse the repository at this point in the history
  • Loading branch information
0xddong committed Jul 4, 2024
1 parent 2e8dae0 commit 0070809
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 37 deletions.
4 changes: 1 addition & 3 deletions src/Strategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,7 @@ contract Strategy is BaseStrategy {

if (currentOfferAmount == 0) {
// new offer
termAuctionListData.insertPending(PendingOffer({
offerId: offerIds[0],
termAuctionListData.insertPending(offerIds[0], PendingOffer({
repoToken: repoToken,
offerAmount: offer.amount,
termAuction: auction,
Expand All @@ -406,7 +405,6 @@ contract Strategy is BaseStrategy {
} else {
// edit offer, overwrite existing
termAuctionListData.offers[offerIds[0]] = PendingOffer({
offerId: offerIds[0],
repoToken: repoToken,
offerAmount: offer.amount,
termAuction: auction,
Expand Down
112 changes: 78 additions & 34 deletions src/TermAuctionList.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@ import {RepoTokenList, RepoTokenListData} from "./RepoTokenList.sol";
import {RepoTokenUtils} from "./RepoTokenUtils.sol";

struct PendingOffer {
address repoToken;
uint256 offerAmount;
ITermAuction termAuction;
ITermAuctionOfferLocker offerLocker;
}

struct PendingOfferMemory {
bytes32 offerId;
address repoToken;
uint256 offerAmount;
ITermAuction termAuction;
ITermAuctionOfferLocker offerLocker;
bool isRepoTokenSeen;
}

struct TermAuctionListNode {
Expand Down Expand Up @@ -58,16 +66,15 @@ library TermAuctionList {
}
}

function insertPending(TermAuctionListData storage listData, PendingOffer memory pendingOffer) internal {
function insertPending(TermAuctionListData storage listData, bytes32 offerId, PendingOffer memory pendingOffer) internal {
bytes32 current = listData.head;
bytes32 id = pendingOffer.offerId;

if (current != NULL_NODE) {
listData.nodes[id].next = current;
listData.nodes[offerId].next = current;
}

listData.head = id;
listData.offers[id] = pendingOffer;
listData.head = offerId;
listData.offers[offerId] = pendingOffer;
}

function removeCompleted(
Expand All @@ -91,7 +98,7 @@ library TermAuctionList {
PendingOffer memory offer = listData.offers[current];
bytes32 next = _getNext(listData, current);

uint256 offerAmount = offer.offerLocker.lockedOffer(offer.offerId).amount;
uint256 offerAmount = offer.offerLocker.lockedOffer(current).amount;
bool removeNode;
bool insertRepoToken;

Expand All @@ -111,7 +118,7 @@ library TermAuctionList {

// withdraw manually
bytes32[] memory offerIds = new bytes32[](1);
offerIds[0] = offer.offerId;
offerIds[0] = current;
offer.offerLocker.unlockOffers(offerIds);
}
}
Expand All @@ -135,39 +142,73 @@ library TermAuctionList {
}
}

function _loadOffers(TermAuctionListData storage listData) private view returns (PendingOfferMemory[] memory offers) {
uint256 len = _count(listData);
offers = new PendingOfferMemory[](len);

uint256 i;
bytes32 current = listData.head;
while (current != NULL_NODE) {
PendingOffer memory currentOffer = listData.offers[current];
PendingOfferMemory memory newOffer = offers[i];

newOffer.offerId = current;
newOffer.repoToken = currentOffer.repoToken;
newOffer.offerAmount = currentOffer.offerAmount;
newOffer.termAuction = currentOffer.termAuction;
newOffer.offerLocker = currentOffer.offerLocker;

i++;
current = _getNext(listData, current);
}
}

function _markRepoTokenAsSeen(PendingOfferMemory[] memory offers, address repoToken) private view {
for (uint256 i; i < offers.length; i++) {
if (repoToken == offers[i].repoToken) {
offers[i].isRepoTokenSeen = true;
}
}
}

function getPresentValue(
TermAuctionListData storage listData,
RepoTokenListData storage repoTokenListData,
ITermController termController,
uint256 purchaseTokenPrecision
) internal view returns (uint256 totalValue) {
if (listData.head == NULL_NODE) return 0;

PendingOfferMemory[] memory offers = _loadOffers(listData);

bytes32 current = listData.head;
while (current != NULL_NODE) {
PendingOffer memory offer = listData.offers[current];
for (uint256 i; i < offers.length; i++) {
PendingOfferMemory memory offer = offers[i];

uint256 offerAmount = offer.offerLocker.lockedOffer(offer.offerId).amount;

/// @dev offer processed, but auctionClosed not yet called and auction is new so repoToken not on List and wont be picked up
/// checking repoTokenAuctionRates to make sure we are not double counting on re-openings
if (offer.termAuction.auctionCompleted() && repoTokenListData.auctionRates[offer.repoToken] == 0) {
uint256 repoTokenAmountInBaseAssetPrecision = RepoTokenUtils.getNormalizedRepoTokenAmount(
offer.repoToken,
ITermRepoToken(offer.repoToken).balanceOf(address(this)),
purchaseTokenPrecision
);
totalValue += RepoTokenUtils.calculatePresentValue(
repoTokenAmountInBaseAssetPrecision,
purchaseTokenPrecision,
RepoTokenList.getRepoTokenMaturity(offer.repoToken),
RepoTokenList.getAuctionRate(termController, ITermRepoToken(offer.repoToken))
);
if (!offer.isRepoTokenSeen) {
uint256 repoTokenAmountInBaseAssetPrecision = RepoTokenUtils.getNormalizedRepoTokenAmount(
offer.repoToken,
ITermRepoToken(offer.repoToken).balanceOf(address(this)),
purchaseTokenPrecision
);
totalValue += RepoTokenUtils.calculatePresentValue(
repoTokenAmountInBaseAssetPrecision,
purchaseTokenPrecision,
RepoTokenList.getRepoTokenMaturity(offer.repoToken),
RepoTokenList.getAuctionRate(termController, ITermRepoToken(offer.repoToken))
);

// since multiple offers can be tied to the same repo token, we need to mark
// the repo tokens we've seen to avoid double counting
_markRepoTokenAsSeen(offers, offer.repoToken);
}
} else {
totalValue += offerAmount;
}

current = _getNext(listData, current);
}
}

Expand All @@ -181,9 +222,10 @@ library TermAuctionList {
) internal view returns (uint256 cumulativeWeightedTimeToMaturity, uint256 cumulativeOfferAmount, bool found) {
if (listData.head == NULL_NODE) return (0, 0, false);

bytes32 current = listData.head;
while (current != NULL_NODE) {
PendingOffer memory offer = listData.offers[current];
PendingOfferMemory[] memory offers = _loadOffers(listData);

for (uint256 i; i < offers.length; i++) {
PendingOfferMemory memory offer = offers[i];

uint256 offerAmount;
if (offer.repoToken == repoToken) {
Expand All @@ -196,12 +238,16 @@ library TermAuctionList {
/// checking repoTokenAuctionRates to make sure we are not double counting on re-openings
if (offer.termAuction.auctionCompleted() && repoTokenListData.auctionRates[offer.repoToken] == 0) {
// use normalized repo token amount if repo token is not in the list
offerAmount = RepoTokenUtils.getNormalizedRepoTokenAmount(
offer.repoToken,
ITermRepoToken(offer.repoToken).balanceOf(address(this)),
purchaseTokenPrecision
);
}
if (!offer.isRepoTokenSeen) {
offerAmount = RepoTokenUtils.getNormalizedRepoTokenAmount(
offer.repoToken,
ITermRepoToken(offer.repoToken).balanceOf(address(this)),
purchaseTokenPrecision
);

_markRepoTokenAsSeen(offers, offer.repoToken);
}
}
}

if (offerAmount > 0) {
Expand All @@ -212,8 +258,6 @@ library TermAuctionList {
cumulativeWeightedTimeToMaturity += weightedTimeToMaturity;
cumulativeOfferAmount += offerAmount;
}

current = _getNext(listData, current);
}
}
}
26 changes: 26 additions & 0 deletions src/test/TestUSDCOffers.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,32 @@ contract TestUSDCSubmitOffer is Setup {
assertEq(offers[1], offerId1);
}

function testMultipleOffersFillAndNoFill() public {
uint256 offer1Amount = 1e6;
uint256 offer2Amount = 5e6;
bytes32 offerId1 = _submitOffer(bytes32("offer id hash 1"), offer1Amount);
bytes32 offerId2 = _submitOffer(bytes32("offer id hash 2"), offer2Amount);

bytes32[] memory offerIds = new bytes32[](2);
offerIds[0] = offerId1;
offerIds[1] = offerId2;
uint256[] memory fillAmounts = new uint256[](2);

// test: offer 1 filled, offer 2 not filled
fillAmounts[0] = offer1Amount;
fillAmounts[1] = 0;
uint256[] memory repoTokenAmounts = new uint256[](2);
repoTokenAmounts[0] = _getRepoTokenAmountGivenPurchaseTokenAmount(
offer1Amount, repoToken1Week, TEST_REPO_TOKEN_RATE
);
repoTokenAmounts[1] = 0;

repoToken1WeekAuction.auctionSuccess(offerIds, fillAmounts, repoTokenAmounts);

// test: asset value should equal to initial asset value (liquid + pending offers)
assertEq(termStrategy.totalAssetValue(), initialState.totalAssetValue);
}

function testEditOfferTotalGreaterThanCurrentLiquidity() public {
bytes32 idHash1 = bytes32("offer id hash 1");
bytes32 offerId1 = _submitOffer(idHash1, 50e6);
Expand Down

0 comments on commit 0070809

Please sign in to comment.