Skip to content
This repository has been archived by the owner on Jan 26, 2024. It is now read-only.

Commit

Permalink
Feat/buyout test (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
SantiagoGregory authored Nov 15, 2022
1 parent 99d8b34 commit ea5d5bd
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 65 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ out

lcov.info

scripts/loanProofGenerator.js
14 changes: 1 addition & 13 deletions src/AstariaRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,9 @@ contract AstariaRouter is Auth, ERC4626Router, Pausable, IAstariaRouter {
s.maxInterestRate = ((uint256(1e16) * 200) / (365 days)).safeCastTo88(); //63419583966; // 200% apy / second
s.strategistFeeNumerator = uint32(200);
s.strategistFeeDenominator = uint32(1000);
s.buyoutFeeNumerator = uint32(200);
s.buyoutFeeNumerator = uint32(100);
s.buyoutFeeDenominator = uint32(1000);
s.minDurationIncrease = uint32(5 days);
s.buyoutInterestWindow = uint32(60 days);
s.guardian = address(msg.sender);
}

Expand Down Expand Up @@ -259,9 +258,6 @@ contract AstariaRouter is Auth, ERC4626Router, Pausable, IAstariaRouter {
} else if (what == FileType.FeeTo) {
address addr = abi.decode(data, (address));
s.feeTo = addr;
} else if (what == FileType.BuyoutInterestWindow) {
uint256 value = abi.decode(data, (uint256));
s.buyoutInterestWindow = value.safeCastTo32();
} else if (what == FileType.StrategyValidator) {
(uint8 TYPE, address addr) = abi.decode(data, (uint8, address));
s.strategyValidators[TYPE] = addr;
Expand Down Expand Up @@ -618,14 +614,6 @@ contract AstariaRouter is Auth, ERC4626Router, Pausable, IAstariaRouter {
);
}

/**
* @notice Retrieves the time window for computing maxbuyout costs
* @return The numerator and denominator used to compute the percentage fee taken by the protocol
*/
function getBuyoutInterestWindow() external view returns (uint32) {
return _loadRouterSlot().buyoutInterestWindow;
}

/**
* @notice Returns whether a given address is that of a Vault.
* @param vault The Vault address.
Expand Down
72 changes: 39 additions & 33 deletions src/LienToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -145,25 +145,34 @@ contract LienToken is ERC721, ILienToken, Auth {
) {
revert InvalidState(InvalidStates.COLLATERAL_AUCTION);
}
(uint256 owed, uint256 buyout) = getBuyout(
(uint256 owed, uint256 buyout) = _getBuyout(
s,
params.encumber.stack[params.position]
);

if (params.encumber.lien.details.maxAmount < owed) {
revert InvalidBuyoutDetails(params.encumber.lien.details.maxAmount, owed);
}
if (
msg.sender !=
s.COLLATERAL_TOKEN.ownerOf(
params.encumber.stack[params.position].lien.collateralId
) &&
msg.sender != ownerOf(params.encumber.stack[params.position].point.lienId)
) {
s.TRANSFER_PROXY.tokenTransferFrom(
s.WETH,
address(msg.sender),
_getPayee(s, params.encumber.stack[params.position].point.lienId),
buyout

s.TRANSFER_PROXY.tokenTransferFrom(
s.WETH,
address(msg.sender),
_getPayee(s, params.encumber.stack[params.position].point.lienId),
buyout
);

address payee = _getPayee(
s,
params.encumber.stack[params.position].point.lienId
);
if (_isPublicVault(s, payee)) {
IPublicVault(payee).handleBuyoutLien(
IPublicVault.BuyoutLienParams({
lienSlope: calculateSlope(params.encumber.stack[params.position]),
lienEnd: params.encumber.stack[params.position].point.end,
increaseYIntercept: buyout -
params.encumber.stack[params.position].point.amount
})
);
}

Expand Down Expand Up @@ -194,11 +203,6 @@ contract LienToken is ERC721, ILienToken, Auth {
newStack[i] = newLien;
_burn(oldLienId);
delete s.lienMeta[oldLienId];
if (s.ASTARIA_ROUTER.isValidVault(stack[i].lien.vault)) {
IPublicVault(stack[i].lien.vault).decreaseEpochLienCount(
stack[i].point.end
);
}
} else {
newStack[i] = stack[i];
}
Expand Down Expand Up @@ -479,11 +483,20 @@ contract LienToken is ERC721, ILienToken, Auth {
{
LienStorage storage s = _loadLienStorageSlot();

uint256 remainingInterest = _getRemainingInterest(s, stack, true);
uint256 buyoutTotal = stack.point.amount +
return _getBuyout(s, stack);
}

function _getBuyout(LienStorage storage s, Stack calldata stack)
internal
view
returns (uint256, uint256)
{
uint256 remainingInterest = _getRemainingInterest(s, stack);
uint256 owed = _getOwed(stack, block.timestamp);
uint256 buyoutTotal = owed +
s.ASTARIA_ROUTER.getBuyoutFee(remainingInterest);

return (_getOwed(stack, block.timestamp), buyoutTotal);
return (owed, buyoutTotal);
}

function makePayment(Stack[] calldata stack, uint256 amount)
Expand Down Expand Up @@ -662,21 +675,14 @@ contract LienToken is ERC721, ILienToken, Auth {
* @dev Computes the interest still owed to a Lien.
* @param s active storage slot
* @param stack the lien
* @param buyout compute with a ceiling based on the buyout interest window
* @return The WETH still owed in interest to the Lien.
*/
function _getRemainingInterest(
LienStorage storage s,
Stack memory stack,
bool buyout
) internal view returns (uint256) {
function _getRemainingInterest(LienStorage storage s, Stack memory stack)
internal
view
returns (uint256)
{
uint256 end = stack.point.end;
if (buyout) {
uint32 buyoutInterestWindow = s.ASTARIA_ROUTER.getBuyoutInterestWindow();
if (end >= block.timestamp + buyoutInterestWindow) {
end = block.timestamp + buyoutInterestWindow;
}
}

uint256 delta_t = end - block.timestamp;

Expand Down
15 changes: 15 additions & 0 deletions src/PublicVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,21 @@ contract PublicVault is
return ROUTER().LIEN_TOKEN();
}

function handleBuyoutLien(BuyoutLienParams calldata params) public {
require(msg.sender == address(LIEN_TOKEN()));
VaultData storage s = _loadStorageSlot();

unchecked {
s.slope -= params.lienSlope.safeCastTo48();
s.yIntercept += params.increaseYIntercept.safeCastTo88();
s.last = block.timestamp.safeCastTo40();
}

uint64 lienEpoch = getLienEpoch(params.lienEnd.safeCastTo64());
_decreaseEpochLienCount(s, lienEpoch);
emit YInterceptChanged(s.yIntercept);
}

/**
* @notice
* @param maxAuctionWindow The max possible auction duration.
Expand Down
5 changes: 1 addition & 4 deletions src/interfaces/IAstariaRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,14 @@ interface IAstariaRouter is IPausable, IBeacon {
address BEACON_PROXY_IMPLEMENTATION; //20
uint88 maxInterestRate; //6
uint32 minInterestBPS; // was uint64
mapping(uint8 => address) strategyValidators;
//slot 3 +
address guardian; //20
uint32 buyoutFeeNumerator;
uint32 buyoutFeeDenominator;
uint32 strategistFeeDenominator;
uint32 strategistFeeNumerator; //4
uint32 minDurationIncrease;
uint32 buyoutInterestWindow;
mapping(uint32 => address) strategyValidators;
mapping(uint8 => address) implementations;
//A strategist can have many deployed vaults
mapping(address => address) vaults;
Expand Down Expand Up @@ -211,8 +210,6 @@ interface IAstariaRouter is IPausable, IBeacon {

function getLiquidatorFee(uint256) external view returns (uint256);

function getBuyoutInterestWindow() external view returns (uint32);

/**
* @notice Liquidate a CollateralToken that has defaulted on one of its liens.
* @param collateralId The ID of the CollateralToken.
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces/IPublicVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ interface IPublicVault is IVaultImplementation {
uint256 interestOwed;
}

struct BuyoutLienParams {
uint256 lienSlope;
uint256 lienEnd;
uint256 increaseYIntercept;
}

struct AfterLiquidationParams {
uint256 lienSlope;
uint256 newAmount;
Expand Down Expand Up @@ -104,6 +110,8 @@ interface IPublicVault is IVaultImplementation {

function decreaseYIntercept(uint256 amount) external;

function handleBuyoutLien(BuyoutLienParams calldata params) external;

function updateVaultAfterLiquidation(
uint256 auctionWindow,
AfterLiquidationParams calldata params
Expand Down
65 changes: 50 additions & 15 deletions src/test/AstariaTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,12 @@ contract AstariaTest is TestHelpers {
isFirstLien: true
});

// buyout liens
vm.warp(block.timestamp + 3 days);

uint256 accruedInterest = uint256(LIEN_TOKEN.getOwed(stack[0]));
uint256 tenthOfRemaining = (uint256(
LIEN_TOKEN.getOwed(stack[0], block.timestamp + 7 days)
) - accruedInterest).mulDivDown(1, 10);

address privateVault = _createPrivateVault({
strategist: strategistOne,
Expand All @@ -403,27 +408,57 @@ contract AstariaTest is TestHelpers {
Lender({addr: strategistOne, amountToLend: 50 ether}),
privateVault
);

VaultImplementation(privateVault).buyoutLien(
tokenContract.computeId(tokenId),
uint8(0),
refinanceTerms,
stack
);

// LIEN_TOKEN.buyoutLien(liens[0], 10 ether, address(1), address(1));

// uint256 collateralId = tokenContract.computeId(tokenId);
//
// // make sure the borrow was successful
// assertEq(WETH9.balanceOf(address(this)), initialBalance + 10 ether);
//
// vm.warp(block.timestamp + 9 days);
//
// _repay(collateralId, 50 ether, address(this));
//
// COLLATERAL_TOKEN.releaseToAddress(collateralId, address(this));
//
// assertEq(ERC721(tokenContract).ownerOf(tokenId), address(this));
assertEq(
WETH9.balanceOf(privateVault),
40 ether - tenthOfRemaining - (accruedInterest - stack[0].point.amount),
"Incorrect PrivateVault balance"
);
assertEq(
WETH9.balanceOf(publicVault),
50 ether + tenthOfRemaining + ((accruedInterest - stack[0].point.amount)),
"Incorrect PublicVault balance"
);
assertEq(
PublicVault(publicVault).getYIntercept(),
50 ether + tenthOfRemaining + ((accruedInterest - stack[0].point.amount)),
"Incorrect PublicVault YIntercept"
);
assertEq(
PublicVault(publicVault).totalAssets(),
50 ether + tenthOfRemaining + (accruedInterest - stack[0].point.amount),
"Incorrect PublicVault YIntercept"
);
assertEq(
PublicVault(publicVault).getSlope(),
0,
"Incorrect PublicVault slope"
);

_signalWithdraw(address(1), publicVault);
_warpToEpochEnd(publicVault);
PublicVault(publicVault).processEpoch();
PublicVault(publicVault).transferWithdrawReserve();

WithdrawProxy withdrawProxy = PublicVault(publicVault).getWithdrawProxy(0);

withdrawProxy.redeem(
withdrawProxy.balanceOf(address(1)),
address(1),
address(1)
);
assertEq(
WETH9.balanceOf(address(1)),
50 ether + tenthOfRemaining + (accruedInterest - stack[0].point.amount),
"Incorrect withdrawer balance"
);
}

function testReleaseToAddress() public {
Expand Down

0 comments on commit ea5d5bd

Please sign in to comment.