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

Update _assertAuction to check borrower t0ReserveSettleAmount and add invariant A10 #1056

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
28 changes: 15 additions & 13 deletions src/PoolInfoUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ contract PoolInfoUtils is Multicall {
kickTime_,
referencePrice_,
neutralPrice_,
debtToCollateral_, , , ) = IPool(ajnaPool_).auctionInfo(borrower_);
debtToCollateral_, , , , ) = IPool(ajnaPool_).auctionInfo(borrower_);

if (kickTime_ != 0) {
(debtToCover_, collateral_, , ) = this.borrowerInfo(ajnaPool_, borrower_);
Expand All @@ -99,18 +99,19 @@ contract PoolInfoUtils is Multicall {
/**
* @notice Returns details of an auction for a given borrower address.
* @dev Calls and returns all values from pool.auctionInfo().
* @param ajnaPool_ Address of `Ajna` pool.
* @param borrower_ Address of the borrower that is liquidated.
* @return kicker_ Address of the kicker that is kicking the auction.
* @return bondFactor_ The factor used for calculating bond size.
* @return bondSize_ The bond amount in quote token terms.
* @return kickTime_ Time the liquidation was initiated.
* @return referencePrice_ Price used to determine auction start price.
* @return neutralPrice_ `Neutral Price` of auction.
* @return debtToCollateral_ Borrower debt to collateral at time of kick, which is used in BPF for kicker's reward calculation.
* @return head_ Address of the head auction.
* @return next_ Address of the next auction in queue.
* @return prev_ Address of the prev auction in queue.
* @param ajnaPool_ Address of `Ajna` pool.
* @param borrower_ Address of the borrower that is liquidated.
* @return kicker_ Address of the kicker that is kicking the auction.
* @return bondFactor_ The factor used for calculating bond size.
* @return bondSize_ The bond amount in quote token terms.
* @return kickTime_ Time the liquidation was initiated.
* @return referencePrice_ Price used to determine auction start price.
* @return neutralPrice_ `Neutral Price` of auction.
* @return debtToCollateral_ Borrower debt to collateral at time of kick, which is used in BPF for kicker's reward calculation.
* @return t0ReserveSettleAmount_ Amount of t0Debt that could be settled via reserves in this auction
* @return head_ Address of the head auction.
* @return next_ Address of the next auction in queue.
* @return prev_ Address of the prev auction in queue.
*/
function auctionInfo(address ajnaPool_, address borrower_) external view returns (
address kicker_,
Expand All @@ -120,6 +121,7 @@ contract PoolInfoUtils is Multicall {
uint256 referencePrice_,
uint256 neutralPrice_,
uint256 debtToCollateral_,
uint256 t0ReserveSettleAmount_,
address head_,
address next_,
address prev_
Expand Down
2 changes: 2 additions & 0 deletions src/base/Pool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool {
uint256 referencePrice_,
uint256 neutralPrice_,
uint256 debtToCollateral_,
uint256 t0ReserveSettleAmount_,
address head_,
address next_,
address prev_
Expand All @@ -754,6 +755,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool {
liquidation.referencePrice,
liquidation.neutralPrice,
liquidation.debtToCollateral,
liquidation.t0ReserveSettleAmount,
auctions.head,
liquidation.next,
liquidation.prev
Expand Down
24 changes: 13 additions & 11 deletions src/interfaces/pool/commons/IPoolState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ interface IPoolState {

/**
* @notice Returns details of an auction for a given borrower address.
* @param borrower_ Address of the borrower that is liquidated.
* @return kicker_ Address of the kicker that is kicking the auction.
* @return bondFactor_ The factor used for calculating bond size.
* @return bondSize_ The bond amount in quote token terms.
* @return kickTime_ Time the liquidation was initiated.
* @return referencePrice_ Price used to determine auction start price.
* @return neutralPrice_ `Neutral Price` of auction.
* @return debtToCollateral_ Borrower debt to collateral, which is used in BPF for kicker's reward calculation.
* @return head_ Address of the head auction.
* @return next_ Address of the next auction in queue.
* @return prev_ Address of the prev auction in queue.
* @param borrower_ Address of the borrower that is liquidated.
* @return kicker_ Address of the kicker that is kicking the auction.
* @return bondFactor_ The factor used for calculating bond size.
* @return bondSize_ The bond amount in quote token terms.
* @return kickTime_ Time the liquidation was initiated.
* @return referencePrice_ Price used to determine auction start price.
* @return neutralPrice_ `Neutral Price` of auction.
* @return debtToCollateral_ Borrower debt to collateral, which is used in BPF for kicker's reward calculation.
* @return t0ReserveSettleAmount_ Amount of t0Debt that could be settled via reserves in this auction
* @return head_ Address of the head auction.
* @return next_ Address of the next auction in queue.
* @return prev_ Address of the prev auction in queue.
*/
function auctionInfo(address borrower_)
external
Expand All @@ -32,6 +33,7 @@ interface IPoolState {
uint256 referencePrice_,
uint256 neutralPrice_,
uint256 debtToCollateral_,
uint256 t0ReserveSettleAmount_,
address head_,
address next_,
address prev_
Expand Down
1 change: 1 addition & 0 deletions tests/INVARIANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
- **A7**: total bond escrowed accumulator (`AuctionsState.totalBondEscrowed`) should increase when auction is kicked with the difference needed to cover the bond and should decrease only when kicker bonds withdrawn (`Pool.withdrawBonds`). Claimable bonds should be available for withdrawal from pool at any time.
- **A8**: Upon a take/arbtake/deposittake the kicker reward <= borrower penalty
- **A9**: reference prices in liquidation queue shall not decrease
- **A10**: Upon kick `t0ReserveSettleAmount` should be set to `BorrowerT0Debt * borrowFeeRate / 2` for the kicked borrower.

## Loans
- **L1**: for each `Loan` in loans array (`LoansState.loans`) starting from index 1, the corresponding address (`Loan.borrower`) is not `0x`, the borrower t0 debt to collateral (`Loan.t0DebtToCollateral`) is different than 0 and the id mapped in indices mapping (`LoansState.indices`) equals index of loan in loans array.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ contract PurchaseQuoteWithExternalLiquidityTest is Test {
address internal _lender;

function setUp() external {
vm.createSelectFork(vm.envString("ETH_RPC_URL"));
vm.createSelectFork(vm.envString("ETH_RPC_URL"), 18800000);
_ajnaPool = ERC20Pool(new ERC20PoolFactory(AJNA).deployPool(WETH, USDC, 0.05 * 10**18));
_lender = makeAddr("lender");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract ERC20PoolPositionHandler is PositionPoolHandler, BaseERC20PoolHandler,
address[] internal _borrowers;

uint16 internal constant LENDERS = 200;
uint16 internal constant LOANS_COUNT = 500;
uint16 internal constant LOANS_COUNT = 320;
uint16 nonce;
uint256 numberOfBuckets;

Expand Down Expand Up @@ -117,7 +117,7 @@ contract ERC20PoolPositionHandler is PositionPoolHandler, BaseERC20PoolHandler,
) external useTimestamps useRandomActor(takerIndex_) skipTime(skippedTime_) writeLogs {
address borrower = _borrowers[constrictToRange(borrowerIndex_, 0, _borrowers.length - 1)];

(, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower);
(, , , uint256 kickTime, , , , , , , ) = _pool.auctionInfo(borrower);

// Kick borrower if not already kicked
if (kickTime == 0) {
Expand Down
2 changes: 1 addition & 1 deletion tests/forge/invariants/base/BasicInvariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ abstract contract BasicInvariants is BaseInvariants {
for (uint256 i = 0; i < actorCount; i++) {
address borrower = IBaseHandler(_handler).actors(i);

(, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower);
(, , , uint256 kickTime, , , , , , , ) = _pool.auctionInfo(borrower);

if (kickTime == 0) {
(uint256 borrowerT0Debt, uint256 borrowerCollateral, ) = _pool.borrowerInfo(borrower);
Expand Down
26 changes: 20 additions & 6 deletions tests/forge/invariants/base/LiquidationInvariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ abstract contract LiquidationInvariants is BasicInvariants {
_invariant_A7();
_invariant_A8();
_invariant_A9();
_invariant_A10();
}

/// @dev checks sum of all borrower's t0debt is equals to total pool t0debtInAuction
Expand All @@ -31,7 +32,7 @@ abstract contract LiquidationInvariants is BasicInvariants {

for (uint256 i = 0; i < actorCount; i++) {
address borrower = IBaseHandler(_handler).actors(i);
(, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower);
(, , , uint256 kickTime, , , , , , , ) = _pool.auctionInfo(borrower);

if (kickTime != 0) {
(uint256 t0debt, , ) = _pool.borrowerInfo(borrower);
Expand Down Expand Up @@ -63,7 +64,7 @@ abstract contract LiquidationInvariants is BasicInvariants {
uint256 lockedBonds;
for (uint256 i = 0; i < actorCount; i++) {
address borrower = IBaseHandler(_handler).actors(i);
(, , uint256 bond, , , , , , , ) = _pool.auctionInfo(borrower);
(, , uint256 bond, , , , , , , , ) = _pool.auctionInfo(borrower);
lockedBonds += bond;
}
require(lockedBonds == kickerLockedBond, "A2: bonds in auctions != than kicker locked bonds");
Expand Down Expand Up @@ -95,7 +96,7 @@ abstract contract LiquidationInvariants is BasicInvariants {
for (uint256 i = 0; i < actorCount; i++) {
address borrower = IBaseHandler(_handler).actors(i);

(, , , uint256 kickTime, , , , , , ) = _pool.auctionInfo(borrower);
(, , , uint256 kickTime, , , , , , , ) = _pool.auctionInfo(borrower);

if (kickTime != 0) borrowersKicked += 1;
}
Expand All @@ -109,7 +110,7 @@ abstract contract LiquidationInvariants is BasicInvariants {

for (uint256 i = 0; i < actorCount; i++) {
address borrower = IBaseHandler(_handler).actors(i);
(address kicker, , uint256 bondSize, , , , , , , ) = _pool.auctionInfo(borrower);
(address kicker, , uint256 bondSize, , , , , , , , ) = _pool.auctionInfo(borrower);
(, uint256 lockedAmount) = _pool.kickerInfo(kicker);

require(lockedAmount >= bondSize, "Auction Invariant A5");
Expand Down Expand Up @@ -145,13 +146,26 @@ abstract contract LiquidationInvariants is BasicInvariants {
/// @dev reference prices in liquidation queue shall not decrease
function _invariant_A9() internal view {
uint256 referencePrice;
(,,,, uint256 lastReferencePrice,,, address nextBorrower,,) = _pool.auctionInfo(address(0));
(,,,, uint256 lastReferencePrice,,,, address nextBorrower,,) = _pool.auctionInfo(address(0));
while (nextBorrower != address(0)) {
(,,,, referencePrice,,,, nextBorrower,) = _pool.auctionInfo(nextBorrower);
(,,,, referencePrice,,,,, nextBorrower,) = _pool.auctionInfo(nextBorrower);
require(lastReferencePrice <= referencePrice, "Auction Invariant A9");
lastReferencePrice = referencePrice;
}
}

/// @dev borrower t0 reserve settle amount should be set borrowerT0debt * borrowFeeRate / 2.
function _invariant_A10() internal view {
uint256 actorCount = IBaseHandler(_handler).getActorsCount();

for (uint256 i = 0; i < actorCount; i++) {
address borrower = IBaseHandler(_handler).actors(i);
uint256 borrowerT0ReserveSettleAmount = IBaseHandler(_handler).borrowerT0ReserveSettleAmount(borrower);
(,,,,,,,uint256 requiredT0ReserveSettleAmount,,,) = _pool.auctionInfo(borrower);

require(borrowerT0ReserveSettleAmount == requiredT0ReserveSettleAmount, "Auction Invariant A10");
}
}

function invariant_call_summary() public virtual override useCurrentTimestamp {
console.log("\nCall Summary\n");
Expand Down
23 changes: 16 additions & 7 deletions tests/forge/invariants/base/handlers/unbounded/BaseHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,11 @@ abstract contract BaseHandler is Test {

struct AuctionInfo {
address kicker;
uint256 bondFactor;
uint256 bondSize;
uint256 kickTime;
uint256 referencePrice;
uint256 neutralPrice;
uint256 debtToCollateral;
uint256 t0ReserveSettleAmount;
uint256 auctionPrice;
uint256 auctionPriceIndex;
address head;
Expand Down Expand Up @@ -144,6 +143,9 @@ abstract contract BaseHandler is Test {
uint256 public borrowerPenalty; // Borrower penalty on take
uint256 public kickerReward; // Kicker reward on take

// Borrower reserve settlement state
mapping(address => uint256) public borrowerT0ReserveSettleAmount;

// All Buckets used in invariant testing that also includes Buckets where collateral is added when a borrower is in auction and has partial NFT
EnumerableSet.UintSet internal buckets;

Expand Down Expand Up @@ -331,12 +333,13 @@ abstract contract BaseHandler is Test {
function _getAuctionInfo(address borrower_) internal view returns (AuctionInfo memory auctionInfo_) {
(
auctionInfo_.kicker,
auctionInfo_.bondFactor,
auctionInfo_.bondSize,
,
,
auctionInfo_.kickTime,
auctionInfo_.referencePrice,
auctionInfo_.neutralPrice,
auctionInfo_.debtToCollateral,
auctionInfo_.t0ReserveSettleAmount,
auctionInfo_.head,
,
) = _pool.auctionInfo(borrower_);
Expand Down Expand Up @@ -527,6 +530,12 @@ abstract contract BaseHandler is Test {
decreaseInBonds = 0;
// record totalBondEscrowed before each action
(previousTotalBonds, , , , ) = _pool.reservesInfo();

// Record borrower reserve settle amount
for(uint256 i = 0; i < actors.length; i++) {
address borrower = actors[i];
borrowerT0ReserveSettleAmount[borrower] = _getAuctionInfo(borrower).t0ReserveSettleAmount;
}
}

/********************************/
Expand Down Expand Up @@ -662,7 +671,7 @@ abstract contract BaseHandler is Test {
) = _poolInfo.poolLoansInfo(address(_pool));

(
, , , , , , ,
, , , , , , , ,
address headAuction, ,
) = _pool.auctionInfo(address(0));

Expand Down Expand Up @@ -778,11 +787,11 @@ abstract contract BaseHandler is Test {
uint256 bondFactor;
uint256 bondSize;
uint256 neutralPrice;
(,,,,,,, nextBorrower,,) = _pool.auctionInfo(address(0));
(,,,,,,,, nextBorrower,,) = _pool.auctionInfo(address(0));
while (nextBorrower != address(0)) {
data = string(abi.encodePacked("Borrower ", Strings.toHexString(uint160(nextBorrower), 20), " Auction Details :"));
printInNextLine(data);
(, bondFactor, bondSize, kickTime, referencePrice, neutralPrice,,, nextBorrower,) = _pool.auctionInfo(nextBorrower);
(, bondFactor, bondSize, kickTime, referencePrice, neutralPrice,,,, nextBorrower,) = _pool.auctionInfo(nextBorrower);

printLog("Bond Factor = ", bondFactor);
printLog("Bond Size = ", bondSize);
Expand Down
Loading
Loading