From c3382be709c6168464d7a8be8b9fdc81b3b19e61 Mon Sep 17 00:00:00 2001 From: halaprix Date: Thu, 30 Nov 2023 15:37:07 +0100 Subject: [PATCH 1/6] chore: update ajna contracts to `rc10` / `rc9.5` --- .../contracts/ajna/ERC20Pool.sol | 90 +++-- .../contracts/ajna/ERC20PoolFactory.sol | 15 +- .../contracts/ajna/ERC721Pool.sol | 140 ++++---- .../contracts/ajna/ERC721PoolFactory.sol | 17 +- .../contracts/ajna/PoolInfoUtils.sol | 302 ++++++++++------- .../contracts/ajna/PoolInfoUtilsMulticall.sol | 76 ++--- .../contracts/ajna/PositionManager.sol | 187 ++++++---- .../contracts/ajna/RewardsManager.sol | 318 +++++++++++------- .../ajna/ajna-actions/AjnaProxyActions.sol | 63 +++- .../contracts/ajna/base/Pool.sol | 7 +- .../pool/commons/IPoolLenderActions.sol | 3 +- .../ajna/libraries/external/KickerActions.sol | 3 +- .../ajna/libraries/external/LenderActions.sol | 23 +- .../ajna/libraries/external/PoolCommons.sol | 12 +- .../ajna/libraries/helpers/PoolHelper.sol | 13 +- 15 files changed, 774 insertions(+), 495 deletions(-) diff --git a/packages/ajna-contracts/contracts/ajna/ERC20Pool.sol b/packages/ajna-contracts/contracts/ajna/ERC20Pool.sol index 7520b8a41..ca149424e 100644 --- a/packages/ajna-contracts/contracts/ajna/ERC20Pool.sol +++ b/packages/ajna-contracts/contracts/ajna/ERC20Pool.sol @@ -3,23 +3,26 @@ pragma solidity 0.8.18; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { +import { IERC20Pool, IERC20PoolBorrowerActions, IERC20PoolImmutables, IERC20PoolLenderActions -} from "./interfaces/pool/erc20/IERC20Pool.sol"; -import { IERC20Taker } from "./interfaces/pool/erc20/IERC20Taker.sol"; +} from './interfaces/pool/erc20/IERC20Pool.sol'; +import { IERC20Taker } from './interfaces/pool/erc20/IERC20Taker.sol'; import { IPoolLenderActions, IPoolKickerActions, IPoolTakerActions, IPoolSettlerActions -} from "./interfaces/pool/IPool.sol"; -import { IERC3156FlashBorrower, IERC3156FlashLender } from "./interfaces/pool/IERC3156FlashLender.sol"; +} from './interfaces/pool/IPool.sol'; +import { + IERC3156FlashBorrower, + IERC3156FlashLender +} from './interfaces/pool/IERC3156FlashLender.sol'; import { DrawDebtResult, @@ -27,26 +30,29 @@ import { SettleParams, SettleResult, TakeResult -} from "./interfaces/pool/commons/IPoolInternals.sol"; -import { PoolState } from "./interfaces/pool/commons/IPoolState.sol"; +} from './interfaces/pool/commons/IPoolInternals.sol'; +import { PoolState } from './interfaces/pool/commons/IPoolState.sol'; -import { FlashloanablePool } from "./base/FlashloanablePool.sol"; +import { FlashloanablePool } from './base/FlashloanablePool.sol'; import { _getCollateralDustPricePrecisionAdjustment, _roundToScale, _roundUpToScale -} from "./libraries/helpers/PoolHelper.sol"; -import { _revertIfAuctionClearable, _revertAfterExpiry } from "./libraries/helpers/RevertsHelper.sol"; +} from './libraries/helpers/PoolHelper.sol'; +import { + _revertIfAuctionClearable, + _revertAfterExpiry +} from './libraries/helpers/RevertsHelper.sol'; -import { Loans } from "./libraries/internal/Loans.sol"; -import { Deposits } from "./libraries/internal/Deposits.sol"; -import { Maths } from "./libraries/internal/Maths.sol"; +import { Loans } from './libraries/internal/Loans.sol'; +import { Deposits } from './libraries/internal/Deposits.sol'; +import { Maths } from './libraries/internal/Maths.sol'; -import { BorrowerActions } from "./libraries/external/BorrowerActions.sol"; -import { LenderActions } from "./libraries/external/LenderActions.sol"; -import { SettlerActions } from "./libraries/external/SettlerActions.sol"; -import { TakerActions } from "./libraries/external/TakerActions.sol"; +import { BorrowerActions } from './libraries/external/BorrowerActions.sol'; +import { LenderActions } from './libraries/external/LenderActions.sol'; +import { SettlerActions } from './libraries/external/SettlerActions.sol'; +import { TakerActions } from './libraries/external/TakerActions.sol'; /** * @title ERC20 Pool contract @@ -77,13 +83,15 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /****************************/ /// @inheritdoc IERC20Pool - function initialize(uint256 rate_) external override { + function initialize( + uint256 rate_ + ) external override { if (isPoolInitialized) revert AlreadyInitialized(); - inflatorState.inflator = uint208(1e18); + inflatorState.inflator = uint208(1e18); inflatorState.inflatorUpdate = uint48(block.timestamp); - interestState.interestRate = uint208(rate_); + interestState.interestRate = uint208(rate_); interestState.interestRateUpdate = uint48(block.timestamp); Loans.init(loans); @@ -128,7 +136,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { PoolState memory poolState = _accruePoolInterest(); // ensure the borrower is not charged for additional debt that they did not receive - amountToBorrow_ = _roundToScale(amountToBorrow_, poolState.quoteTokenScale); + amountToBorrow_ = _roundToScale(amountToBorrow_, poolState.quoteTokenScale); // ensure the borrower is not credited with a fractional amount of collateral smaller than the token scale collateralToPledge_ = _roundToScale(collateralToPledge_, _getArgUint256(COLLATERAL_SCALE)); @@ -147,8 +155,8 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { emit DrawDebt(borrowerAddress_, amountToBorrow_, collateralToPledge_, result.newLup); // update in memory pool state struct - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; + poolState.debt = result.poolDebt; + poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; // adjust t0Debt2ToCollateral ratio @@ -200,7 +208,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { // ensure accounting is performed using the appropriate token scale if (maxQuoteTokenAmountToRepay_ != type(uint256).max) maxQuoteTokenAmountToRepay_ = _roundToScale(maxQuoteTokenAmountToRepay_, poolState.quoteTokenScale); - collateralAmountToPull_ = _roundToScale(collateralAmountToPull_, _getArgUint256(COLLATERAL_SCALE)); + collateralAmountToPull_ = _roundToScale(collateralAmountToPull_, _getArgUint256(COLLATERAL_SCALE)); RepayDebtResult memory result = BorrowerActions.repayDebt( auctions, @@ -216,8 +224,8 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { emit RepayDebt(borrowerAddress_, result.quoteTokenToRepay, collateralAmountToPull_, result.newLup); // update in memory pool state struct - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; + poolState.debt = result.poolDebt; + poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; // adjust t0Debt2ToCollateral ratio @@ -272,7 +280,12 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { if (amountToAdd_ != 0 && amountToAdd_ < _bucketCollateralDust(index_)) revert DustAmountNotExceeded(); amountToAdd_ = _roundToScale(amountToAdd_, _getArgUint256(COLLATERAL_SCALE)); - bucketLP_ = LenderActions.addCollateral(buckets, deposits, amountToAdd_, index_); + bucketLP_ = LenderActions.addCollateral( + buckets, + deposits, + amountToAdd_, + index_ + ); emit AddCollateral(msg.sender, index_, amountToAdd_, bucketLP_); @@ -343,7 +356,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { reserveAuction, poolState, SettleParams({ - borrower: borrowerAddress_, + borrower: borrowerAddress_, poolBalance: _getNormalizedPoolQuoteTokenBalance(), bucketDepth: maxDepth_ }) @@ -364,9 +377,9 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function take( - address borrowerAddress_, - uint256 maxAmount_, - address callee_, + address borrowerAddress_, + uint256 maxAmount_, + address callee_, bytes calldata data_ ) external override nonReentrant returns (uint256 collateralTaken_) { PoolState memory poolState = _accruePoolInterest(); @@ -414,7 +427,12 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @dev - decrement `poolBalances.pledgedCollateral` accumulator * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ - function bucketTake(address borrowerAddress_, bool depositTake_, uint256 index_) external override nonReentrant { + function bucketTake( + address borrowerAddress_, + bool depositTake_, + uint256 index_ + ) external override nonReentrant { + PoolState memory poolState = _accruePoolInterest(); TakeResult memory result = TakerActions.bucketTake( @@ -440,7 +458,9 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @inheritdoc FlashloanablePool * @dev Override default implementation and allows flashloans for both quote and collateral token. */ - function _isFlashloanSupported(address token_) internal view virtual override returns (bool) { + function _isFlashloanSupported( + address token_ + ) internal virtual view override returns (bool) { return token_ == _getArgAddress(QUOTE_ADDRESS) || token_ == _getArgAddress(COLLATERAL_ADDRESS); } @@ -478,5 +498,5 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { uint256 pricePrecisionAdjustment = _getCollateralDustPricePrecisionAdjustment(bucketIndex_); // difference between the normalized scale and the collateral token's scale return Maths.max(_getArgUint256(COLLATERAL_SCALE), 10 ** pricePrecisionAdjustment); - } + } } diff --git a/packages/ajna-contracts/contracts/ajna/ERC20PoolFactory.sol b/packages/ajna-contracts/contracts/ajna/ERC20PoolFactory.sol index 968376c2d..7ece99e25 100644 --- a/packages/ajna-contracts/contracts/ajna/ERC20PoolFactory.sol +++ b/packages/ajna-contracts/contracts/ajna/ERC20PoolFactory.sol @@ -4,12 +4,12 @@ pragma solidity 0.8.18; import { ClonesWithImmutableArgs } from "./libs/clones-with-immutable-args/src/ClonesWithImmutableArgs.sol"; -import { IERC20PoolFactory } from "./interfaces/pool/erc20/IERC20PoolFactory.sol"; -import { IPoolFactory } from "./interfaces/pool/IPoolFactory.sol"; -import { PoolType } from "./interfaces/pool/IPool.sol"; +import { IERC20PoolFactory } from './interfaces/pool/erc20/IERC20PoolFactory.sol'; +import { IPoolFactory } from './interfaces/pool/IPoolFactory.sol'; +import { PoolType } from './interfaces/pool/IPool.sol'; -import { ERC20Pool } from "./ERC20Pool.sol"; -import { PoolDeployer } from "./base/PoolDeployer.sol"; +import { ERC20Pool } from './ERC20Pool.sol'; +import { PoolDeployer } from './base/PoolDeployer.sol'; /** * @title ERC20 Pool Factory @@ -18,6 +18,7 @@ import { PoolDeployer } from "./base/PoolDeployer.sol"; * @dev Reverts if pool is already created or if params to deploy new pool are invalid. */ contract ERC20PoolFactory is PoolDeployer, IERC20PoolFactory { + using ClonesWithImmutableArgs for address; /// @dev `ERC20` clonable pool contract used to deploy the new pool. @@ -49,9 +50,7 @@ contract ERC20PoolFactory is PoolDeployer, IERC20PoolFactory { * @dev - `PoolCreated` */ function deployPool( - address collateral_, - address quote_, - uint256 interestRate_ + address collateral_, address quote_, uint256 interestRate_ ) external canDeploy(collateral_, quote_, interestRate_) returns (address pool_) { address existingPool = deployedPools[ERC20_NON_SUBSET_HASH][collateral_][quote_]; if (existingPool != address(0)) revert IPoolFactory.PoolAlreadyExists(existingPool); diff --git a/packages/ajna-contracts/contracts/ajna/ERC721Pool.sol b/packages/ajna-contracts/contracts/ajna/ERC721Pool.sol index 11ec7bdf4..003d5ee80 100644 --- a/packages/ajna-contracts/contracts/ajna/ERC721Pool.sol +++ b/packages/ajna-contracts/contracts/ajna/ERC721Pool.sol @@ -9,38 +9,41 @@ import { IPoolKickerActions, IPoolTakerActions, IPoolSettlerActions -} from "./interfaces/pool/IPool.sol"; +} from './interfaces/pool/IPool.sol'; import { DrawDebtResult, RepayDebtResult, SettleParams, SettleResult, TakeResult -} from "./interfaces/pool/commons/IPoolInternals.sol"; -import { PoolState } from "./interfaces/pool/commons/IPoolState.sol"; +} from './interfaces/pool/commons/IPoolInternals.sol'; +import { PoolState } from './interfaces/pool/commons/IPoolState.sol'; import { IERC721Pool, IERC721PoolBorrowerActions, IERC721PoolImmutables, IERC721PoolLenderActions -} from "./interfaces/pool/erc721/IERC721Pool.sol"; -import { IERC721Taker } from "./interfaces/pool/erc721/IERC721Taker.sol"; -import { IERC721PoolState } from "./interfaces/pool/erc721/IERC721PoolState.sol"; +} from './interfaces/pool/erc721/IERC721Pool.sol'; +import { IERC721Taker } from './interfaces/pool/erc721/IERC721Taker.sol'; +import { IERC721PoolState } from './interfaces/pool/erc721/IERC721PoolState.sol'; -import { FlashloanablePool } from "./base/FlashloanablePool.sol"; -import { _roundToScale } from "./libraries/helpers/PoolHelper.sol"; +import { FlashloanablePool } from './base/FlashloanablePool.sol'; +import { _roundToScale } from './libraries/helpers/PoolHelper.sol'; -import { _revertIfAuctionClearable, _revertAfterExpiry } from "./libraries/helpers/RevertsHelper.sol"; +import { + _revertIfAuctionClearable, + _revertAfterExpiry +} from './libraries/helpers/RevertsHelper.sol'; -import { Maths } from "./libraries/internal/Maths.sol"; -import { Deposits } from "./libraries/internal/Deposits.sol"; -import { Loans } from "./libraries/internal/Loans.sol"; +import { Maths } from './libraries/internal/Maths.sol'; +import { Deposits } from './libraries/internal/Deposits.sol'; +import { Loans } from './libraries/internal/Loans.sol'; -import { LenderActions } from "./libraries/external/LenderActions.sol"; -import { BorrowerActions } from "./libraries/external/BorrowerActions.sol"; -import { SettlerActions } from "./libraries/external/SettlerActions.sol"; -import { TakerActions } from "./libraries/external/TakerActions.sol"; +import { LenderActions } from './libraries/external/LenderActions.sol'; +import { BorrowerActions } from './libraries/external/BorrowerActions.sol'; +import { SettlerActions } from './libraries/external/SettlerActions.sol'; +import { TakerActions } from './libraries/external/TakerActions.sol'; /** * @title ERC721 Pool contract @@ -57,6 +60,7 @@ import { TakerActions } from "./libraries/external/TakerActions.sol"; * @dev Calls logic from external `PoolCommons`, `LenderActions`, `BorrowerActions` and `Auction` actions libraries. */ contract ERC721Pool is FlashloanablePool, IERC721Pool { + /*****************/ /*** Constants ***/ /*****************/ @@ -71,35 +75,36 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /// @dev Borrower `address => array` of tokenIds pledged by borrower mapping. mapping(address => uint256[]) public borrowerTokenIds; /// @dev Array of `tokenIds` in pool buckets (claimable from pool). - uint256[] public bucketTokenIds; + uint256[] public bucketTokenIds; /// @dev Mapping of `tokenIds` allowed in `NFT` Subset type pool. - mapping(uint256 => bool) internal tokenIdsAllowed_; + mapping(uint256 => bool) internal tokenIdsAllowed_; /****************************/ /*** Initialize Functions ***/ /****************************/ /// @inheritdoc IERC721Pool - function initialize(uint256[] memory tokenIds_, uint256 rate_) external override { + function initialize( + uint256[] memory tokenIds_, + uint256 rate_ + ) external override { if (isPoolInitialized) revert AlreadyInitialized(); - inflatorState.inflator = uint208(1e18); + inflatorState.inflator = uint208(1e18); inflatorState.inflatorUpdate = uint48(block.timestamp); - interestState.interestRate = uint208(rate_); + interestState.interestRate = uint208(rate_); interestState.interestRateUpdate = uint48(block.timestamp); uint256 noOfTokens = tokenIds_.length; if (noOfTokens != 0) { // add subset of tokenIds allowed in the pool - for (uint256 id = 0; id < noOfTokens; ) { + for (uint256 id = 0; id < noOfTokens;) { tokenIdsAllowed_[tokenIds_[id]] = true; - unchecked { - ++id; - } + unchecked { ++id; } } } @@ -163,8 +168,8 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { emit DrawDebtNFT(borrowerAddress_, amountToBorrow_, tokenIdsToPledge_, result.newLup); // update in memory pool state struct - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; + poolState.debt = result.poolDebt; + poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; // adjust t0Debt2ToCollateral ratio @@ -234,8 +239,8 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { emit RepayDebt(borrowerAddress_, result.quoteTokenToRepay, noOfNFTsToPull_, result.newLup); // update in memory pool state struct - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; + poolState.debt = result.poolDebt; + poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; // adjust t0Debt2ToCollateral ratio @@ -286,7 +291,12 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { _revertAfterExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); - bucketLP_ = LenderActions.addCollateral(buckets, deposits, Maths.wad(tokenIds_.length), index_); + bucketLP_ = LenderActions.addCollateral( + buckets, + deposits, + Maths.wad(tokenIds_.length), + index_ + ); emit AddCollateralNFT(msg.sender, index_, tokenIds_, bucketLP_); @@ -314,7 +324,10 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { PoolState memory poolState = _accruePoolInterest(); uint256 collateralAmount = Maths.wad(noOfNFTsToRemove_); - (collateralMerged_, bucketLP_) = LenderActions.mergeOrRemoveCollateral( + ( + collateralMerged_, + bucketLP_ + ) = LenderActions.mergeOrRemoveCollateral( buckets, deposits, removalIndexes_, @@ -350,7 +363,12 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { PoolState memory poolState = _accruePoolInterest(); removedAmount_ = Maths.wad(noOfNFTsToRemove_); - redeemedLP_ = LenderActions.removeCollateral(buckets, deposits, removedAmount_, index_); + redeemedLP_ = LenderActions.removeCollateral( + buckets, + deposits, + removedAmount_, + index_ + ); emit RemoveCollateral(msg.sender, index_, noOfNFTsToRemove_, redeemedLP_); @@ -375,11 +393,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { function settle( address borrowerAddress_, uint256 maxDepth_ - ) external override nonReentrant returns (uint256 collateralSettled_, bool isBorrowerSettled_) { + ) external nonReentrant override returns (uint256 collateralSettled_, bool isBorrowerSettled_) { PoolState memory poolState = _accruePoolInterest(); SettleParams memory params = SettleParams({ - borrower: borrowerAddress_, + borrower: borrowerAddress_, poolBalance: _getNormalizedPoolQuoteTokenBalance(), bucketDepth: maxDepth_ }); @@ -412,9 +430,9 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function take( - address borrowerAddress_, - uint256 collateral_, - address callee_, + address borrowerAddress_, + uint256 collateral_, + address callee_, bytes calldata data_ ) external override nonReentrant returns (uint256 collateralTaken_) { PoolState memory poolState = _accruePoolInterest(); @@ -444,7 +462,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { if (data_.length != 0) { IERC721Taker(callee_).atomicSwapCallback( tokensTaken, - totalQuoteTokenAmount / poolState.quoteTokenScale, + totalQuoteTokenAmount / poolState.quoteTokenScale, data_ ); } @@ -469,7 +487,12 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @dev - decrement `poolBalances.pledgedCollateral` accumulator * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ - function bucketTake(address borrowerAddress_, bool depositTake_, uint256 index_) external override nonReentrant { + function bucketTake( + address borrowerAddress_, + bool depositTake_, + uint256 index_ + ) external override nonReentrant { + PoolState memory poolState = _accruePoolInterest(); TakeResult memory result = TakerActions.bucketTake( @@ -501,11 +524,14 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @param borrowerAddress_ Address of borrower. * @param borrowerCollateral_ Current borrower collateral to be rebalanced. */ - function _rebalanceTokens(address borrowerAddress_, uint256 borrowerCollateral_) internal { + function _rebalanceTokens( + address borrowerAddress_, + uint256 borrowerCollateral_ + ) internal { // rebalance borrower's collateral, transfer difference to floor collateral from borrower to pool claimable array uint256[] storage borrowerTokens = borrowerTokenIds[borrowerAddress_]; - uint256 noOfTokensPledged = borrowerTokens.length; + uint256 noOfTokensPledged = borrowerTokens.length; /* eg1. borrowerCollateral_ = 4.1, noOfTokensPledged = 6; noOfTokensToTransfer = 1 eg2. borrowerCollateral_ = 4, noOfTokensPledged = 6; noOfTokensToTransfer = 2 @@ -513,14 +539,12 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { uint256 borrowerCollateralRoundedUp = (borrowerCollateral_ + 1e18 - 1) / 1e18; uint256 noOfTokensToTransfer = noOfTokensPledged - borrowerCollateralRoundedUp; - for (uint256 i = 0; i < noOfTokensToTransfer; ) { + for (uint256 i = 0; i < noOfTokensToTransfer;) { uint256 tokenId = borrowerTokens[--noOfTokensPledged]; // start with moving the last token pledged by borrower - borrowerTokens.pop(); // remove token id from borrower - bucketTokenIds.push(tokenId); // add token id to pool claimable tokens + borrowerTokens.pop(); // remove token id from borrower + bucketTokenIds.push(tokenId); // add token id to pool claimable tokens - unchecked { - ++i; - } + unchecked { ++i; } } } @@ -530,17 +554,18 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @param poolTokens_ Array in pool that tracks `NFT` ids (could be tracking `NFT`s pledged by borrower or `NFT`s added by a lender in a specific bucket). * @param tokenIds_ Array of `NFT` token ids to transfer from `msg.sender` to pool. */ - function _transferFromSenderToPool(uint256[] storage poolTokens_, uint256[] calldata tokenIds_) internal { - for (uint256 i = 0; i < tokenIds_.length; ) { + function _transferFromSenderToPool( + uint256[] storage poolTokens_, + uint256[] calldata tokenIds_ + ) internal { + for (uint256 i = 0; i < tokenIds_.length;) { uint256 tokenId = tokenIds_[i]; if (!tokenIdsAllowed(tokenId)) revert OnlySubset(); poolTokens_.push(tokenId); _transferNFT(msg.sender, address(this), tokenId); - unchecked { - ++i; - } + unchecked { ++i; } } } @@ -561,7 +586,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { uint256 noOfNFTsInPool = poolTokens_.length; - for (uint256 i = 0; i < amountToRemove_; ) { + for (uint256 i = 0; i < amountToRemove_;) { uint256 tokenId = poolTokens_[--noOfNFTsInPool]; // start with transferring the last token added in bucket poolTokens_.pop(); @@ -569,9 +594,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { tokensTransferred[i] = tokenId; - unchecked { - ++i; - } + unchecked { ++i; } } return tokensTransferred; @@ -594,12 +617,13 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /*******************************/ /// @inheritdoc IERC721PoolState - function totalBorrowerTokens(address borrower_) external view override returns (uint256) { + function totalBorrowerTokens(address borrower_) external view override returns(uint256) { return borrowerTokenIds[borrower_].length; } /// @inheritdoc IERC721PoolState - function totalBucketTokens() external view override returns (uint256) { + function totalBucketTokens() external view override returns(uint256) { return bucketTokenIds.length; } + } diff --git a/packages/ajna-contracts/contracts/ajna/ERC721PoolFactory.sol b/packages/ajna-contracts/contracts/ajna/ERC721PoolFactory.sol index cfccb1b06..86b29593a 100644 --- a/packages/ajna-contracts/contracts/ajna/ERC721PoolFactory.sol +++ b/packages/ajna-contracts/contracts/ajna/ERC721PoolFactory.sol @@ -5,12 +5,12 @@ pragma solidity 0.8.18; import { ClonesWithImmutableArgs } from "./libs/clones-with-immutable-args/src/ClonesWithImmutableArgs.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { IERC721PoolFactory } from "./interfaces/pool/erc721/IERC721PoolFactory.sol"; -import { IPoolFactory } from "./interfaces/pool/IPoolFactory.sol"; -import { PoolType } from "./interfaces/pool/IPool.sol"; +import { IERC721PoolFactory } from './interfaces/pool/erc721/IERC721PoolFactory.sol'; +import { IPoolFactory } from './interfaces/pool/IPoolFactory.sol'; +import { PoolType } from './interfaces/pool/IPool.sol'; -import { ERC721Pool } from "./ERC721Pool.sol"; -import { PoolDeployer } from "./base/PoolDeployer.sol"; +import { ERC721Pool } from './ERC721Pool.sol'; +import { PoolDeployer } from './base/PoolDeployer.sol'; /** * @title ERC721 Pool Factory @@ -20,6 +20,7 @@ import { PoolDeployer } from "./base/PoolDeployer.sol"; * @dev Reverts if pool is already created or if params to deploy new pool are invalid. */ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { + using ClonesWithImmutableArgs for address; /// @dev `ERC721` clonable pool contract used to deploy the new pool. @@ -52,10 +53,7 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { * @dev - `PoolCreated` */ function deployPool( - address collateral_, - address quote_, - uint256[] memory tokenIds_, - uint256 interestRate_ + address collateral_, address quote_, uint256[] memory tokenIds_, uint256 interestRate_ ) external canDeploy(collateral_, quote_, interestRate_) returns (address pool_) { bytes32 subsetHash = getNFTSubsetHash(tokenIds_); @@ -137,4 +135,5 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { } } } + } diff --git a/packages/ajna-contracts/contracts/ajna/PoolInfoUtils.sol b/packages/ajna-contracts/contracts/ajna/PoolInfoUtils.sol index 4d901a4c0..a2595afec 100644 --- a/packages/ajna-contracts/contracts/ajna/PoolInfoUtils.sol +++ b/packages/ajna-contracts/contracts/ajna/PoolInfoUtils.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.18; -import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; -import { IPool, IERC20Token } from "./interfaces/pool/IPool.sol"; +import { IPool, IERC20Token } from './interfaces/pool/IPool.sol'; import { _auctionPrice, @@ -19,13 +19,14 @@ import { _priceAt, _reserveAuctionPrice, MAX_FENWICK_INDEX, - MIN_PRICE -} from "./libraries/helpers/PoolHelper.sol"; + MIN_PRICE, + COLLATERALIZATION_FACTOR +} from './libraries/helpers/PoolHelper.sol'; -import { Buckets } from "./libraries/internal/Buckets.sol"; -import { Maths } from "./libraries/internal/Maths.sol"; +import { Buckets } from './libraries/internal/Buckets.sol'; +import { Maths } from './libraries/internal/Maths.sol'; -import { PoolCommons } from "./libraries/external/PoolCommons.sol"; +import { PoolCommons } from './libraries/external/PoolCommons.sol'; /** * @title Pool Info Utils contract @@ -33,6 +34,7 @@ import { PoolCommons } from "./libraries/external/PoolCommons.sol"; * @dev Pool info is calculated using same helper functions / logic as in `Pool` contracts. */ contract PoolInfoUtils { + /** * @notice Exposes status of a liquidation auction. * @param ajnaPool_ Address of `Ajna` pool. @@ -44,30 +46,27 @@ contract PoolInfoUtils { * @return price_ Current price of the auction. (`WAD`) * @return neutralPrice_ Price at which bond holder is neither rewarded nor penalized. (`WAD`) */ - function auctionStatus( - address ajnaPool_, - address borrower_ - ) + function auctionStatus(address ajnaPool_, address borrower_) external view returns ( uint256 kickTime_, uint256 collateral_, uint256 debtToCover_, - bool isCollateralized_, + bool isCollateralized_, uint256 price_, uint256 neutralPrice_ ) { IPool pool = IPool(ajnaPool_); uint256 referencePrice; - (, , , kickTime_, referencePrice, neutralPrice_, , , ) = pool.auctionInfo(borrower_); + ( , , , kickTime_, referencePrice, neutralPrice_, , , ) = pool.auctionInfo(borrower_); if (kickTime_ != 0) { (debtToCover_, collateral_, ) = this.borrowerInfo(ajnaPool_, borrower_); - - (uint256 poolDebt, , , ) = pool.debtInfo(); - uint256 lup_ = _priceAt(pool.depositIndex(poolDebt)); - isCollateralized_ = _isCollateralized(debtToCover_, collateral_, lup_, pool.poolType()); + + (uint256 poolDebt,,,) = pool.debtInfo(); + uint256 lup_ = _priceAt(pool.depositIndex(poolDebt)); + isCollateralized_ = _isCollateralized(debtToCover_, collateral_, lup_, pool.poolType()); price_ = _auctionPrice(referencePrice, kickTime_); } @@ -81,21 +80,29 @@ contract PoolInfoUtils { * @return collateral_ Pledged collateral, including encumbered (`WAD`). * @return t0Np_ `Neutral price` (`WAD`). */ - function borrowerInfo( - address ajnaPool_, - address borrower_ - ) external view returns (uint256 debt_, uint256 collateral_, uint256 t0Np_) { + function borrowerInfo(address ajnaPool_, address borrower_) + external + view + returns ( + uint256 debt_, + uint256 collateral_, + uint256 t0Np_ + ) + { IPool pool = IPool(ajnaPool_); - (uint256 inflator, uint256 lastInflatorUpdate) = pool.inflatorInfo(); + ( + uint256 inflator, + uint256 lastInflatorUpdate + ) = pool.inflatorInfo(); - (uint256 interestRate, ) = pool.interestRateInfo(); + (uint256 interestRate,) = pool.interestRateInfo(); uint256 pendingInflator = PoolCommons.pendingInflator(inflator, lastInflatorUpdate, interestRate); uint256 t0Debt; uint256 npTpRatio; - (t0Debt, collateral_, npTpRatio) = pool.borrowerInfo(borrower_); + (t0Debt, collateral_, npTpRatio) = pool.borrowerInfo(borrower_); t0Np_ = collateral_ == 0 ? 0 : Math.mulDiv(t0Debt, npTpRatio, collateral_); @@ -113,10 +120,7 @@ contract PoolInfoUtils { * @return scale_ Lender interest multiplier (`WAD`). * @return exchangeRate_ The exchange rate of the bucket, in `WAD` units. */ - function bucketInfo( - address ajnaPool_, - uint256 index_ - ) + function bucketInfo(address ajnaPool_, uint256 index_) external view returns ( @@ -145,9 +149,7 @@ contract PoolInfoUtils { * @return pendingInflator_ Pending inflator in pool. * @return pendingInterestFactor_ Factor used to scale the inflator. */ - function poolLoansInfo( - address ajnaPool_ - ) + function poolLoansInfo(address ajnaPool_) external view returns ( @@ -163,11 +165,14 @@ contract PoolInfoUtils { poolSize_ = pool.depositSize(); (maxBorrower_, , loansCount_) = pool.loansInfo(); - (uint256 inflator, uint256 inflatorUpdate) = pool.inflatorInfo(); + ( + uint256 inflator, + uint256 inflatorUpdate + ) = pool.inflatorInfo(); (uint256 interestRate, ) = pool.interestRateInfo(); - pendingInflator_ = PoolCommons.pendingInflator(inflator, inflatorUpdate, interestRate); + pendingInflator_ = PoolCommons.pendingInflator(inflator, inflatorUpdate, interestRate); pendingInterestFactor_ = PoolCommons.pendingInterestFactor(interestRate, block.timestamp - inflatorUpdate); } @@ -181,26 +186,31 @@ contract PoolInfoUtils { * @return lup_ The price value of the current `Lowest Utilized Price` (LUP) bucket, in `WAD` units. * @return lupIndex_ The index of the current `Lowest Utilized Price` (`LUP`) bucket, in `WAD` units. */ - function poolPricesInfo( - address ajnaPool_ - ) + function poolPricesInfo(address ajnaPool_) external view - returns (uint256 hpb_, uint256 hpbIndex_, uint256 htp_, uint256 htpIndex_, uint256 lup_, uint256 lupIndex_) + returns ( + uint256 hpb_, + uint256 hpbIndex_, + uint256 htp_, + uint256 htpIndex_, + uint256 lup_, + uint256 lupIndex_ + ) { IPool pool = IPool(ajnaPool_); - (uint256 debt, , , ) = pool.debtInfo(); + (uint256 debt,,,) = pool.debtInfo(); hpbIndex_ = pool.depositIndex(1); - hpb_ = _priceAt(hpbIndex_); + hpb_ = _priceAt(hpbIndex_); - (, uint256 maxThresholdPrice, ) = pool.loansInfo(); + (, uint256 maxThresholdPrice,) = pool.loansInfo(); - htp_ = maxThresholdPrice; + htp_ = maxThresholdPrice; htpIndex_ = htp_ >= MIN_PRICE ? _indexOf(htp_) : MAX_FENWICK_INDEX; lupIndex_ = pool.depositIndex(debt); - lup_ = _priceAt(lupIndex_); + lup_ = _priceAt(lupIndex_); } /** @@ -211,7 +221,11 @@ contract PoolInfoUtils { */ function availableQuoteTokenAmount(address ajnaPool_) external view returns (uint256 amount_) { IPool pool = IPool(ajnaPool_); - (uint256 bondEscrowed, uint256 unclaimedReserve, , ) = pool.reservesInfo(); + ( + uint256 bondEscrowed, + uint256 unclaimedReserve, + , + ) = pool.reservesInfo(); uint256 escrowedAmounts = bondEscrowed + unclaimedReserve; uint256 poolBalance = IERC20Token(pool.quoteTokenAddress()).balanceOf(ajnaPool_) * pool.quoteTokenScale(); @@ -228,9 +242,7 @@ contract PoolInfoUtils { * @return auctionPrice_ Current price at which `1` quote token may be purchased, denominated in `Ajna`. * @return timeRemaining_ Seconds remaining before takes are no longer allowed. */ - function poolReservesInfo( - address ajnaPool_ - ) + function poolReservesInfo(address ajnaPool_) external view returns ( @@ -243,8 +255,8 @@ contract PoolInfoUtils { { IPool pool = IPool(ajnaPool_); - (, uint256 poolDebt, , ) = pool.debtInfo(); - uint256 poolSize = pool.depositSize(); + (,uint256 poolDebt,,) = pool.debtInfo(); + uint256 poolSize = pool.depositSize(); uint256 quoteTokenBalance = IERC20Token(pool.quoteTokenAddress()).balanceOf(ajnaPool_) * pool.quoteTokenScale(); @@ -255,11 +267,17 @@ contract PoolInfoUtils { reserves_ = poolDebt + quoteTokenBalance - poolSize - bondEscrowed - unclaimedReserve; } - claimableReserves_ = _claimableReserves(poolDebt, poolSize, bondEscrowed, unclaimedReserve, quoteTokenBalance); + claimableReserves_ = _claimableReserves( + poolDebt, + poolSize, + bondEscrowed, + unclaimedReserve, + quoteTokenBalance + ); claimableReservesRemaining_ = unclaimedReserve; - auctionPrice_ = _reserveAuctionPrice(auctionKickTime); - timeRemaining_ = 3 days - Maths.min(3 days, block.timestamp - auctionKickTime); + auctionPrice_ = _reserveAuctionPrice(auctionKickTime); + timeRemaining_ = 3 days - Maths.min(3 days, block.timestamp - auctionKickTime); } /** @@ -270,9 +288,7 @@ contract PoolInfoUtils { * @return poolActualUtilization_ The current pool actual utilization, in `WAD` units. * @return poolTargetUtilization_ The current pool Target utilization, in `WAD` units. */ - function poolUtilizationInfo( - address ajnaPool_ - ) + function poolUtilizationInfo(address ajnaPool_) external view returns ( @@ -284,8 +300,8 @@ contract PoolInfoUtils { { IPool pool = IPool(ajnaPool_); - (uint256 poolDebt, , , ) = pool.debtInfo(); - uint256 poolCollateral = pool.pledgedCollateral(); + (uint256 poolDebt,,,) = pool.debtInfo(); + uint256 poolCollateral = pool.pledgedCollateral(); (, , uint256 noOfLoans) = pool.loansInfo(); if (poolDebt != 0) poolMinDebtAmount_ = _minDebtAmount(poolDebt, noOfLoans); @@ -304,36 +320,48 @@ contract PoolInfoUtils { * the remainder accumulates in reserves. * @param ajnaPool_ Address of `Ajna` pool. * @return lenderInterestMargin_ Lender interest margin in pool. - */ - function lenderInterestMargin(address ajnaPool_) external view returns (uint256 lenderInterestMargin_) { + */ + function lenderInterestMargin(address ajnaPool_) + external + view + returns (uint256 lenderInterestMargin_) + { IPool pool = IPool(ajnaPool_); - uint256 utilization = pool.depositUtilization(); + uint256 utilization = pool.depositUtilization(); lenderInterestMargin_ = PoolCommons.lenderInterestMargin(utilization); } /** * @notice Returns bucket price for a given bucket index. - */ - function indexToPrice(uint256 index_) external pure returns (uint256) { + */ + function indexToPrice( + uint256 index_ + ) external pure returns (uint256) + { return _priceAt(index_); } /** * @notice Returns bucket index for a given bucket price. - */ - function priceToIndex(uint256 price_) external pure returns (uint256) { + */ + function priceToIndex( + uint256 price_ + ) external pure returns (uint256) + { return _indexOf(price_); } /** * @notice Returns current `LUP` for a given pool. - */ - function lup(address ajnaPool_) external view returns (uint256) { + */ + function lup( + address ajnaPool_ + ) external view returns (uint256) { IPool pool = IPool(ajnaPool_); - (uint256 debt, , , ) = pool.debtInfo(); + (uint256 debt,,,) = pool.debtInfo(); uint256 currentLupIndex = pool.depositIndex(debt); return _priceAt(currentLupIndex); @@ -341,19 +369,23 @@ contract PoolInfoUtils { /** * @notice Returns current `LUP` index for a given pool. - */ - function lupIndex(address ajnaPool_) external view returns (uint256) { + */ + function lupIndex( + address ajnaPool_ + ) external view returns (uint256) { IPool pool = IPool(ajnaPool_); - (uint256 debt, , , ) = pool.debtInfo(); + (uint256 debt,,,) = pool.debtInfo(); return pool.depositIndex(debt); } /** * @notice Returns current `HPB` for a given pool. - */ - function hpb(address ajnaPool_) external view returns (uint256) { + */ + function hpb( + address ajnaPool_ + ) external view returns (uint256) { IPool pool = IPool(ajnaPool_); uint256 hbpIndex = pool.depositIndex(1); @@ -363,8 +395,10 @@ contract PoolInfoUtils { /** * @notice Returns current `HPB` index for a given pool. - */ - function hpbIndex(address ajnaPool_) external view returns (uint256) { + */ + function hpbIndex( + address ajnaPool_ + ) external view returns (uint256) { IPool pool = IPool(ajnaPool_); return pool.depositIndex(1); @@ -372,8 +406,10 @@ contract PoolInfoUtils { /** * @notice Returns current `HTP` for a given pool. - */ - function htp(address ajnaPool_) external view returns (uint256 htp_) { + */ + function htp( + address ajnaPool_ + ) external view returns (uint256 htp_) { (, htp_, ) = IPool(ajnaPool_).loansInfo(); } @@ -382,8 +418,10 @@ contract PoolInfoUtils { * @notice Calculated as greater of the current annualized interest rate divided by `52` (one week of interest) or `5` bps. * @return Fee rate calculated from the pool interest rate. */ - function borrowFeeRate(address ajnaPool_) external view returns (uint256) { - (uint256 interestRate, ) = IPool(ajnaPool_).interestRateInfo(); + function borrowFeeRate( + address ajnaPool_ + ) external view returns (uint256) { + (uint256 interestRate,) = IPool(ajnaPool_).interestRateInfo(); return _borrowFeeRate(interestRate); } @@ -392,9 +430,11 @@ contract PoolInfoUtils { * @notice Calculated as current annualized rate divided by `365` (`24` hours of interest). * @return Fee rate calculated from the pool interest rate. */ - function unutilizedDepositFeeRate(address ajnaPool_) external view returns (uint256) { - (uint256 interestRate, ) = IPool(ajnaPool_).interestRateInfo(); - return _depositFeeRate(interestRate); + function unutilizedDepositFeeRate( + address ajnaPool_ + ) external view returns (uint256) { + (uint256 interestRate,) = IPool(ajnaPool_).interestRateInfo(); + return _depositFeeRate(interestRate); } /** @@ -409,8 +449,14 @@ contract PoolInfoUtils { uint256 index_ ) external view returns (uint256 quoteAmount_) { IPool pool = IPool(ajnaPool_); - (uint256 bucketLP_, uint256 bucketCollateral, , uint256 bucketDeposit, ) = pool.bucketInfo(index_); - quoteAmount_ = _lpToQuoteToken(bucketLP_, bucketCollateral, bucketDeposit, lp_, _priceAt(index_)); + (uint256 bucketLP_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); + quoteAmount_ = _lpToQuoteToken( + bucketLP_, + bucketCollateral, + bucketDeposit, + lp_, + _priceAt(index_) + ); } /** @@ -425,47 +471,63 @@ contract PoolInfoUtils { uint256 index_ ) external view returns (uint256 collateralAmount_) { IPool pool = IPool(ajnaPool_); - (uint256 bucketLP_, uint256 bucketCollateral, , uint256 bucketDeposit, ) = pool.bucketInfo(index_); - collateralAmount_ = _lpToCollateral(bucketCollateral, bucketLP_, bucketDeposit, lp_, _priceAt(index_)); + (uint256 bucketLP_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); + collateralAmount_ = _lpToCollateral( + bucketCollateral, + bucketLP_, + bucketDeposit, + lp_, + _priceAt(index_) + ); } } -/**********************/ -/*** Pool Utilities ***/ -/**********************/ + /**********************/ + /*** Pool Utilities ***/ + /**********************/ -/** - * @notice Calculates encumberance for a debt amount at a given price. - * @param debt_ The debt amount to calculate encumberance for. - * @param price_ The price to calculate encumberance at. - * @return encumberance_ Encumberance value. - */ -function _encumberance(uint256 debt_, uint256 price_) pure returns (uint256 encumberance_) { - return price_ != 0 ? Maths.wdiv(Maths.wmul(1.04 * 1e18, debt_), price_) : 0; -} - -/** - * @notice Calculates collateralization for a given debt and collateral amounts, at a given price. - * @param debt_ The debt amount. - * @param collateral_ The collateral amount. - * @param price_ The price to calculate collateralization at. - * @return Collateralization value. `1 WAD` if debt amount is `0`. - */ -function _collateralization(uint256 debt_, uint256 collateral_, uint256 price_) pure returns (uint256) { - // cannot be undercollateralized if there is no debt - if (debt_ == 0) return 1e18; + /** + * @notice Calculates encumberance for a debt amount at a given price. + * @param debt_ The debt amount to calculate encumberance for. + * @param price_ The price to calculate encumberance at. + * @return encumberance_ Encumberance value. + */ + function _encumberance( + uint256 debt_, + uint256 price_ + ) pure returns (uint256 encumberance_) { + return price_ != 0 ? Maths.wdiv(Maths.wmul(COLLATERALIZATION_FACTOR , debt_), price_) : 0; + } - // borrower is undercollateralized when lup at MIN_PRICE - if (price_ == MIN_PRICE) return 0; - return Maths.wdiv(Maths.wmul(collateral_, price_), Maths.wmul(1.04 * 1e18, debt_)); -} + /** + * @notice Calculates collateralization for a given debt and collateral amounts, at a given price. + * @param debt_ The debt amount. + * @param collateral_ The collateral amount. + * @param price_ The price to calculate collateralization at. + * @return Collateralization value. `1 WAD` if debt amount is `0`. + */ + function _collateralization( + uint256 debt_, + uint256 collateral_, + uint256 price_ + ) pure returns (uint256) { + // cannot be undercollateralized if there is no debt + if (debt_ == 0) return 1e18; + + // borrower is undercollateralized when lup at MIN_PRICE + if (price_ == MIN_PRICE) return 0; + return Maths.wdiv(Maths.wmul(collateral_, price_), Maths.wmul(COLLATERALIZATION_FACTOR, debt_)); + } -/** - * @notice Calculates target utilization for given `EMA` values. - * @param debtColEma_ The `EMA` of debt squared to collateral. - * @param lupt0DebtEma_ The `EMA` of `LUP * t0 debt`. - * @return Target utilization of the pool. - */ -function _targetUtilization(uint256 debtColEma_, uint256 lupt0DebtEma_) pure returns (uint256) { - return (lupt0DebtEma_ != 0) ? Maths.wdiv(debtColEma_, lupt0DebtEma_) : Maths.WAD; -} + /** + * @notice Calculates target utilization for given `EMA` values. + * @param debtColEma_ The `EMA` of debt squared to collateral. + * @param lupt0DebtEma_ The `EMA` of `LUP * t0 debt`. + * @return Target utilization of the pool. + */ + function _targetUtilization( + uint256 debtColEma_, + uint256 lupt0DebtEma_ + ) pure returns (uint256) { + return (lupt0DebtEma_ != 0) ? Maths.wdiv(debtColEma_, lupt0DebtEma_) : Maths.WAD; + } diff --git a/packages/ajna-contracts/contracts/ajna/PoolInfoUtilsMulticall.sol b/packages/ajna-contracts/contracts/ajna/PoolInfoUtilsMulticall.sol index d32f738f2..6d07f4cee 100644 --- a/packages/ajna-contracts/contracts/ajna/PoolInfoUtilsMulticall.sol +++ b/packages/ajna-contracts/contracts/ajna/PoolInfoUtilsMulticall.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.18; import { PoolInfoUtils } from "./PoolInfoUtils.sol"; contract PoolInfoUtilsMulticall { + PoolInfoUtils public immutable poolInfoUtils; struct PoolPriceInfo { @@ -53,13 +54,10 @@ contract PoolInfoUtilsMulticall { * @return poolUtilizationInfo_ Pool utilization info struct * @return bucketInfo_ Bucket info struct */ - function poolDetailsAndBucketInfo( - address ajnaPool_, - uint256 bucketIndex_ - ) + function poolDetailsAndBucketInfo(address ajnaPool_, uint256 bucketIndex_) external view - returns ( + returns( PoolPriceInfo memory poolPriceInfo_, PoolReservesInfo memory poolReservesInfo_, PoolUtilizationInfo memory poolUtilizationInfo_, @@ -89,7 +87,7 @@ contract PoolInfoUtilsMulticall { poolUtilizationInfo_.poolActualUtilization, poolUtilizationInfo_.poolTargetUtilization ) = poolInfoUtils.poolUtilizationInfo(ajnaPool_); - + ( bucketInfo_.price, bucketInfo_.quoteTokens, @@ -107,12 +105,19 @@ contract PoolInfoUtilsMulticall { * @return borrowFeeRate Borrow fee rate calculated from the pool interest ra * @return depositFeeRate Deposit fee rate calculated from the pool interest rate */ - function poolRatesAndFees( - address ajnaPool_ - ) external view returns (uint256 lenderInterestMargin, uint256 borrowFeeRate, uint256 depositFeeRate) { + function poolRatesAndFees(address ajnaPool_) + external + view + returns + ( + uint256 lenderInterestMargin, + uint256 borrowFeeRate, + uint256 depositFeeRate + ) + { lenderInterestMargin = poolInfoUtils.lenderInterestMargin(ajnaPool_); - borrowFeeRate = poolInfoUtils.borrowFeeRate(ajnaPool_); - depositFeeRate = poolInfoUtils.unutilizedDepositFeeRate(ajnaPool_); + borrowFeeRate = poolInfoUtils.borrowFeeRate(ajnaPool_); + depositFeeRate = poolInfoUtils.unutilizedDepositFeeRate(ajnaPool_); } /** @@ -121,24 +126,21 @@ contract PoolInfoUtilsMulticall { * @param args_ Array of serialized function arguments of all read-only functions to called * @return results_ Array of result of all read-only function calls in bytes */ - function multicall( - string[] calldata functionSignatures_, - string[] calldata args_ - ) external returns (bytes[] memory results_) { + function multicall(string[] calldata functionSignatures_, string[] calldata args_) external returns (bytes[] memory results_) { uint256 currentIndex = 0; results_ = new bytes[](functionSignatures_.length); - for (uint256 i = 0; i < functionSignatures_.length; i++) { + for(uint256 i = 0; i < functionSignatures_.length; i++) { string[] memory parameters = _parseFunctionSignature(functionSignatures_[i]); uint256 noOfParams = parameters.length; bytes memory callData; if (noOfParams == 1) { if (keccak256(bytes(parameters[0])) == keccak256(bytes("uint256"))) { uint256 arg = _stringToUint(args_[currentIndex]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg); + callData = abi.encodeWithSignature(functionSignatures_[i], arg); } if (keccak256(bytes(parameters[0])) == keccak256(bytes("address"))) { address arg = _stringToAddress(args_[currentIndex]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg); + callData = abi.encodeWithSignature(functionSignatures_[i], arg); } } @@ -146,12 +148,12 @@ contract PoolInfoUtilsMulticall { if (keccak256(bytes(parameters[1])) == keccak256(bytes("uint256"))) { address arg1 = _stringToAddress(args_[currentIndex]); uint256 arg2 = _stringToUint(args_[currentIndex + 1]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2); + callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2); } if (keccak256(bytes(parameters[1])) == keccak256(bytes("address"))) { address arg1 = _stringToAddress(args_[currentIndex]); address arg2 = _stringToAddress(args_[currentIndex + 1]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2); + callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2); } } @@ -159,7 +161,7 @@ contract PoolInfoUtilsMulticall { address arg1 = _stringToAddress(args_[currentIndex]); uint256 arg2 = _stringToUint(args_[currentIndex + 1]); uint256 arg3 = _stringToUint(args_[currentIndex + 2]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2, arg3); + callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2, arg3); } currentIndex += noOfParams; @@ -181,11 +183,7 @@ contract PoolInfoUtilsMulticall { trimmedSignature_ = _trimFunctionName(signature_); // Check if the string starts with '(' and ends with ')' - if ( - bytes(trimmedSignature_).length >= 2 && - bytes(trimmedSignature_)[0] == bytes("(")[0] && - bytes(trimmedSignature_)[bytes(trimmedSignature_).length - 1] == bytes(")")[0] - ) { + if (bytes(trimmedSignature_).length >= 2 && bytes(trimmedSignature_)[0] == bytes("(")[0] && bytes(trimmedSignature_)[bytes(trimmedSignature_).length - 1] == bytes(")")[0]) { // Remove the first and last characters trimmedSignature_ = _substring(trimmedSignature_, 1, bytes(trimmedSignature_).length - 2); } @@ -251,11 +249,7 @@ contract PoolInfoUtilsMulticall { } // Extracts a substring from a given string - function _substring( - string memory str_, - uint256 startIndex_, - uint256 endIndex_ - ) internal pure returns (string memory) { + function _substring(string memory str_, uint256 startIndex_, uint256 endIndex_) internal pure returns (string memory) { require(startIndex_ <= endIndex_, "Invalid substring indices"); bytes memory strBytes = bytes(str_); bytes memory result = new bytes(endIndex_ - startIndex_ + 1); @@ -280,27 +274,25 @@ contract PoolInfoUtilsMulticall { // Converts a hexadecimal character to its decimal value function _hexCharToDecimal(uint8 character_) internal pure returns (uint8) { - if (bytes1(character_) >= bytes1("0") && bytes1(character_) <= bytes1("9")) { - return character_ - uint8(bytes1("0")); + if (bytes1(character_) >= bytes1('0') && bytes1(character_) <= bytes1('9')) { + return character_ - uint8(bytes1('0')); } - if (bytes1(character_) >= bytes1("a") && bytes1(character_) <= bytes1("f")) { - return 10 + character_ - uint8(bytes1("a")); + if (bytes1(character_) >= bytes1('a') && bytes1(character_) <= bytes1('f')) { + return 10 + character_ - uint8(bytes1('a')); } - if (bytes1(character_) >= bytes1("A") && bytes1(character_) <= bytes1("F")) { - return 10 + character_ - uint8(bytes1("A")); + if (bytes1(character_) >= bytes1('A') && bytes1(character_) <= bytes1('F')) { + return 10 + character_ - uint8(bytes1('A')); } return 0; } - + // Converts a hexadecimal string to bytes function _hexStringToBytes(string memory str_) internal pure returns (bytes memory bytesString_) { bytes memory strBytes = bytes(str_); require(strBytes.length % 2 == 0); // length must be even bytesString_ = new bytes(strBytes.length / 2); for (uint i = 1; i < strBytes.length / 2; ++i) { - bytesString_[i] = bytes1( - _hexCharToDecimal(uint8(strBytes[2 * i])) * 16 + _hexCharToDecimal(uint8(strBytes[2 * i + 1])) - ); + bytesString_[i] = bytes1(_hexCharToDecimal(uint8(strBytes[2 * i])) * 16 + _hexCharToDecimal(uint8(strBytes[2 * i + 1]))); } } @@ -313,4 +305,4 @@ contract PoolInfoUtilsMulticall { tempAddress_ := div(mload(add(add(strBytes, 0x20), 1)), 0x1000000000000000000000000) } } -} +} \ No newline at end of file diff --git a/packages/ajna-contracts/contracts/ajna/PositionManager.sol b/packages/ajna-contracts/contracts/ajna/PositionManager.sol index 5a49fc905..5e32ec8cc 100644 --- a/packages/ajna-contracts/contracts/ajna/PositionManager.sol +++ b/packages/ajna-contracts/contracts/ajna/PositionManager.sol @@ -2,27 +2,30 @@ pragma solidity 0.8.18; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; -import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import { EnumerableSet } from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; +import { Multicall } from '@openzeppelin/contracts/utils/Multicall.sol'; +import { ReentrancyGuard } from '@openzeppelin/contracts/security/ReentrancyGuard.sol'; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; -import { IPool } from "./interfaces/pool/IPool.sol"; -import { IPositionManager } from "./interfaces/position/IPositionManager.sol"; -import { IPositionManagerOwnerActions } from "./interfaces/position/IPositionManagerOwnerActions.sol"; -import { IPositionManagerDerivedState } from "./interfaces/position/IPositionManagerDerivedState.sol"; +import { IPool } from './interfaces/pool/IPool.sol'; +import { IPositionManager } from './interfaces/position/IPositionManager.sol'; +import { IPositionManagerOwnerActions } from './interfaces/position/IPositionManagerOwnerActions.sol'; +import { IPositionManagerDerivedState } from './interfaces/position/IPositionManagerDerivedState.sol'; -import { ERC20PoolFactory } from "./ERC20PoolFactory.sol"; -import { ERC721PoolFactory } from "./ERC721PoolFactory.sol"; +import { ERC20PoolFactory } from './ERC20PoolFactory.sol'; +import { ERC721PoolFactory } from './ERC721PoolFactory.sol'; -import { PermitERC721 } from "./base/PermitERC721.sol"; +import { PermitERC721 } from './base/PermitERC721.sol'; -import { _lpToQuoteToken, _priceAt } from "./libraries/helpers/PoolHelper.sol"; -import { tokenSymbol } from "./libraries/helpers/SafeTokenNamer.sol"; +import { + _lpToQuoteToken, + _priceAt +} from './libraries/helpers/PoolHelper.sol'; +import { tokenSymbol } from './libraries/helpers/SafeTokenNamer.sol'; -import { PositionNFTSVG } from "./libraries/external/PositionNFTSVG.sol"; +import { PositionNFTSVG } from './libraries/external/PositionNFTSVG.sol'; /** * @title Position Manager Contract @@ -36,7 +39,7 @@ import { PositionNFTSVG } from "./libraries/external/PositionNFTSVG.sol"; */ contract PositionManager is PermitERC721, IPositionManager, Multicall, ReentrancyGuard { using EnumerableSet for EnumerableSet.UintSet; - using SafeERC20 for ERC20; + using SafeERC20 for ERC20; /***********************/ /*** State Variables ***/ @@ -53,7 +56,7 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc /******************/ /// @dev The `ERC20` pools factory contract, used to check if address is an `Ajna` pool. - ERC20PoolFactory private immutable erc20PoolFactory; + ERC20PoolFactory private immutable erc20PoolFactory; /// @dev The `ERC721` pools factory contract, used to check if address is an `Ajna` pool. ERC721PoolFactory private immutable erc721PoolFactory; @@ -63,23 +66,23 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc /// @dev Struct used for `moveLiquidity` function local vars. struct MoveLiquidityLocalVars { - uint256 bucketLP; // [WAD] amount of LP in from bucket + uint256 bucketLP; // [WAD] amount of LP in from bucket uint256 bucketCollateral; // [WAD] amount of collateral in from bucket - uint256 bankruptcyTime; // from bucket bankruptcy time - uint256 bucketDeposit; // [WAD] from bucket deposit - uint256 fromDepositTime; // lender deposit time in from bucket - uint256 fromLP; // [WAD] the LP memorialized in from position - uint256 toDepositTime; // lender deposit time in to bucket - uint256 maxQuote; // [WAD] max amount that can be moved from bucket - uint256 lpbAmountFrom; // [WAD] the LP redeemed from bucket - uint256 lpbAmountTo; // [WAD] the LP awarded in to bucket + uint256 bankruptcyTime; // from bucket bankruptcy time + uint256 bucketDeposit; // [WAD] from bucket deposit + uint256 fromDepositTime; // lender deposit time in from bucket + uint256 fromLP; // [WAD] the LP memorialized in from position + uint256 toDepositTime; // lender deposit time in to bucket + uint256 maxQuote; // [WAD] max amount that can be moved from bucket + uint256 lpbAmountFrom; // [WAD] the LP redeemed from bucket + uint256 lpbAmountTo; // [WAD] the LP awarded in to bucket } /// @dev Struct used for `memorializePositions` function Lenders Local vars struct LendersBucketLocalVars { - uint256 lpBalance; // Lender lp balance in a bucket + uint256 lpBalance; // Lender lp balance in a bucket uint256 depositTime; // Lender deposit time in a bucket - uint256 allowance; // Lp allowance for a bucket + uint256 allowance; // Lp allowance for a bucket } /*****************/ @@ -92,6 +95,7 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc * @param tokenId_ Id of positions `NFT`. */ modifier mayInteract(address pool_, uint256 tokenId_) { + // revert if token id is not a valid / minted id _requireMinted(tokenId_); @@ -112,10 +116,11 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc ERC20PoolFactory erc20Factory_, ERC721PoolFactory erc721Factory_ ) PermitERC721("Ajna Positions NFT-V1", "AJNA-V1-POS", "1") { - if (address(erc20Factory_) == address(0) || address(erc721Factory_) == address(0)) - revert DeployWithZeroAddress(); + if ( + address(erc20Factory_) == address(0) || address(erc721Factory_) == address(0) + ) revert DeployWithZeroAddress(); - erc20PoolFactory = erc20Factory_; + erc20PoolFactory = erc20Factory_; erc721PoolFactory = erc721Factory_; } @@ -137,7 +142,10 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc * @dev === Emit events === * @dev - `Burn` */ - function burn(address pool_, uint256 tokenId_) external override mayInteract(pool_, tokenId_) { + function burn( + address pool_, + uint256 tokenId_ + ) external override mayInteract(pool_, tokenId_) { // revert if trying to burn an positions token that still has liquidity if (positionTokens[tokenId_].positionIndexes.length() != 0) revert LiquidityNotRemoved(); @@ -173,11 +181,11 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc address pool_, uint256 tokenId_, uint256[] calldata indexes_ - ) external override mayInteract(pool_, tokenId_) { + ) external mayInteract(pool_, tokenId_) override { TokenInfo storage tokenInfo = positionTokens[tokenId_]; EnumerableSet.UintSet storage positionIndexes = tokenInfo.positionIndexes; - IPool pool = IPool(pool_); + IPool pool = IPool(pool_); address owner = ownerOf(tokenId_); LendersBucketLocalVars memory vars; @@ -220,9 +228,7 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc // save position in storage tokenInfo.positions[index] = position; - unchecked { - ++i; - } + unchecked { ++i; } } // update pool LP accounting and transfer ownership of LP to PositionManager contract @@ -288,8 +294,8 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc uint256 toIndex_, uint256 expiry_ ) external override nonReentrant mayInteract(pool_, tokenId_) { - TokenInfo storage tokenInfo = positionTokens[tokenId_]; - Position storage fromPosition = tokenInfo.positions[fromIndex_]; + TokenInfo storage tokenInfo = positionTokens[tokenId_]; + Position storage fromPosition = tokenInfo.positions[fromIndex_]; MoveLiquidityLocalVars memory vars; vars.fromDepositTime = fromPosition.depositTime; @@ -301,10 +307,13 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc // ensure bucketDeposit accounts for accrued interest IPool(pool_).updateInterest(); - // retrieve info of bucket from which liquidity is moved - (vars.bucketLP, vars.bucketCollateral, vars.bankruptcyTime, vars.bucketDeposit, ) = IPool(pool_).bucketInfo( - fromIndex_ - ); + // retrieve info of bucket from which liquidity is moved + ( + vars.bucketLP, + vars.bucketCollateral, + vars.bankruptcyTime, + vars.bucketDeposit, + ) = IPool(pool_).bucketInfo(fromIndex_); // check that from bucket hasn't gone bankrupt since memorialization if (vars.fromDepositTime <= vars.bankruptcyTime) revert BucketBankrupt(); @@ -319,7 +328,10 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc ); // move quote tokens in pool - (vars.lpbAmountFrom, vars.lpbAmountTo, ) = IPool(pool_).moveQuoteToken( + ( + vars.lpbAmountFrom, + vars.lpbAmountTo, + ) = IPool(pool_).moveQuoteToken( vars.maxQuote, fromIndex_, toIndex_, @@ -352,7 +364,14 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc (, vars.toDepositTime) = IPool(pool_).lenderInfo(toIndex_, address(this)); toPosition.depositTime = vars.toDepositTime; - emit MoveLiquidity(ownerOf(tokenId_), tokenId_, fromIndex_, toIndex_, vars.lpbAmountFrom, vars.lpbAmountTo); + emit MoveLiquidity( + ownerOf(tokenId_), + tokenId_, + fromIndex_, + toIndex_, + vars.lpbAmountFrom, + vars.lpbAmountTo + ); } /** @@ -407,9 +426,7 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc // remove LP tracked by position manager at bucket index delete tokenInfo.positions[index]; - unchecked { - ++i; - } + unchecked { ++i; } } address owner = ownerOf(tokenId_); @@ -432,11 +449,18 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc * @param subsetHash_ Factory's subset hash pool. * @return `True` if a valid `Ajna` pool, `false` otherwise. */ - function _isAjnaPool(address pool_, bytes32 subsetHash_) internal view returns (bool) { + function _isAjnaPool( + address pool_, + bytes32 subsetHash_ + ) internal view returns (bool) { address collateralAddress = IPool(pool_).collateralAddress(); - address quoteAddress = IPool(pool_).quoteTokenAddress(); + address quoteAddress = IPool(pool_).quoteTokenAddress(); - address erc20DeployedPoolAddress = erc20PoolFactory.deployedPools(subsetHash_, collateralAddress, quoteAddress); + address erc20DeployedPoolAddress = erc20PoolFactory.deployedPools( + subsetHash_, + collateralAddress, + quoteAddress + ); address erc721DeployedPoolAddress = erc721PoolFactory.deployedPools( subsetHash_, collateralAddress, @@ -468,14 +492,19 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc /**********************/ /// @inheritdoc IPositionManagerDerivedState - function getLP(uint256 tokenId_, uint256 index_) external view override returns (uint256) { + function getLP( + uint256 tokenId_, + uint256 index_ + ) external override view returns (uint256) { TokenInfo storage tokenInfo = positionTokens[tokenId_]; Position memory position = tokenInfo.positions[index_]; return _bucketBankruptAfterDeposit(IPool(tokenInfo.pool), index_, position.depositTime) ? 0 : position.lps; } /// @inheritdoc IPositionManagerDerivedState - function getPositionIndexes(uint256 tokenId_) external view override returns (uint256[] memory) { + function getPositionIndexes( + uint256 tokenId_ + ) external view override returns (uint256[] memory) { return positionTokens[tokenId_].positionIndexes.values(); } @@ -495,21 +524,23 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc if (!_bucketBankruptAfterDeposit(pool, indexes[i], tokenInfo.positions[indexes[i]].depositTime)) { filteredIndexes_[filteredIndexesLength++] = indexes[i]; } - unchecked { - ++i; - } + unchecked { ++i; } } // resize array - assembly { - mstore(filteredIndexes_, filteredIndexesLength) - } + assembly { mstore(filteredIndexes_, filteredIndexesLength) } } /// @inheritdoc IPositionManagerDerivedState - function getPositionInfo(uint256 tokenId_, uint256 index_) external view override returns (uint256, uint256) { + function getPositionInfo( + uint256 tokenId_, + uint256 index_ + ) external view override returns (uint256, uint256) { Position memory position = positionTokens[tokenId_].positions[index_]; - return (position.lps, position.depositTime); + return ( + position.lps, + position.depositTime + ); } /// @inheritdoc IPositionManagerDerivedState @@ -518,42 +549,54 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc } /// @inheritdoc IPositionManagerDerivedState - function isAjnaPool(address pool_, bytes32 subsetHash_) external view override returns (bool) { + function isAjnaPool( + address pool_, + bytes32 subsetHash_ + ) external override view returns (bool) { return _isAjnaPool(pool_, subsetHash_); } /// @inheritdoc IPositionManagerDerivedState - function isPositionBucketBankrupt(uint256 tokenId_, uint256 index_) external view override returns (bool) { + function isPositionBucketBankrupt( + uint256 tokenId_, + uint256 index_ + ) external view override returns (bool) { TokenInfo storage tokenInfo = positionTokens[tokenId_]; return _bucketBankruptAfterDeposit(IPool(tokenInfo.pool), index_, tokenInfo.positions[index_].depositTime); } /// @inheritdoc IPositionManagerDerivedState - function isIndexInPosition(uint256 tokenId_, uint256 index_) external view override returns (bool) { + function isIndexInPosition( + uint256 tokenId_, + uint256 index_ + ) external override view returns (bool) { return positionTokens[tokenId_].positionIndexes.contains(index_); } /** * @dev See {IERC721Metadata-tokenURI}. */ - function tokenURI(uint256 tokenId_) public view override returns (string memory) { + function tokenURI( + uint256 tokenId_ + ) public view override returns (string memory) { if (!_exists(tokenId_)) revert NoToken(); TokenInfo storage tokenInfo = positionTokens[tokenId_]; address pool = tokenInfo.pool; address collateralTokenAddress = IPool(pool).collateralAddress(); - address quoteTokenAddress = IPool(pool).quoteTokenAddress(); + address quoteTokenAddress = IPool(pool).quoteTokenAddress(); PositionNFTSVG.ConstructTokenURIParams memory params = PositionNFTSVG.ConstructTokenURIParams({ collateralTokenSymbol: tokenSymbol(collateralTokenAddress), - quoteTokenSymbol: tokenSymbol(quoteTokenAddress), - tokenId: tokenId_, - pool: pool, - owner: ownerOf(tokenId_), - indexes: tokenInfo.positionIndexes.values() + quoteTokenSymbol: tokenSymbol(quoteTokenAddress), + tokenId: tokenId_, + pool: pool, + owner: ownerOf(tokenId_), + indexes: tokenInfo.positionIndexes.values() }); return PositionNFTSVG.constructTokenURI(params); } + } diff --git a/packages/ajna-contracts/contracts/ajna/RewardsManager.sol b/packages/ajna-contracts/contracts/ajna/RewardsManager.sol index 4baacc790..f46dfa7ce 100644 --- a/packages/ajna-contracts/contracts/ajna/RewardsManager.sol +++ b/packages/ajna-contracts/contracts/ajna/RewardsManager.sol @@ -2,24 +2,28 @@ pragma solidity 0.8.18; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol'; +import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; -import { IPool } from "./interfaces/pool/IPool.sol"; -import { IPositionManager } from "./interfaces/position/IPositionManager.sol"; -import { IPositionManagerOwnerActions } from "./interfaces/position/IPositionManagerOwnerActions.sol"; +import { IPool } from './interfaces/pool/IPool.sol'; +import { IPositionManager } from './interfaces/position/IPositionManager.sol'; +import { IPositionManagerOwnerActions } from './interfaces/position/IPositionManagerOwnerActions.sol'; import { IRewardsManager, IRewardsManagerOwnerActions, IRewardsManagerState, IRewardsManagerDerivedState -} from "./interfaces/rewards/IRewardsManager.sol"; -import { StakeInfo, BucketState, PoolRewardsInfo } from "./interfaces/rewards/IRewardsManagerState.sol"; +} from './interfaces/rewards/IRewardsManager.sol'; +import { + StakeInfo, + BucketState, + PoolRewardsInfo +} from './interfaces/rewards/IRewardsManagerState.sol'; -import { PositionManager } from "./PositionManager.sol"; +import { PositionManager } from './PositionManager.sol'; -import { Maths } from "./libraries/internal/Maths.sol"; +import { Maths } from './libraries/internal/Maths.sol'; /** * @title Rewards (staking) Manager contract @@ -32,6 +36,7 @@ import { Maths } from "./libraries/internal/Maths.sol"; * - `unstake` token */ contract RewardsManager is IRewardsManager { + using SafeERC20 for IERC20; /*****************/ @@ -92,7 +97,9 @@ contract RewardsManager is IRewardsManager { * @param positionManager_ Address of the PositionManager contract. */ constructor(address ajnaToken_, IPositionManager positionManager_) { - if (ajnaToken_ == address(0) || address(positionManager_) == address(0)) revert DeployWithZeroAddress(); + if ( + ajnaToken_ == address(0) || address(positionManager_) == address(0) + ) revert DeployWithZeroAddress(); ajnaToken = ajnaToken_; positionManager = positionManager_; @@ -110,7 +117,11 @@ contract RewardsManager is IRewardsManager { * @dev === Emit events === * @dev - `ClaimRewards` */ - function claimRewards(uint256 tokenId_, uint256 epochToClaim_, uint256 minAmount_) external override { + function claimRewards( + uint256 tokenId_, + uint256 epochToClaim_, + uint256 minAmount_ + ) external override { StakeInfo storage stakeInfo = stakes[tokenId_]; if (msg.sender != stakeInfo.owner) revert NotOwnerOfDeposit(); @@ -128,7 +139,10 @@ contract RewardsManager is IRewardsManager { ); // transfer rewards to claimer, ensuring amount is not below specified min amount - _transferAjnaRewards({ transferAmount_: rewardsEarned, minAmount_: minAmount_ }); + _transferAjnaRewards({ + transferAmount_: rewardsEarned, + minAmount_: minAmount_ + }); } /** @@ -138,14 +152,16 @@ contract RewardsManager is IRewardsManager { * @dev === Emit events === * @dev - `Stake` */ - function stake(uint256 tokenId_) external override { + function stake( + uint256 tokenId_ + ) external override { address ajnaPool = positionManager.poolKey(tokenId_); // check that msg.sender is owner of tokenId if (IERC721(address(positionManager)).ownerOf(tokenId_) != msg.sender) revert NotOwnerOfDeposit(); StakeInfo storage stakeInfo = stakes[tokenId_]; - stakeInfo.owner = msg.sender; + stakeInfo.owner = msg.sender; stakeInfo.ajnaPool = ajnaPool; uint256 curBurnEpoch = IPool(ajnaPool).currentBurnEpoch(); @@ -170,9 +186,7 @@ contract RewardsManager is IRewardsManager { bucketState.rateAtStakeTime = IPool(ajnaPool).bucketExchangeRate(bucketId); // iterations are bounded by array length (which is itself bounded), preventing overflow / underflow - unchecked { - ++i; - } + unchecked { ++i; } } emit Stake(msg.sender, ajnaPool, tokenId_); @@ -181,10 +195,17 @@ contract RewardsManager is IRewardsManager { IERC721(address(positionManager)).transferFrom(msg.sender, address(this), tokenId_); // calculate rewards for updating exchange rates, if any - uint256 updateReward = _updateBucketExchangeRates(ajnaPool, curBurnEpoch, positionIndexes); + uint256 updateReward = _updateBucketExchangeRates( + ajnaPool, + curBurnEpoch, + positionIndexes + ); // transfer bucket update rewards to sender even if there's not enough balance for entire amount - _transferAjnaRewards({ transferAmount_: updateReward, minAmount_: 0 }); + _transferAjnaRewards({ + transferAmount_: updateReward, + minAmount_: 0 + }); } /** @@ -195,19 +216,29 @@ contract RewardsManager is IRewardsManager { * @dev - `ClaimRewards` * @dev - `Unstake` */ - function unstake(uint256 tokenId_) external override { - _unstake({ tokenId_: tokenId_, claimRewards_: true }); + function unstake( + uint256 tokenId_ + ) external override { + _unstake({ + tokenId_: tokenId_, + claimRewards_: true + }); } - /** + /** * @inheritdoc IRewardsManagerOwnerActions * @dev === Revert on === * @dev not owner `NotOwnerOfDeposit()` * @dev === Emit events === * @dev - `Unstake` */ - function emergencyUnstake(uint256 tokenId_) external override { - _unstake({ tokenId_: tokenId_, claimRewards_: false }); + function emergencyUnstake( + uint256 tokenId_ + ) external override { + _unstake({ + tokenId_: tokenId_, + claimRewards_: false + }); } /** @@ -226,7 +257,10 @@ contract RewardsManager is IRewardsManager { updateReward = _updateBucketExchangeRates(pool_, IPool(pool_).currentBurnEpoch(), indexes_); // transfer bucket update rewards to sender even if there's not enough balance for entire amount - _transferAjnaRewards({ transferAmount_: updateReward, minAmount_: 0 }); + _transferAjnaRewards({ + transferAmount_: updateReward, + minAmount_: 0 + }); } /*******************************/ @@ -238,25 +272,36 @@ contract RewardsManager is IRewardsManager { uint256 tokenId_, uint256 epochToClaim_ ) external view override returns (uint256 rewards_) { - address ajnaPool = stakes[tokenId_].ajnaPool; + address ajnaPool = stakes[tokenId_].ajnaPool; uint256 lastClaimedEpoch = stakes[tokenId_].lastClaimedEpoch; - uint256 stakingEpoch = stakes[tokenId_].stakingEpoch; + uint256 stakingEpoch = stakes[tokenId_].stakingEpoch; uint256[] memory positionIndexes = positionManager.getPositionIndexesFiltered(tokenId_); // iterate through all burn periods to calculate and claim rewards for (uint256 epoch = lastClaimedEpoch; epoch < epochToClaim_; ) { - rewards_ += _calculateNextEpochRewards(tokenId_, epoch, stakingEpoch, ajnaPool, positionIndexes); - unchecked { - ++epoch; - } + rewards_ += _calculateNextEpochRewards( + tokenId_, + epoch, + stakingEpoch, + ajnaPool, + positionIndexes + ); + + unchecked { ++epoch; } } } /// @inheritdoc IRewardsManagerState - function getStakeInfo(uint256 tokenId_) external view override returns (address, address, uint256) { - return (stakes[tokenId_].owner, stakes[tokenId_].ajnaPool, stakes[tokenId_].lastClaimedEpoch); + function getStakeInfo( + uint256 tokenId_ + ) external view override returns (address, address, uint256) { + return ( + stakes[tokenId_].owner, + stakes[tokenId_].ajnaPool, + stakes[tokenId_].lastClaimedEpoch + ); } /// @inheritdoc IRewardsManagerState @@ -280,12 +325,18 @@ contract RewardsManager is IRewardsManager { } /// @inheritdoc IRewardsManagerState - function getRewardsClaimed(address pool_, uint256 epoch_) external view override returns (uint256) { + function getRewardsClaimed( + address pool_, + uint256 epoch_ + ) external view override returns (uint256) { return poolRewardsInfo[pool_].rewardsClaimed[epoch_]; } /// @inheritdoc IRewardsManagerState - function getUpdateRewardsClaimed(address pool_, uint256 epoch_) external view override returns (uint256) { + function getUpdateRewardsClaimed( + address pool_, + uint256 epoch_ + ) external view override returns (uint256) { return poolRewardsInfo[pool_].updateBucketRewardsClaimed[epoch_]; } @@ -304,9 +355,9 @@ contract RewardsManager is IRewardsManager { uint256 tokenId_, uint256 epochToClaim_ ) internal returns (uint256 rewards_) { - address ajnaPool = stakes[tokenId_].ajnaPool; + address ajnaPool = stakes[tokenId_].ajnaPool; uint256 lastClaimedEpoch = stakes[tokenId_].lastClaimedEpoch; - uint256 stakingEpoch = stakes[tokenId_].stakingEpoch; + uint256 stakingEpoch = stakes[tokenId_].stakingEpoch; mapping(uint256 => uint256) storage rewardsClaimed = poolRewardsInfo[ajnaPool].rewardsClaimed; @@ -314,6 +365,7 @@ contract RewardsManager is IRewardsManager { // iterate through all burn periods to calculate and claim rewards for (uint256 epoch = lastClaimedEpoch; epoch < epochToClaim_; ) { + uint256 nextEpochRewards = _calculateNextEpochRewards( tokenId_, epoch, @@ -324,9 +376,7 @@ contract RewardsManager is IRewardsManager { rewards_ += nextEpochRewards; - unchecked { - ++epoch; - } + unchecked { ++epoch; } // update epoch token claim trackers rewardsClaimed[epoch] += nextEpochRewards; @@ -351,6 +401,7 @@ contract RewardsManager is IRewardsManager { address ajnaPool_, uint256[] memory positionIndexes_ ) internal view returns (uint256 epochRewards_) { + uint256 nextEpoch = epoch_ + 1; uint256 claimedRewardsInNextEpoch = poolRewardsInfo[ajnaPool_].rewardsClaimed[nextEpoch]; uint256 bucketIndex; @@ -365,9 +416,11 @@ contract RewardsManager is IRewardsManager { uint256 bucketRate; if (epoch_ != stakingEpoch_) { + // if staked in a previous epoch then use the initial exchange rate of epoch bucketRate = poolRewardsInfo[ajnaPool_].bucketExchangeRates[bucketIndex][epoch_]; } else { + // if staked during the epoch then use the bucket rate at the time of staking bucketRate = bucketSnapshot.rateAtStakeTime; } @@ -380,14 +433,17 @@ contract RewardsManager is IRewardsManager { bucketSnapshot.lpsAtStakeTime, bucketRate ); - unchecked { - ++i; - } + unchecked { ++i; } } // calculate and accumulate rewards if interest earned if (interestEarned != 0) { - epochRewards_ = _calculateNewRewards(ajnaPool_, interestEarned, nextEpoch, claimedRewardsInNextEpoch); + epochRewards_ = _calculateNewRewards( + ajnaPool_, + interestEarned, + nextEpoch, + claimedRewardsInNextEpoch + ); } } @@ -407,15 +463,19 @@ contract RewardsManager is IRewardsManager { uint256 bucketLP_, uint256 exchangeRate_ ) internal view returns (uint256 interestEarned_) { + if (exchangeRate_ != 0) { + uint256 nextExchangeRate = poolRewardsInfo[pool_].bucketExchangeRates[bucketIndex_][nextEventEpoch_]; // calculate interest earned only if next exchange rate is higher than current exchange rate if (nextExchangeRate > exchangeRate_) { + // calculate the equivalent amount of quote tokens given the stakes lp balance, // and the exchange rate at the next and current burn events interestEarned_ = Maths.wmul(nextExchangeRate - exchangeRate_, bucketLP_); } + } } @@ -442,17 +502,19 @@ contract RewardsManager is IRewardsManager { ) = _getEpochInfo(ajnaPool_, nextEpoch_); // calculate rewards earned - newRewards_ = totalInterestEarnedInPeriod == 0 - ? 0 - : Maths.floorWdiv( - Maths.wmul(Maths.wmul(interestEarned_, totalBurnedInPeriod), REWARD_FACTOR), - totalInterestEarnedInPeriod - ); + newRewards_ = totalInterestEarnedInPeriod == 0 ? 0 : Maths.floorWdiv( + Maths.wmul( + Maths.wmul(interestEarned_, totalBurnedInPeriod), + REWARD_FACTOR + ), + totalInterestEarnedInPeriod + ); uint256 rewardsCapped = Maths.wmul(REWARD_CAP, totalBurnedInPeriod); // Check rewards claimed - check that less than 80% of the tokens for a given burn event have been claimed. if (rewardsClaimedInEpoch_ + newRewards_ > rewardsCapped) { + // set claim reward to difference between cap and reward newRewards_ = rewardsClaimedInEpoch_ > rewardsCapped ? 0 : rewardsCapped - rewardsClaimedInEpoch_; } @@ -474,6 +536,7 @@ contract RewardsManager is IRewardsManager { bool validateEpoch_, address ajnaPool_ ) internal returns (uint256 rewardsEarned_) { + // revert if higher epoch to claim than current burn epoch if (validateEpoch_ && epochToClaim_ > curBurnEpoch_) revert EpochNotAvailable(); @@ -488,9 +551,18 @@ contract RewardsManager is IRewardsManager { rewardsEarned_ += _calculateAndClaimStakingRewards(tokenId_, epochToClaim_); } - uint256[] memory burnEpochsClaimed = _getBurnEpochsClaimed(stakeInfo_.lastClaimedEpoch, epochToClaim_); + uint256[] memory burnEpochsClaimed = _getBurnEpochsClaimed( + stakeInfo_.lastClaimedEpoch, + epochToClaim_ + ); - emit ClaimRewards(msg.sender, ajnaPool_, tokenId_, burnEpochsClaimed, rewardsEarned_); + emit ClaimRewards( + msg.sender, + ajnaPool_, + tokenId_, + burnEpochsClaimed, + rewardsEarned_ + ); // update last interaction burn event stakeInfo_.lastClaimedEpoch = uint96(epochToClaim_); @@ -535,32 +607,37 @@ contract RewardsManager is IRewardsManager { uint256 curBurnEpoch_, uint256[] memory indexes_ ) internal returns (uint256 updatedRewards_) { + // retrieve epoch values used to determine if updater receives rewards - (uint256 curBurnTime, uint256 totalBurnedInEpoch, uint256 totalInterestEarned) = _getEpochInfo( - pool_, - curBurnEpoch_ - ); + ( + uint256 curBurnTime, + uint256 totalBurnedInEpoch, + uint256 totalInterestEarned + ) = _getEpochInfo(pool_, curBurnEpoch_); // Update exchange rates without reward if first epoch or if the epoch does not have burned tokens associated with it if (totalBurnedInEpoch == 0) { uint256 noOfIndexes = indexes_.length; for (uint256 i = 0; i < noOfIndexes; ) { - _updateBucketExchangeRate(pool_, indexes_[i], curBurnEpoch_); + _updateBucketExchangeRate( + pool_, + indexes_[i], + curBurnEpoch_ + ); // iterations are bounded by array length (which is itself bounded), preventing overflow / underflow - unchecked { - ++i; - } + unchecked { ++i; } } - } else { + } + else { if (block.timestamp <= curBurnTime + UPDATE_PERIOD) { - mapping(uint256 => uint256) storage updateRewardsClaimed = poolRewardsInfo[pool_] - .updateBucketRewardsClaimed; + mapping(uint256 => uint256) storage updateRewardsClaimed = poolRewardsInfo[pool_].updateBucketRewardsClaimed; // update exchange rates and calculate rewards if tokens were burned and within allowed time period uint256 noOfIndexes = indexes_.length; for (uint256 i = 0; i < noOfIndexes; ) { + // calculate rewards earned for updating bucket exchange rate updatedRewards_ += _updateBucketExchangeRateAndCalculateRewards( pool_, @@ -571,12 +648,10 @@ contract RewardsManager is IRewardsManager { ); // iterations are bounded by array length (which is itself bounded), preventing overflow / underflow - unchecked { - ++i; - } + unchecked { ++i; } } - uint256 rewardsCap = Maths.wmul(UPDATE_CAP, totalBurnedInEpoch); + uint256 rewardsCap = Maths.wmul(UPDATE_CAP, totalBurnedInEpoch); uint256 rewardsClaimedInEpoch = updateRewardsClaimed[curBurnEpoch_]; // update total tokens claimed for updating bucket exchange rates tracker @@ -603,11 +678,13 @@ contract RewardsManager is IRewardsManager { * @param bucketIndex_ Bucket index to update exchange rate. * @param burnEpoch_ Current burn epoch of the pool. */ - function _updateBucketExchangeRate(address pool_, uint256 bucketIndex_, uint256 burnEpoch_) internal { + function _updateBucketExchangeRate( + address pool_, + uint256 bucketIndex_, + uint256 burnEpoch_ + ) internal { // cache storage pointer for reduced gas - mapping(uint256 => uint256) storage _bucketExchangeRates = poolRewardsInfo[pool_].bucketExchangeRates[ - bucketIndex_ - ]; + mapping(uint256 => uint256) storage _bucketExchangeRates = poolRewardsInfo[pool_].bucketExchangeRates[bucketIndex_]; uint256 burnExchangeRate = _bucketExchangeRates[burnEpoch_]; // update bucket exchange rate at epoch only if it wasn't previously updated @@ -636,9 +713,7 @@ contract RewardsManager is IRewardsManager { uint256 interestEarned_ ) internal returns (uint256 rewards_) { // cache storage pointer for reduced gas - mapping(uint256 => uint256) storage _bucketExchangeRates = poolRewardsInfo[pool_].bucketExchangeRates[ - bucketIndex_ - ]; + mapping(uint256 => uint256) storage _bucketExchangeRates = poolRewardsInfo[pool_].bucketExchangeRates[bucketIndex_]; uint256 burnExchangeRate = _bucketExchangeRates[burnEpoch_]; // update bucket exchange rate at epoch only if it wasn't previously updated @@ -654,21 +729,23 @@ contract RewardsManager is IRewardsManager { // skip reward calculation if update at the previous epoch was missed and if exchange rate decreased due to bad debt // prevents excess rewards from being provided from using a 0 value as an input to the interestFactor calculation below. if (prevBucketExchangeRate != 0 && prevBucketExchangeRate < curBucketExchangeRate) { + // retrieve current deposit of the bucket (, , , uint256 bucketDeposit, ) = IPool(pool_).bucketInfo(bucketIndex_); uint256 burnFactor = Maths.wmul(totalBurned_, bucketDeposit); // calculate rewards earned for updating bucket exchange rate - rewards_ = interestEarned_ == 0 - ? 0 - : Maths.wdiv( + rewards_ = interestEarned_ == 0 ? 0 : Maths.wdiv( + Maths.wmul( + UPDATE_CLAIM_REWARD, Maths.wmul( - UPDATE_CLAIM_REWARD, - Maths.wmul(burnFactor, curBucketExchangeRate - prevBucketExchangeRate) - ), - Maths.wmul(curBucketExchangeRate, interestEarned_) - ); + burnFactor, + curBucketExchangeRate - prevBucketExchangeRate + ) + ), + Maths.wmul(curBucketExchangeRate, interestEarned_) + ); } } } @@ -708,9 +785,7 @@ contract RewardsManager is IRewardsManager { for (uint256 i = 0; i < noOfIndexes; ) { delete stakeInfo.snapshot[positionIndexes[i]]; // reset BucketState struct for current position - unchecked { - ++i; - } + unchecked { ++i; } } // remove recorded stake info @@ -720,7 +795,10 @@ contract RewardsManager is IRewardsManager { // gracefully unstake, transfer rewards to claimer ensuring entire amount if (claimRewards_) { - _transferAjnaRewards({ transferAmount_: rewardsEarned, minAmount_: rewardsEarned }); + _transferAjnaRewards({ + transferAmount_: rewardsEarned, + minAmount_: rewardsEarned + }); } // transfer LP NFT from contract to sender @@ -751,33 +829,43 @@ contract RewardsManager is IRewardsManager { } } -/**********************/ -/** Rewards Utilities */ -/**********************/ + /**********************/ + /** Rewards Utilities */ + /**********************/ -/** - * @notice Retrieve the total ajna tokens burned and total interest earned over a given epoch. - * @param pool_ Address of the `Ajna` pool to retrieve accumulators of. - * @param epoch_ time window used to identify time between Ajna burn events (kickReserve and takeReserve actions). - * @return currentBurnTime_ timestamp of the latest burn event. - * @return tokensBurned_ total `Ajna` tokens burned in epoch. - * @return interestEarned_ total interest earned in epoch. - */ -function _getEpochInfo( - address pool_, - uint256 epoch_ -) view returns (uint256 currentBurnTime_, uint256 tokensBurned_, uint256 interestEarned_) { - // 0 epoch won't have any ajna burned or interest associated with it - if (epoch_ != 0) { - uint256 totalInterestLatest; - uint256 totalBurnedLatest; - - (currentBurnTime_, totalInterestLatest, totalBurnedLatest) = IPool(pool_).burnInfo(epoch_); - - (, uint256 totalInterestPrev, uint256 totalBurnedPrev) = IPool(pool_).burnInfo(epoch_ - 1); - - // calculate total tokens burned and interest earned in epoch - tokensBurned_ = totalBurnedLatest != 0 ? totalBurnedLatest - totalBurnedPrev : 0; - interestEarned_ = totalInterestLatest != 0 ? totalInterestLatest - totalInterestPrev : 0; + /** + * @notice Retrieve the total ajna tokens burned and total interest earned over a given epoch. + * @param pool_ Address of the `Ajna` pool to retrieve accumulators of. + * @param epoch_ time window used to identify time between Ajna burn events (kickReserve and takeReserve actions). + * @return currentBurnTime_ timestamp of the latest burn event. + * @return tokensBurned_ total `Ajna` tokens burned in epoch. + * @return interestEarned_ total interest earned in epoch. + */ + function _getEpochInfo( + address pool_, + uint256 epoch_ + ) view returns (uint256 currentBurnTime_, uint256 tokensBurned_, uint256 interestEarned_) { + + // 0 epoch won't have any ajna burned or interest associated with it + if (epoch_ != 0) { + + uint256 totalInterestLatest; + uint256 totalBurnedLatest; + + ( + currentBurnTime_, + totalInterestLatest, + totalBurnedLatest + ) = IPool(pool_).burnInfo(epoch_); + + ( + , + uint256 totalInterestPrev, + uint256 totalBurnedPrev + ) = IPool(pool_).burnInfo(epoch_ - 1); + + // calculate total tokens burned and interest earned in epoch + tokensBurned_ = totalBurnedLatest != 0 ? totalBurnedLatest - totalBurnedPrev : 0; + interestEarned_ = totalInterestLatest != 0 ? totalInterestLatest - totalInterestPrev : 0; + } } -} diff --git a/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol b/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol index 1dc622d5b..5c4d9c7b1 100644 --- a/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol +++ b/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol @@ -13,6 +13,7 @@ import { IRewardsManager } from "../interfaces/rewards/IRewardsManager.sol"; import { IAccountGuard } from "../../interfaces/dpm/IAccountGuard.sol"; import { IWETH } from "../../interfaces/tokens/IWETH.sol"; +import { console } from "hardhat/console.sol"; interface IAjnaProxyActions { function positionManager() external view returns (IPositionManager); @@ -28,7 +29,7 @@ contract AjnaProxyActions is IAjnaProxyActions { address public immutable WETH; address public immutable GUARD; address public immutable deployer; - string public constant ajnaVersion = "Ajna_rc9"; + string public constant ajnaVersion = "Ajna_rc10"; IAjnaProxyActions public immutable self; IPositionManager public positionManager; IRewardsManager public rewardsManager; @@ -179,12 +180,16 @@ contract AjnaProxyActions is IAjnaProxyActions { * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance * @dev 1WBTC = 16,990.23 USDC translates to: 16990230 */ - function _supplyQuote(IERC20Pool pool, uint256 amount, uint256 price) internal { + function _supplyQuote( + IERC20Pool pool, + uint256 amount, + uint256 price + ) internal returns (uint256 bucketLP, uint256 addedAmount) { address debtToken = pool.quoteTokenAddress(); _pull(debtToken, amount); uint256 index = convertPriceToIndex(price); IERC20(debtToken).forceApprove(address(pool), amount); - pool.addQuoteToken(amount * pool.quoteTokenScale(), index, block.timestamp + 1); + (bucketLP, addedAmount) = pool.addQuoteToken(amount * pool.quoteTokenScale(), index, block.timestamp + 1); } /** @@ -209,13 +214,13 @@ contract AjnaProxyActions is IAjnaProxyActions { function _withdrawQuote(IERC20Pool pool, uint256 amount, uint256 price) internal { address debtToken = pool.quoteTokenAddress(); uint256 index = convertPriceToIndex(price); - uint256 balanceBefore = IERC20(debtToken).balanceOf(address(this)); + uint256 withdrawnBalanceWAD; if (amount == type(uint256).max) { - pool.removeQuoteToken(type(uint256).max, index); + (withdrawnBalanceWAD, ) = pool.removeQuoteToken(type(uint256).max, index); } else { - pool.removeQuoteToken((amount * pool.quoteTokenScale()), index); + (withdrawnBalanceWAD, ) = pool.removeQuoteToken((amount * pool.quoteTokenScale()), index); } - uint256 withdrawnBalance = IERC20(debtToken).balanceOf(address(this)) - balanceBefore; + uint256 withdrawnBalance = _roundToScale(withdrawnBalanceWAD, pool.quoteTokenScale()) / pool.quoteTokenScale(); _send(debtToken, withdrawnBalance); } @@ -224,12 +229,11 @@ contract AjnaProxyActions is IAjnaProxyActions { * @param pool Address of the Ajana Pool. * @param price Price of the bucket to redeem. */ - function _removeCollateral(IERC20Pool pool, uint256 price) internal { + function _removeCollateral(IERC20Pool pool, uint256 price) internal returns (uint256 withdrawnBalance) { address collateralToken = pool.collateralAddress(); uint256 index = convertPriceToIndex(price); - uint256 balanceBefore = IERC20(collateralToken).balanceOf(address(this)); - pool.removeCollateral(type(uint256).max, index); - uint256 withdrawnBalance = IERC20(collateralToken).balanceOf(address(this)) - balanceBefore; + (uint256 withdrawnBalanceWAD, ) = pool.removeCollateral(type(uint256).max, index); + withdrawnBalance = _roundToScale(withdrawnBalanceWAD, pool.collateralScale()) / pool.collateralScale(); _send(collateralToken, withdrawnBalance); } @@ -336,10 +340,15 @@ contract AjnaProxyActions is IAjnaProxyActions { _pull(debtToken, amount); IERC20(debtToken).forceApprove(address(pool), amount); (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); - uint256 balanceBefore = IERC20(debtToken).balanceOf(address(this)); - pool.repayDebt(address(this), amount * pool.quoteTokenScale(), 0, address(this), lupIndex_); + uint256 repaidAmountWAD = pool.repayDebt( + address(this), + amount * pool.quoteTokenScale(), + 0, + address(this), + lupIndex_ + ); _stampLoan(pool, stamploanEnabled); - uint256 repaidAmount = balanceBefore - IERC20(debtToken).balanceOf(address(this)); + uint256 repaidAmount = _roundUpToScale(repaidAmountWAD, pool.quoteTokenScale()) / pool.quoteTokenScale(); uint256 leftoverBalance = amount - repaidAmount; if (leftoverBalance > 0) { _send(debtToken, leftoverBalance); @@ -373,8 +382,7 @@ contract AjnaProxyActions is IAjnaProxyActions { _pull(debtToken, debtAmount); IERC20(debtToken).forceApprove(address(pool), debtAmount); (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); - uint256 quoteBalanceBefore = IERC20(debtToken).balanceOf(address(this)); - pool.repayDebt( + uint256 repaidAmountWAD = pool.repayDebt( address(this), debtAmount * pool.quoteTokenScale(), collateralAmount * pool.collateralScale(), @@ -382,7 +390,7 @@ contract AjnaProxyActions is IAjnaProxyActions { lupIndex_ ); _send(collateralToken, collateralAmount); - uint256 repaidAmount = quoteBalanceBefore - IERC20(debtToken).balanceOf(address(this)); + uint256 repaidAmount = _roundUpToScale(repaidAmountWAD, pool.quoteTokenScale()) / pool.quoteTokenScale(); uint256 quoteLeftoverBalance = debtAmount - repaidAmount; if (quoteLeftoverBalance > 0) { _send(debtToken, quoteLeftoverBalance); @@ -807,4 +815,25 @@ contract AjnaProxyActions is IAjnaProxyActions { (uint256 lpCount, ) = pool.lenderInfo(index, address(this)); quoteAmount = poolInfoUtils.lpToQuoteTokens(address(pool), lpCount, index); } + + /** + * @notice Rounds a token amount down to the minimum amount permissible by the token scale. + * @param amount_ Value to be rounded. + * @param tokenScale_ Scale of the token, presented as a power of `10`. + * @return scaledAmount_ Rounded value. + */ + function _roundToScale(uint256 amount_, uint256 tokenScale_) public pure returns (uint256 scaledAmount_) { + scaledAmount_ = (amount_ / tokenScale_) * tokenScale_; + } + + /** + * @notice Rounds a token amount up to the next amount permissible by the token scale. + * @param amount_ Value to be rounded. + * @param tokenScale_ Scale of the token, presented as a power of `10`. + * @return scaledAmount_ Rounded value. + */ + function _roundUpToScale(uint256 amount_, uint256 tokenScale_) public pure returns (uint256 scaledAmount_) { + if (amount_ % tokenScale_ == 0) scaledAmount_ = amount_; + else scaledAmount_ = _roundToScale(amount_, tokenScale_) + tokenScale_; + } } diff --git a/packages/ajna-contracts/contracts/ajna/base/Pool.sol b/packages/ajna-contracts/contracts/ajna/base/Pool.sol index a3f774451..efa587e6f 100644 --- a/packages/ajna-contracts/contracts/ajna/base/Pool.sol +++ b/packages/ajna-contracts/contracts/ajna/base/Pool.sol @@ -50,6 +50,7 @@ import { } from '../interfaces/pool/commons/IPoolInternals.sol'; import { + COLLATERALIZATION_FACTOR, _determineInflatorState, _priceAt, _roundToScale @@ -152,7 +153,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { uint256 amount_, uint256 index_, uint256 expiry_ - ) external override nonReentrant returns (uint256 bucketLP_) { + ) external override nonReentrant returns (uint256 bucketLP_, uint256 addedAmount_) { _revertAfterExpiry(expiry_); _revertIfAuctionClearable(auctions, loans); @@ -163,7 +164,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { amount_ = _roundToScale(amount_, poolState.quoteTokenScale); uint256 newLup; - (bucketLP_, newLup) = LenderActions.addQuoteToken( + (bucketLP_, addedAmount_, newLup) = LenderActions.addQuoteToken( buckets, deposits, poolState, @@ -940,7 +941,7 @@ abstract contract Pool is Clone, ReentrancyGuard, Multicall, IPool { Loan memory maxLoan = Loans.getMax(loans); return ( maxLoan.borrower, - Maths.wmul(maxLoan.thresholdPrice, inflatorState.inflator), + Maths.wmul(Maths.wmul(maxLoan.thresholdPrice, inflatorState.inflator), COLLATERALIZATION_FACTOR), Loans.noOfLoans(loans) ); } diff --git a/packages/ajna-contracts/contracts/ajna/interfaces/pool/commons/IPoolLenderActions.sol b/packages/ajna-contracts/contracts/ajna/interfaces/pool/commons/IPoolLenderActions.sol index 89544a184..9c2286e06 100644 --- a/packages/ajna-contracts/contracts/ajna/interfaces/pool/commons/IPoolLenderActions.sol +++ b/packages/ajna-contracts/contracts/ajna/interfaces/pool/commons/IPoolLenderActions.sol @@ -17,12 +17,13 @@ interface IPoolLenderActions { * @param index_ The index of the bucket to which the quote tokens will be added. * @param expiry_ Timestamp after which this transaction will revert, preventing inclusion in a block with unfavorable price. * @return bucketLP_ The amount of `LP` changed for the added quote tokens (`WAD` precision). + * @return addedAmount_ The amount of quote token added (`WAD` precision). */ function addQuoteToken( uint256 amount_, uint256 index_, uint256 expiry_ - ) external returns (uint256 bucketLP_); + ) external returns (uint256 bucketLP_, uint256 addedAmount_); /** * @notice Called by lenders to move an amount of credit from a specified price bucket to another specified price bucket. diff --git a/packages/ajna-contracts/contracts/ajna/libraries/external/KickerActions.sol b/packages/ajna-contracts/contracts/ajna/libraries/external/KickerActions.sol index 9ab5a995c..f89fbea65 100644 --- a/packages/ajna-contracts/contracts/ajna/libraries/external/KickerActions.sol +++ b/packages/ajna-contracts/contracts/ajna/libraries/external/KickerActions.sol @@ -26,6 +26,7 @@ import { import { MAX_INFLATED_PRICE, + COLLATERALIZATION_FACTOR, _bondParams, _claimableReserves, _isCollateralized, @@ -314,7 +315,7 @@ library KickerActions { // which will make it harder for kicker to earn a reward and more likely that the kicker is penalized _revertIfPriceDroppedBelowLimit(vars.neutralPrice, limitIndex_); - vars.htp = Maths.wmul(Loans.getMax(loans_).thresholdPrice, poolState_.inflator); + vars.htp = Maths.wmul(Maths.wmul(Loans.getMax(loans_).thresholdPrice, poolState_.inflator), COLLATERALIZATION_FACTOR); vars.referencePrice = Maths.min(Maths.max(vars.htp, vars.neutralPrice), MAX_INFLATED_PRICE); (vars.bondFactor, vars.bondSize) = _bondParams( diff --git a/packages/ajna-contracts/contracts/ajna/libraries/external/LenderActions.sol b/packages/ajna-contracts/contracts/ajna/libraries/external/LenderActions.sol index 3d53471f3..31228aa55 100644 --- a/packages/ajna-contracts/contracts/ajna/libraries/external/LenderActions.sol +++ b/packages/ajna-contracts/contracts/ajna/libraries/external/LenderActions.sol @@ -16,7 +16,12 @@ import { PoolState } from '../../interfaces/pool/commons/IPoolState.sol'; -import { _depositFeeRate, _priceAt, MAX_FENWICK_INDEX } from '../helpers/PoolHelper.sol'; +import { + _depositFeeRate, + _priceAt, + MAX_FENWICK_INDEX, + COLLATERALIZATION_FACTOR +} from '../helpers/PoolHelper.sol'; import { Deposits } from '../internal/Deposits.sol'; import { Buckets } from '../internal/Buckets.sol'; @@ -149,7 +154,7 @@ library LenderActions { DepositsState storage deposits_, PoolState calldata poolState_, AddQuoteParams calldata params_ - ) external returns (uint256 bucketLP_, uint256 lup_) { + ) external returns (uint256 bucketLP_, uint256 addedAmount_, uint256 lup_) { // revert if no amount to be added if (params_.amount == 0) revert InvalidAmount(); // revert if adding to an invalid index @@ -166,16 +171,16 @@ library LenderActions { uint256 bucketScale = Deposits.scale(deposits_, params_.index); uint256 bucketDeposit = Maths.wmul(bucketScale, unscaledBucketDeposit); uint256 bucketPrice = _priceAt(params_.index); - uint256 addedAmount = params_.amount; + addedAmount_ = params_.amount; // charge deposit fee - addedAmount = Maths.wmul(addedAmount, Maths.WAD - _depositFeeRate(poolState_.rate)); + addedAmount_ = Maths.wmul(addedAmount_, Maths.WAD - _depositFeeRate(poolState_.rate)); bucketLP_ = Buckets.quoteTokensToLP( bucket.collateral, bucket.lps, bucketDeposit, - addedAmount, + addedAmount_, bucketPrice, Math.Rounding.Down ); @@ -183,7 +188,7 @@ library LenderActions { // revert if (due to rounding) the awarded LP is 0 if (bucketLP_ == 0) revert InsufficientLP(); - uint256 unscaledAmount = Maths.wdiv(addedAmount, bucketScale); + uint256 unscaledAmount = Maths.wdiv(addedAmount_, bucketScale); // revert if unscaled amount is 0 if (unscaledAmount == 0) revert InvalidAmount(); @@ -202,7 +207,7 @@ library LenderActions { emit AddQuoteToken( msg.sender, params_.index, - addedAmount, + addedAmount_, bucketLP_, lup_ ); @@ -298,7 +303,7 @@ library LenderActions { // recalculate LUP and HTP lup_ = Deposits.getLup(deposits_, poolState_.debt); - vars.htp = Maths.wmul(params_.thresholdPrice, poolState_.inflator); + vars.htp = Maths.wmul(Maths.wmul(params_.thresholdPrice, poolState_.inflator), COLLATERALIZATION_FACTOR); // check loan book's htp against new lup, revert if move drives LUP below HTP if ( @@ -415,7 +420,7 @@ library LenderActions { lup_ = Deposits.getLup(deposits_, poolState_.debt); - uint256 htp = Maths.wmul(params_.thresholdPrice, poolState_.inflator); + uint256 htp = Maths.wmul(Maths.wmul(params_.thresholdPrice, poolState_.inflator), COLLATERALIZATION_FACTOR); if ( // check loan book's htp doesn't exceed new lup diff --git a/packages/ajna-contracts/contracts/ajna/libraries/external/PoolCommons.sol b/packages/ajna-contracts/contracts/ajna/libraries/external/PoolCommons.sol index 6de8ec58a..09723e3f8 100644 --- a/packages/ajna-contracts/contracts/ajna/libraries/external/PoolCommons.sol +++ b/packages/ajna-contracts/contracts/ajna/libraries/external/PoolCommons.sol @@ -8,11 +8,17 @@ import { PRBMathUD60x18 } from "../../libs/prb-math/contracts/PRBMathUD60x18.sol import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + import { InterestState, EmaState, PoolState, DepositsState } from '../../interfaces/pool/commons/IPoolState.sol'; import { IERC3156FlashBorrower } from '../../interfaces/pool/IERC3156FlashBorrower.sol'; -import { _dwatp, _indexOf, MAX_FENWICK_INDEX, MIN_PRICE, MAX_PRICE } from '../helpers/PoolHelper.sol'; - +import { + _dwatp, + _indexOf, + MAX_FENWICK_INDEX, + MIN_PRICE, MAX_PRICE, + COLLATERALIZATION_FACTOR +} from '../helpers/PoolHelper.sol'; import { Deposits } from '../internal/Deposits.sol'; import { Buckets } from '../internal/Buckets.sol'; @@ -245,7 +251,7 @@ library PoolCommons { // calculate the highest threshold price newInflator_ = Maths.wmul(poolState_.inflator, pendingFactor); - uint256 htp = Maths.wmul(thresholdPrice_, poolState_.inflator); + uint256 htp = Maths.wmul(Maths.wmul(thresholdPrice_, poolState_.inflator), COLLATERALIZATION_FACTOR); uint256 accrualIndex; if (htp > MAX_PRICE) accrualIndex = 1; // if HTP is over the highest price bucket then no buckets earn interest diff --git a/packages/ajna-contracts/contracts/ajna/libraries/helpers/PoolHelper.sol b/packages/ajna-contracts/contracts/ajna/libraries/helpers/PoolHelper.sol index 5be0ecba7..15f657ab0 100644 --- a/packages/ajna-contracts/contracts/ajna/libraries/helpers/PoolHelper.sol +++ b/packages/ajna-contracts/contracts/ajna/libraries/helpers/PoolHelper.sol @@ -35,6 +35,9 @@ import { Maths } from '../internal/Maths.sol'; /// @dev step amounts in basis points. This is a constant across pools at `0.005`, achieved by dividing `WAD` by `10,000` int256 constant FLOAT_STEP_INT = 1.005 * 1e18; + /// @dev collateralization factor used to calculate borrrower HTP/TP/collateralization. + uint256 constant COLLATERALIZATION_FACTOR = 1.04 * 1e18; + /** * @notice Calculates the price (`WAD` precision) for a given `Fenwick` index. * @dev Reverts with `BucketIndexOutOfBounds` if index exceeds maximum constant. @@ -175,7 +178,13 @@ import { Maths } from '../internal/Maths.sol'; uint256 inflator_, uint256 t0Debt2ToCollateral_ ) pure returns (uint256) { - return t0Debt_ == 0 ? 0 : Maths.wdiv(Maths.wmul(inflator_, t0Debt2ToCollateral_), t0Debt_); + return t0Debt_ == 0 ? 0 : Maths.wdiv( + Maths.wmul( + Maths.wmul(inflator_, t0Debt2ToCollateral_), + COLLATERALIZATION_FACTOR + ), + t0Debt_ + ); } /** @@ -201,7 +210,7 @@ import { Maths } from '../internal/Maths.sol'; collateral_ = (collateral_ / Maths.WAD) * Maths.WAD; // use collateral floor } - return Maths.wmul(collateral_, price_) >= Maths.wmul(1.04 * 1e18, debt_); + return Maths.wmul(collateral_, price_) >= Maths.wmul(COLLATERALIZATION_FACTOR, debt_); } /** From ae5a02bb66107aa14add1e02d3eca5853707be31 Mon Sep 17 00:00:00 2001 From: halaprix Date: Thu, 30 Nov 2023 16:57:04 +0100 Subject: [PATCH 2/6] chore: update addresses --- .../contracts/ajna/ajna-actions/AjnaProxyActions.sol | 6 +++--- packages/ajna-contracts/scripts/common/config.ts | 8 ++++---- packages/deploy-configurations/configs/goerli.conf.ts | 4 ++-- packages/deploy-configurations/configs/sepolia.conf.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol b/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol index 5c4d9c7b1..f6b1e5801 100644 --- a/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol +++ b/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol @@ -430,7 +430,7 @@ contract AjnaProxyActions is IAjnaProxyActions { address debtToken = pool.quoteTokenAddress(); (uint256 debt, uint256 collateral, ) = poolInfoUtils.borrowerInfo(address(pool), address(this)); - uint256 debtPlusBuffer = ((debt / pool.quoteTokenScale()) + 1) * pool.quoteTokenScale(); + uint256 debtPlusBuffer = _roundUpToScale(debt, pool.quoteTokenScale()); uint256 amountDebt = debtPlusBuffer / pool.quoteTokenScale(); _pull(debtToken, amountDebt); @@ -822,7 +822,7 @@ contract AjnaProxyActions is IAjnaProxyActions { * @param tokenScale_ Scale of the token, presented as a power of `10`. * @return scaledAmount_ Rounded value. */ - function _roundToScale(uint256 amount_, uint256 tokenScale_) public pure returns (uint256 scaledAmount_) { + function _roundToScale(uint256 amount_, uint256 tokenScale_) internal pure returns (uint256 scaledAmount_) { scaledAmount_ = (amount_ / tokenScale_) * tokenScale_; } @@ -832,7 +832,7 @@ contract AjnaProxyActions is IAjnaProxyActions { * @param tokenScale_ Scale of the token, presented as a power of `10`. * @return scaledAmount_ Rounded value. */ - function _roundUpToScale(uint256 amount_, uint256 tokenScale_) public pure returns (uint256 scaledAmount_) { + function _roundUpToScale(uint256 amount_, uint256 tokenScale_) internal pure returns (uint256 scaledAmount_) { if (amount_ % tokenScale_ == 0) scaledAmount_ = amount_; else scaledAmount_ = _roundToScale(amount_, tokenScale_) + tokenScale_; } diff --git a/packages/ajna-contracts/scripts/common/config.ts b/packages/ajna-contracts/scripts/common/config.ts index 86027afd6..e72d719a8 100644 --- a/packages/ajna-contracts/scripts/common/config.ts +++ b/packages/ajna-contracts/scripts/common/config.ts @@ -2,10 +2,10 @@ import { Pool } from "./types"; export const ADDRESSES = { goerli: { - AJNA_PROXY_ACTIONS: "0xCf90A985b9648bbe1ECcd87D9B0d73d7f3254f19", - ERC20_POOL_FACTORY: "0x378c45f3f9FAc53261A675cC5CF971563634174d", - POOL_INFO_UTILS: "0xAA3C8bebFf9a9da25F2eBBb800398D594207393f", - POSITION_MANAGER: "0xa2f247C6BAB7d8A749339361978FcF5e1e99dC02", + AJNA_PROXY_ACTIONS: "0x499afBC7aE808e51e48A29FD30e80DfC5B1F2e56", + ERC20_POOL_FACTORY: "0xe86a0B3031a01eAB1eD850074476B80cCBc2B33d", + POOL_INFO_UTILS: "0x44fc9f35aF1f650B84754643B371A077F54009F7", + POSITION_MANAGER: "0x3E55440D385D1982509f6D5A35D2E932AAADc3C0", REWARD_MANAGER: "0x0000000000000000000000000000000000000000", GUARD: "0x9319710C25cdaDDD1766F0bDE40F1A4034C17c7e", SERVICE_REGISTRY: "0x5A5277B8c8a42e6d8Ab517483D7D59b4ca03dB7F", diff --git a/packages/deploy-configurations/configs/goerli.conf.ts b/packages/deploy-configurations/configs/goerli.conf.ts index 9e3ddad3b..a3b4c95d7 100644 --- a/packages/deploy-configurations/configs/goerli.conf.ts +++ b/packages/deploy-configurations/configs/goerli.conf.ts @@ -967,7 +967,7 @@ export const config: SystemConfig = { ajna: { AjnaPoolInfo: { name: 'AjnaPoolInfo', - address: '0xAA3C8bebFf9a9da25F2eBBb800398D594207393f', + address: '0x44fc9f35aF1f650B84754643B371A077F54009F7', serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.AJNA_POOL_UTILS_INFO, }, AjnaProxyActions: { @@ -1093,7 +1093,7 @@ export const config: SystemConfig = { }, ERC20PoolFactory: { name: 'ERC20PoolFactory', - address: '0x378c45f3f9FAc53261A675cC5CF971563634174d', + address: '0xe86a0B3031a01eAB1eD850074476B80cCBc2B33d', serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.ERC20_POOL_FACTORY, }, }, diff --git a/packages/deploy-configurations/configs/sepolia.conf.ts b/packages/deploy-configurations/configs/sepolia.conf.ts index a9927cb12..1cfb0f0df 100644 --- a/packages/deploy-configurations/configs/sepolia.conf.ts +++ b/packages/deploy-configurations/configs/sepolia.conf.ts @@ -1093,7 +1093,7 @@ export const config: SystemConfig = { }, ERC20PoolFactory: { name: 'ERC20PoolFactory', - address: '0x378c45f3f9FAc53261A675cC5CF971563634174d', + address: '0xe86a0B3031a01eAB1eD850074476B80cCBc2B33d', serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.ERC20_POOL_FACTORY, }, }, From 89216d18321ddeb4c1f5b28c518537e9f05d20b4 Mon Sep 17 00:00:00 2001 From: halaprix Date: Fri, 1 Dec 2023 19:33:19 +0100 Subject: [PATCH 3/6] chore: format --- .../contracts/ajna/ERC20Pool.sol | 90 ++--- .../contracts/ajna/ERC20PoolFactory.sol | 15 +- .../contracts/ajna/ERC721Pool.sol | 140 ++++---- .../contracts/ajna/ERC721PoolFactory.sol | 17 +- .../contracts/ajna/PoolInfoUtils.sol | 299 +++++++--------- .../contracts/ajna/PoolInfoUtilsMulticall.sol | 76 +++-- .../contracts/ajna/PositionManager.sol | 187 ++++------ .../contracts/ajna/RewardsManager.sol | 318 +++++++----------- 8 files changed, 458 insertions(+), 684 deletions(-) diff --git a/packages/ajna-contracts/contracts/ajna/ERC20Pool.sol b/packages/ajna-contracts/contracts/ajna/ERC20Pool.sol index ca149424e..7520b8a41 100644 --- a/packages/ajna-contracts/contracts/ajna/ERC20Pool.sol +++ b/packages/ajna-contracts/contracts/ajna/ERC20Pool.sol @@ -3,26 +3,23 @@ pragma solidity 0.8.18; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { +import { IERC20Pool, IERC20PoolBorrowerActions, IERC20PoolImmutables, IERC20PoolLenderActions -} from './interfaces/pool/erc20/IERC20Pool.sol'; -import { IERC20Taker } from './interfaces/pool/erc20/IERC20Taker.sol'; +} from "./interfaces/pool/erc20/IERC20Pool.sol"; +import { IERC20Taker } from "./interfaces/pool/erc20/IERC20Taker.sol"; import { IPoolLenderActions, IPoolKickerActions, IPoolTakerActions, IPoolSettlerActions -} from './interfaces/pool/IPool.sol'; -import { - IERC3156FlashBorrower, - IERC3156FlashLender -} from './interfaces/pool/IERC3156FlashLender.sol'; +} from "./interfaces/pool/IPool.sol"; +import { IERC3156FlashBorrower, IERC3156FlashLender } from "./interfaces/pool/IERC3156FlashLender.sol"; import { DrawDebtResult, @@ -30,29 +27,26 @@ import { SettleParams, SettleResult, TakeResult -} from './interfaces/pool/commons/IPoolInternals.sol'; -import { PoolState } from './interfaces/pool/commons/IPoolState.sol'; +} from "./interfaces/pool/commons/IPoolInternals.sol"; +import { PoolState } from "./interfaces/pool/commons/IPoolState.sol"; -import { FlashloanablePool } from './base/FlashloanablePool.sol'; +import { FlashloanablePool } from "./base/FlashloanablePool.sol"; import { _getCollateralDustPricePrecisionAdjustment, _roundToScale, _roundUpToScale -} from './libraries/helpers/PoolHelper.sol'; -import { - _revertIfAuctionClearable, - _revertAfterExpiry -} from './libraries/helpers/RevertsHelper.sol'; +} from "./libraries/helpers/PoolHelper.sol"; +import { _revertIfAuctionClearable, _revertAfterExpiry } from "./libraries/helpers/RevertsHelper.sol"; -import { Loans } from './libraries/internal/Loans.sol'; -import { Deposits } from './libraries/internal/Deposits.sol'; -import { Maths } from './libraries/internal/Maths.sol'; +import { Loans } from "./libraries/internal/Loans.sol"; +import { Deposits } from "./libraries/internal/Deposits.sol"; +import { Maths } from "./libraries/internal/Maths.sol"; -import { BorrowerActions } from './libraries/external/BorrowerActions.sol'; -import { LenderActions } from './libraries/external/LenderActions.sol'; -import { SettlerActions } from './libraries/external/SettlerActions.sol'; -import { TakerActions } from './libraries/external/TakerActions.sol'; +import { BorrowerActions } from "./libraries/external/BorrowerActions.sol"; +import { LenderActions } from "./libraries/external/LenderActions.sol"; +import { SettlerActions } from "./libraries/external/SettlerActions.sol"; +import { TakerActions } from "./libraries/external/TakerActions.sol"; /** * @title ERC20 Pool contract @@ -83,15 +77,13 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { /****************************/ /// @inheritdoc IERC20Pool - function initialize( - uint256 rate_ - ) external override { + function initialize(uint256 rate_) external override { if (isPoolInitialized) revert AlreadyInitialized(); - inflatorState.inflator = uint208(1e18); + inflatorState.inflator = uint208(1e18); inflatorState.inflatorUpdate = uint48(block.timestamp); - interestState.interestRate = uint208(rate_); + interestState.interestRate = uint208(rate_); interestState.interestRateUpdate = uint48(block.timestamp); Loans.init(loans); @@ -136,7 +128,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { PoolState memory poolState = _accruePoolInterest(); // ensure the borrower is not charged for additional debt that they did not receive - amountToBorrow_ = _roundToScale(amountToBorrow_, poolState.quoteTokenScale); + amountToBorrow_ = _roundToScale(amountToBorrow_, poolState.quoteTokenScale); // ensure the borrower is not credited with a fractional amount of collateral smaller than the token scale collateralToPledge_ = _roundToScale(collateralToPledge_, _getArgUint256(COLLATERAL_SCALE)); @@ -155,8 +147,8 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { emit DrawDebt(borrowerAddress_, amountToBorrow_, collateralToPledge_, result.newLup); // update in memory pool state struct - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; + poolState.debt = result.poolDebt; + poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; // adjust t0Debt2ToCollateral ratio @@ -208,7 +200,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { // ensure accounting is performed using the appropriate token scale if (maxQuoteTokenAmountToRepay_ != type(uint256).max) maxQuoteTokenAmountToRepay_ = _roundToScale(maxQuoteTokenAmountToRepay_, poolState.quoteTokenScale); - collateralAmountToPull_ = _roundToScale(collateralAmountToPull_, _getArgUint256(COLLATERAL_SCALE)); + collateralAmountToPull_ = _roundToScale(collateralAmountToPull_, _getArgUint256(COLLATERAL_SCALE)); RepayDebtResult memory result = BorrowerActions.repayDebt( auctions, @@ -224,8 +216,8 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { emit RepayDebt(borrowerAddress_, result.quoteTokenToRepay, collateralAmountToPull_, result.newLup); // update in memory pool state struct - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; + poolState.debt = result.poolDebt; + poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; // adjust t0Debt2ToCollateral ratio @@ -280,12 +272,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { if (amountToAdd_ != 0 && amountToAdd_ < _bucketCollateralDust(index_)) revert DustAmountNotExceeded(); amountToAdd_ = _roundToScale(amountToAdd_, _getArgUint256(COLLATERAL_SCALE)); - bucketLP_ = LenderActions.addCollateral( - buckets, - deposits, - amountToAdd_, - index_ - ); + bucketLP_ = LenderActions.addCollateral(buckets, deposits, amountToAdd_, index_); emit AddCollateral(msg.sender, index_, amountToAdd_, bucketLP_); @@ -356,7 +343,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { reserveAuction, poolState, SettleParams({ - borrower: borrowerAddress_, + borrower: borrowerAddress_, poolBalance: _getNormalizedPoolQuoteTokenBalance(), bucketDepth: maxDepth_ }) @@ -377,9 +364,9 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function take( - address borrowerAddress_, - uint256 maxAmount_, - address callee_, + address borrowerAddress_, + uint256 maxAmount_, + address callee_, bytes calldata data_ ) external override nonReentrant returns (uint256 collateralTaken_) { PoolState memory poolState = _accruePoolInterest(); @@ -427,12 +414,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @dev - decrement `poolBalances.pledgedCollateral` accumulator * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ - function bucketTake( - address borrowerAddress_, - bool depositTake_, - uint256 index_ - ) external override nonReentrant { - + function bucketTake(address borrowerAddress_, bool depositTake_, uint256 index_) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); TakeResult memory result = TakerActions.bucketTake( @@ -458,9 +440,7 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { * @inheritdoc FlashloanablePool * @dev Override default implementation and allows flashloans for both quote and collateral token. */ - function _isFlashloanSupported( - address token_ - ) internal virtual view override returns (bool) { + function _isFlashloanSupported(address token_) internal view virtual override returns (bool) { return token_ == _getArgAddress(QUOTE_ADDRESS) || token_ == _getArgAddress(COLLATERAL_ADDRESS); } @@ -498,5 +478,5 @@ contract ERC20Pool is FlashloanablePool, IERC20Pool { uint256 pricePrecisionAdjustment = _getCollateralDustPricePrecisionAdjustment(bucketIndex_); // difference between the normalized scale and the collateral token's scale return Maths.max(_getArgUint256(COLLATERAL_SCALE), 10 ** pricePrecisionAdjustment); - } + } } diff --git a/packages/ajna-contracts/contracts/ajna/ERC20PoolFactory.sol b/packages/ajna-contracts/contracts/ajna/ERC20PoolFactory.sol index 7ece99e25..968376c2d 100644 --- a/packages/ajna-contracts/contracts/ajna/ERC20PoolFactory.sol +++ b/packages/ajna-contracts/contracts/ajna/ERC20PoolFactory.sol @@ -4,12 +4,12 @@ pragma solidity 0.8.18; import { ClonesWithImmutableArgs } from "./libs/clones-with-immutable-args/src/ClonesWithImmutableArgs.sol"; -import { IERC20PoolFactory } from './interfaces/pool/erc20/IERC20PoolFactory.sol'; -import { IPoolFactory } from './interfaces/pool/IPoolFactory.sol'; -import { PoolType } from './interfaces/pool/IPool.sol'; +import { IERC20PoolFactory } from "./interfaces/pool/erc20/IERC20PoolFactory.sol"; +import { IPoolFactory } from "./interfaces/pool/IPoolFactory.sol"; +import { PoolType } from "./interfaces/pool/IPool.sol"; -import { ERC20Pool } from './ERC20Pool.sol'; -import { PoolDeployer } from './base/PoolDeployer.sol'; +import { ERC20Pool } from "./ERC20Pool.sol"; +import { PoolDeployer } from "./base/PoolDeployer.sol"; /** * @title ERC20 Pool Factory @@ -18,7 +18,6 @@ import { PoolDeployer } from './base/PoolDeployer.sol'; * @dev Reverts if pool is already created or if params to deploy new pool are invalid. */ contract ERC20PoolFactory is PoolDeployer, IERC20PoolFactory { - using ClonesWithImmutableArgs for address; /// @dev `ERC20` clonable pool contract used to deploy the new pool. @@ -50,7 +49,9 @@ contract ERC20PoolFactory is PoolDeployer, IERC20PoolFactory { * @dev - `PoolCreated` */ function deployPool( - address collateral_, address quote_, uint256 interestRate_ + address collateral_, + address quote_, + uint256 interestRate_ ) external canDeploy(collateral_, quote_, interestRate_) returns (address pool_) { address existingPool = deployedPools[ERC20_NON_SUBSET_HASH][collateral_][quote_]; if (existingPool != address(0)) revert IPoolFactory.PoolAlreadyExists(existingPool); diff --git a/packages/ajna-contracts/contracts/ajna/ERC721Pool.sol b/packages/ajna-contracts/contracts/ajna/ERC721Pool.sol index 003d5ee80..11ec7bdf4 100644 --- a/packages/ajna-contracts/contracts/ajna/ERC721Pool.sol +++ b/packages/ajna-contracts/contracts/ajna/ERC721Pool.sol @@ -9,41 +9,38 @@ import { IPoolKickerActions, IPoolTakerActions, IPoolSettlerActions -} from './interfaces/pool/IPool.sol'; +} from "./interfaces/pool/IPool.sol"; import { DrawDebtResult, RepayDebtResult, SettleParams, SettleResult, TakeResult -} from './interfaces/pool/commons/IPoolInternals.sol'; -import { PoolState } from './interfaces/pool/commons/IPoolState.sol'; +} from "./interfaces/pool/commons/IPoolInternals.sol"; +import { PoolState } from "./interfaces/pool/commons/IPoolState.sol"; import { IERC721Pool, IERC721PoolBorrowerActions, IERC721PoolImmutables, IERC721PoolLenderActions -} from './interfaces/pool/erc721/IERC721Pool.sol'; -import { IERC721Taker } from './interfaces/pool/erc721/IERC721Taker.sol'; -import { IERC721PoolState } from './interfaces/pool/erc721/IERC721PoolState.sol'; +} from "./interfaces/pool/erc721/IERC721Pool.sol"; +import { IERC721Taker } from "./interfaces/pool/erc721/IERC721Taker.sol"; +import { IERC721PoolState } from "./interfaces/pool/erc721/IERC721PoolState.sol"; -import { FlashloanablePool } from './base/FlashloanablePool.sol'; -import { _roundToScale } from './libraries/helpers/PoolHelper.sol'; +import { FlashloanablePool } from "./base/FlashloanablePool.sol"; +import { _roundToScale } from "./libraries/helpers/PoolHelper.sol"; -import { - _revertIfAuctionClearable, - _revertAfterExpiry -} from './libraries/helpers/RevertsHelper.sol'; +import { _revertIfAuctionClearable, _revertAfterExpiry } from "./libraries/helpers/RevertsHelper.sol"; -import { Maths } from './libraries/internal/Maths.sol'; -import { Deposits } from './libraries/internal/Deposits.sol'; -import { Loans } from './libraries/internal/Loans.sol'; +import { Maths } from "./libraries/internal/Maths.sol"; +import { Deposits } from "./libraries/internal/Deposits.sol"; +import { Loans } from "./libraries/internal/Loans.sol"; -import { LenderActions } from './libraries/external/LenderActions.sol'; -import { BorrowerActions } from './libraries/external/BorrowerActions.sol'; -import { SettlerActions } from './libraries/external/SettlerActions.sol'; -import { TakerActions } from './libraries/external/TakerActions.sol'; +import { LenderActions } from "./libraries/external/LenderActions.sol"; +import { BorrowerActions } from "./libraries/external/BorrowerActions.sol"; +import { SettlerActions } from "./libraries/external/SettlerActions.sol"; +import { TakerActions } from "./libraries/external/TakerActions.sol"; /** * @title ERC721 Pool contract @@ -60,7 +57,6 @@ import { TakerActions } from './libraries/external/TakerActions.sol'; * @dev Calls logic from external `PoolCommons`, `LenderActions`, `BorrowerActions` and `Auction` actions libraries. */ contract ERC721Pool is FlashloanablePool, IERC721Pool { - /*****************/ /*** Constants ***/ /*****************/ @@ -75,36 +71,35 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /// @dev Borrower `address => array` of tokenIds pledged by borrower mapping. mapping(address => uint256[]) public borrowerTokenIds; /// @dev Array of `tokenIds` in pool buckets (claimable from pool). - uint256[] public bucketTokenIds; + uint256[] public bucketTokenIds; /// @dev Mapping of `tokenIds` allowed in `NFT` Subset type pool. - mapping(uint256 => bool) internal tokenIdsAllowed_; + mapping(uint256 => bool) internal tokenIdsAllowed_; /****************************/ /*** Initialize Functions ***/ /****************************/ /// @inheritdoc IERC721Pool - function initialize( - uint256[] memory tokenIds_, - uint256 rate_ - ) external override { + function initialize(uint256[] memory tokenIds_, uint256 rate_) external override { if (isPoolInitialized) revert AlreadyInitialized(); - inflatorState.inflator = uint208(1e18); + inflatorState.inflator = uint208(1e18); inflatorState.inflatorUpdate = uint48(block.timestamp); - interestState.interestRate = uint208(rate_); + interestState.interestRate = uint208(rate_); interestState.interestRateUpdate = uint48(block.timestamp); uint256 noOfTokens = tokenIds_.length; if (noOfTokens != 0) { // add subset of tokenIds allowed in the pool - for (uint256 id = 0; id < noOfTokens;) { + for (uint256 id = 0; id < noOfTokens; ) { tokenIdsAllowed_[tokenIds_[id]] = true; - unchecked { ++id; } + unchecked { + ++id; + } } } @@ -168,8 +163,8 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { emit DrawDebtNFT(borrowerAddress_, amountToBorrow_, tokenIdsToPledge_, result.newLup); // update in memory pool state struct - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; + poolState.debt = result.poolDebt; + poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; // adjust t0Debt2ToCollateral ratio @@ -239,8 +234,8 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { emit RepayDebt(borrowerAddress_, result.quoteTokenToRepay, noOfNFTsToPull_, result.newLup); // update in memory pool state struct - poolState.debt = result.poolDebt; - poolState.t0Debt = result.t0PoolDebt; + poolState.debt = result.poolDebt; + poolState.t0Debt = result.t0PoolDebt; poolState.collateral = result.poolCollateral; // adjust t0Debt2ToCollateral ratio @@ -291,12 +286,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { _revertAfterExpiry(expiry_); PoolState memory poolState = _accruePoolInterest(); - bucketLP_ = LenderActions.addCollateral( - buckets, - deposits, - Maths.wad(tokenIds_.length), - index_ - ); + bucketLP_ = LenderActions.addCollateral(buckets, deposits, Maths.wad(tokenIds_.length), index_); emit AddCollateralNFT(msg.sender, index_, tokenIds_, bucketLP_); @@ -324,10 +314,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { PoolState memory poolState = _accruePoolInterest(); uint256 collateralAmount = Maths.wad(noOfNFTsToRemove_); - ( - collateralMerged_, - bucketLP_ - ) = LenderActions.mergeOrRemoveCollateral( + (collateralMerged_, bucketLP_) = LenderActions.mergeOrRemoveCollateral( buckets, deposits, removalIndexes_, @@ -363,12 +350,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { PoolState memory poolState = _accruePoolInterest(); removedAmount_ = Maths.wad(noOfNFTsToRemove_); - redeemedLP_ = LenderActions.removeCollateral( - buckets, - deposits, - removedAmount_, - index_ - ); + redeemedLP_ = LenderActions.removeCollateral(buckets, deposits, removedAmount_, index_); emit RemoveCollateral(msg.sender, index_, noOfNFTsToRemove_, redeemedLP_); @@ -393,11 +375,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { function settle( address borrowerAddress_, uint256 maxDepth_ - ) external nonReentrant override returns (uint256 collateralSettled_, bool isBorrowerSettled_) { + ) external override nonReentrant returns (uint256 collateralSettled_, bool isBorrowerSettled_) { PoolState memory poolState = _accruePoolInterest(); SettleParams memory params = SettleParams({ - borrower: borrowerAddress_, + borrower: borrowerAddress_, poolBalance: _getNormalizedPoolQuoteTokenBalance(), bucketDepth: maxDepth_ }); @@ -430,9 +412,9 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ function take( - address borrowerAddress_, - uint256 collateral_, - address callee_, + address borrowerAddress_, + uint256 collateral_, + address callee_, bytes calldata data_ ) external override nonReentrant returns (uint256 collateralTaken_) { PoolState memory poolState = _accruePoolInterest(); @@ -462,7 +444,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { if (data_.length != 0) { IERC721Taker(callee_).atomicSwapCallback( tokensTaken, - totalQuoteTokenAmount / poolState.quoteTokenScale, + totalQuoteTokenAmount / poolState.quoteTokenScale, data_ ); } @@ -487,12 +469,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @dev - decrement `poolBalances.pledgedCollateral` accumulator * @dev - update `t0Debt2ToCollateral` ratio only if auction settled, debt and collateral pre action are considered 0 */ - function bucketTake( - address borrowerAddress_, - bool depositTake_, - uint256 index_ - ) external override nonReentrant { - + function bucketTake(address borrowerAddress_, bool depositTake_, uint256 index_) external override nonReentrant { PoolState memory poolState = _accruePoolInterest(); TakeResult memory result = TakerActions.bucketTake( @@ -524,14 +501,11 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @param borrowerAddress_ Address of borrower. * @param borrowerCollateral_ Current borrower collateral to be rebalanced. */ - function _rebalanceTokens( - address borrowerAddress_, - uint256 borrowerCollateral_ - ) internal { + function _rebalanceTokens(address borrowerAddress_, uint256 borrowerCollateral_) internal { // rebalance borrower's collateral, transfer difference to floor collateral from borrower to pool claimable array uint256[] storage borrowerTokens = borrowerTokenIds[borrowerAddress_]; - uint256 noOfTokensPledged = borrowerTokens.length; + uint256 noOfTokensPledged = borrowerTokens.length; /* eg1. borrowerCollateral_ = 4.1, noOfTokensPledged = 6; noOfTokensToTransfer = 1 eg2. borrowerCollateral_ = 4, noOfTokensPledged = 6; noOfTokensToTransfer = 2 @@ -539,12 +513,14 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { uint256 borrowerCollateralRoundedUp = (borrowerCollateral_ + 1e18 - 1) / 1e18; uint256 noOfTokensToTransfer = noOfTokensPledged - borrowerCollateralRoundedUp; - for (uint256 i = 0; i < noOfTokensToTransfer;) { + for (uint256 i = 0; i < noOfTokensToTransfer; ) { uint256 tokenId = borrowerTokens[--noOfTokensPledged]; // start with moving the last token pledged by borrower - borrowerTokens.pop(); // remove token id from borrower - bucketTokenIds.push(tokenId); // add token id to pool claimable tokens + borrowerTokens.pop(); // remove token id from borrower + bucketTokenIds.push(tokenId); // add token id to pool claimable tokens - unchecked { ++i; } + unchecked { + ++i; + } } } @@ -554,18 +530,17 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { * @param poolTokens_ Array in pool that tracks `NFT` ids (could be tracking `NFT`s pledged by borrower or `NFT`s added by a lender in a specific bucket). * @param tokenIds_ Array of `NFT` token ids to transfer from `msg.sender` to pool. */ - function _transferFromSenderToPool( - uint256[] storage poolTokens_, - uint256[] calldata tokenIds_ - ) internal { - for (uint256 i = 0; i < tokenIds_.length;) { + function _transferFromSenderToPool(uint256[] storage poolTokens_, uint256[] calldata tokenIds_) internal { + for (uint256 i = 0; i < tokenIds_.length; ) { uint256 tokenId = tokenIds_[i]; if (!tokenIdsAllowed(tokenId)) revert OnlySubset(); poolTokens_.push(tokenId); _transferNFT(msg.sender, address(this), tokenId); - unchecked { ++i; } + unchecked { + ++i; + } } } @@ -586,7 +561,7 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { uint256 noOfNFTsInPool = poolTokens_.length; - for (uint256 i = 0; i < amountToRemove_;) { + for (uint256 i = 0; i < amountToRemove_; ) { uint256 tokenId = poolTokens_[--noOfNFTsInPool]; // start with transferring the last token added in bucket poolTokens_.pop(); @@ -594,7 +569,9 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { tokensTransferred[i] = tokenId; - unchecked { ++i; } + unchecked { + ++i; + } } return tokensTransferred; @@ -617,13 +594,12 @@ contract ERC721Pool is FlashloanablePool, IERC721Pool { /*******************************/ /// @inheritdoc IERC721PoolState - function totalBorrowerTokens(address borrower_) external view override returns(uint256) { + function totalBorrowerTokens(address borrower_) external view override returns (uint256) { return borrowerTokenIds[borrower_].length; } /// @inheritdoc IERC721PoolState - function totalBucketTokens() external view override returns(uint256) { + function totalBucketTokens() external view override returns (uint256) { return bucketTokenIds.length; } - } diff --git a/packages/ajna-contracts/contracts/ajna/ERC721PoolFactory.sol b/packages/ajna-contracts/contracts/ajna/ERC721PoolFactory.sol index 86b29593a..cfccb1b06 100644 --- a/packages/ajna-contracts/contracts/ajna/ERC721PoolFactory.sol +++ b/packages/ajna-contracts/contracts/ajna/ERC721PoolFactory.sol @@ -5,12 +5,12 @@ pragma solidity 0.8.18; import { ClonesWithImmutableArgs } from "./libs/clones-with-immutable-args/src/ClonesWithImmutableArgs.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { IERC721PoolFactory } from './interfaces/pool/erc721/IERC721PoolFactory.sol'; -import { IPoolFactory } from './interfaces/pool/IPoolFactory.sol'; -import { PoolType } from './interfaces/pool/IPool.sol'; +import { IERC721PoolFactory } from "./interfaces/pool/erc721/IERC721PoolFactory.sol"; +import { IPoolFactory } from "./interfaces/pool/IPoolFactory.sol"; +import { PoolType } from "./interfaces/pool/IPool.sol"; -import { ERC721Pool } from './ERC721Pool.sol'; -import { PoolDeployer } from './base/PoolDeployer.sol'; +import { ERC721Pool } from "./ERC721Pool.sol"; +import { PoolDeployer } from "./base/PoolDeployer.sol"; /** * @title ERC721 Pool Factory @@ -20,7 +20,6 @@ import { PoolDeployer } from './base/PoolDeployer.sol'; * @dev Reverts if pool is already created or if params to deploy new pool are invalid. */ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { - using ClonesWithImmutableArgs for address; /// @dev `ERC721` clonable pool contract used to deploy the new pool. @@ -53,7 +52,10 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { * @dev - `PoolCreated` */ function deployPool( - address collateral_, address quote_, uint256[] memory tokenIds_, uint256 interestRate_ + address collateral_, + address quote_, + uint256[] memory tokenIds_, + uint256 interestRate_ ) external canDeploy(collateral_, quote_, interestRate_) returns (address pool_) { bytes32 subsetHash = getNFTSubsetHash(tokenIds_); @@ -135,5 +137,4 @@ contract ERC721PoolFactory is PoolDeployer, IERC721PoolFactory { } } } - } diff --git a/packages/ajna-contracts/contracts/ajna/PoolInfoUtils.sol b/packages/ajna-contracts/contracts/ajna/PoolInfoUtils.sol index a2595afec..e8e570c62 100644 --- a/packages/ajna-contracts/contracts/ajna/PoolInfoUtils.sol +++ b/packages/ajna-contracts/contracts/ajna/PoolInfoUtils.sol @@ -2,9 +2,9 @@ pragma solidity 0.8.18; -import { Math } from '@openzeppelin/contracts/utils/math/Math.sol'; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { IPool, IERC20Token } from './interfaces/pool/IPool.sol'; +import { IPool, IERC20Token } from "./interfaces/pool/IPool.sol"; import { _auctionPrice, @@ -21,12 +21,12 @@ import { MAX_FENWICK_INDEX, MIN_PRICE, COLLATERALIZATION_FACTOR -} from './libraries/helpers/PoolHelper.sol'; +} from "./libraries/helpers/PoolHelper.sol"; -import { Buckets } from './libraries/internal/Buckets.sol'; -import { Maths } from './libraries/internal/Maths.sol'; +import { Buckets } from "./libraries/internal/Buckets.sol"; +import { Maths } from "./libraries/internal/Maths.sol"; -import { PoolCommons } from './libraries/external/PoolCommons.sol'; +import { PoolCommons } from "./libraries/external/PoolCommons.sol"; /** * @title Pool Info Utils contract @@ -34,7 +34,6 @@ import { PoolCommons } from './libraries/external/PoolCommons.sol'; * @dev Pool info is calculated using same helper functions / logic as in `Pool` contracts. */ contract PoolInfoUtils { - /** * @notice Exposes status of a liquidation auction. * @param ajnaPool_ Address of `Ajna` pool. @@ -46,27 +45,30 @@ contract PoolInfoUtils { * @return price_ Current price of the auction. (`WAD`) * @return neutralPrice_ Price at which bond holder is neither rewarded nor penalized. (`WAD`) */ - function auctionStatus(address ajnaPool_, address borrower_) + function auctionStatus( + address ajnaPool_, + address borrower_ + ) external view returns ( uint256 kickTime_, uint256 collateral_, uint256 debtToCover_, - bool isCollateralized_, + bool isCollateralized_, uint256 price_, uint256 neutralPrice_ ) { IPool pool = IPool(ajnaPool_); uint256 referencePrice; - ( , , , kickTime_, referencePrice, neutralPrice_, , , ) = pool.auctionInfo(borrower_); + (, , , kickTime_, referencePrice, neutralPrice_, , , ) = pool.auctionInfo(borrower_); if (kickTime_ != 0) { (debtToCover_, collateral_, ) = this.borrowerInfo(ajnaPool_, borrower_); - - (uint256 poolDebt,,,) = pool.debtInfo(); - uint256 lup_ = _priceAt(pool.depositIndex(poolDebt)); - isCollateralized_ = _isCollateralized(debtToCover_, collateral_, lup_, pool.poolType()); + + (uint256 poolDebt, , , ) = pool.debtInfo(); + uint256 lup_ = _priceAt(pool.depositIndex(poolDebt)); + isCollateralized_ = _isCollateralized(debtToCover_, collateral_, lup_, pool.poolType()); price_ = _auctionPrice(referencePrice, kickTime_); } @@ -80,29 +82,21 @@ contract PoolInfoUtils { * @return collateral_ Pledged collateral, including encumbered (`WAD`). * @return t0Np_ `Neutral price` (`WAD`). */ - function borrowerInfo(address ajnaPool_, address borrower_) - external - view - returns ( - uint256 debt_, - uint256 collateral_, - uint256 t0Np_ - ) - { + function borrowerInfo( + address ajnaPool_, + address borrower_ + ) external view returns (uint256 debt_, uint256 collateral_, uint256 t0Np_) { IPool pool = IPool(ajnaPool_); - ( - uint256 inflator, - uint256 lastInflatorUpdate - ) = pool.inflatorInfo(); + (uint256 inflator, uint256 lastInflatorUpdate) = pool.inflatorInfo(); - (uint256 interestRate,) = pool.interestRateInfo(); + (uint256 interestRate, ) = pool.interestRateInfo(); uint256 pendingInflator = PoolCommons.pendingInflator(inflator, lastInflatorUpdate, interestRate); uint256 t0Debt; uint256 npTpRatio; - (t0Debt, collateral_, npTpRatio) = pool.borrowerInfo(borrower_); + (t0Debt, collateral_, npTpRatio) = pool.borrowerInfo(borrower_); t0Np_ = collateral_ == 0 ? 0 : Math.mulDiv(t0Debt, npTpRatio, collateral_); @@ -120,7 +114,10 @@ contract PoolInfoUtils { * @return scale_ Lender interest multiplier (`WAD`). * @return exchangeRate_ The exchange rate of the bucket, in `WAD` units. */ - function bucketInfo(address ajnaPool_, uint256 index_) + function bucketInfo( + address ajnaPool_, + uint256 index_ + ) external view returns ( @@ -149,7 +146,9 @@ contract PoolInfoUtils { * @return pendingInflator_ Pending inflator in pool. * @return pendingInterestFactor_ Factor used to scale the inflator. */ - function poolLoansInfo(address ajnaPool_) + function poolLoansInfo( + address ajnaPool_ + ) external view returns ( @@ -165,14 +164,11 @@ contract PoolInfoUtils { poolSize_ = pool.depositSize(); (maxBorrower_, , loansCount_) = pool.loansInfo(); - ( - uint256 inflator, - uint256 inflatorUpdate - ) = pool.inflatorInfo(); + (uint256 inflator, uint256 inflatorUpdate) = pool.inflatorInfo(); (uint256 interestRate, ) = pool.interestRateInfo(); - pendingInflator_ = PoolCommons.pendingInflator(inflator, inflatorUpdate, interestRate); + pendingInflator_ = PoolCommons.pendingInflator(inflator, inflatorUpdate, interestRate); pendingInterestFactor_ = PoolCommons.pendingInterestFactor(interestRate, block.timestamp - inflatorUpdate); } @@ -186,31 +182,26 @@ contract PoolInfoUtils { * @return lup_ The price value of the current `Lowest Utilized Price` (LUP) bucket, in `WAD` units. * @return lupIndex_ The index of the current `Lowest Utilized Price` (`LUP`) bucket, in `WAD` units. */ - function poolPricesInfo(address ajnaPool_) + function poolPricesInfo( + address ajnaPool_ + ) external view - returns ( - uint256 hpb_, - uint256 hpbIndex_, - uint256 htp_, - uint256 htpIndex_, - uint256 lup_, - uint256 lupIndex_ - ) + returns (uint256 hpb_, uint256 hpbIndex_, uint256 htp_, uint256 htpIndex_, uint256 lup_, uint256 lupIndex_) { IPool pool = IPool(ajnaPool_); - (uint256 debt,,,) = pool.debtInfo(); + (uint256 debt, , , ) = pool.debtInfo(); hpbIndex_ = pool.depositIndex(1); - hpb_ = _priceAt(hpbIndex_); + hpb_ = _priceAt(hpbIndex_); - (, uint256 maxThresholdPrice,) = pool.loansInfo(); + (, uint256 maxThresholdPrice, ) = pool.loansInfo(); - htp_ = maxThresholdPrice; + htp_ = maxThresholdPrice; htpIndex_ = htp_ >= MIN_PRICE ? _indexOf(htp_) : MAX_FENWICK_INDEX; lupIndex_ = pool.depositIndex(debt); - lup_ = _priceAt(lupIndex_); + lup_ = _priceAt(lupIndex_); } /** @@ -221,11 +212,7 @@ contract PoolInfoUtils { */ function availableQuoteTokenAmount(address ajnaPool_) external view returns (uint256 amount_) { IPool pool = IPool(ajnaPool_); - ( - uint256 bondEscrowed, - uint256 unclaimedReserve, - , - ) = pool.reservesInfo(); + (uint256 bondEscrowed, uint256 unclaimedReserve, , ) = pool.reservesInfo(); uint256 escrowedAmounts = bondEscrowed + unclaimedReserve; uint256 poolBalance = IERC20Token(pool.quoteTokenAddress()).balanceOf(ajnaPool_) * pool.quoteTokenScale(); @@ -242,7 +229,9 @@ contract PoolInfoUtils { * @return auctionPrice_ Current price at which `1` quote token may be purchased, denominated in `Ajna`. * @return timeRemaining_ Seconds remaining before takes are no longer allowed. */ - function poolReservesInfo(address ajnaPool_) + function poolReservesInfo( + address ajnaPool_ + ) external view returns ( @@ -255,8 +244,8 @@ contract PoolInfoUtils { { IPool pool = IPool(ajnaPool_); - (,uint256 poolDebt,,) = pool.debtInfo(); - uint256 poolSize = pool.depositSize(); + (, uint256 poolDebt, , ) = pool.debtInfo(); + uint256 poolSize = pool.depositSize(); uint256 quoteTokenBalance = IERC20Token(pool.quoteTokenAddress()).balanceOf(ajnaPool_) * pool.quoteTokenScale(); @@ -267,17 +256,11 @@ contract PoolInfoUtils { reserves_ = poolDebt + quoteTokenBalance - poolSize - bondEscrowed - unclaimedReserve; } - claimableReserves_ = _claimableReserves( - poolDebt, - poolSize, - bondEscrowed, - unclaimedReserve, - quoteTokenBalance - ); + claimableReserves_ = _claimableReserves(poolDebt, poolSize, bondEscrowed, unclaimedReserve, quoteTokenBalance); claimableReservesRemaining_ = unclaimedReserve; - auctionPrice_ = _reserveAuctionPrice(auctionKickTime); - timeRemaining_ = 3 days - Maths.min(3 days, block.timestamp - auctionKickTime); + auctionPrice_ = _reserveAuctionPrice(auctionKickTime); + timeRemaining_ = 3 days - Maths.min(3 days, block.timestamp - auctionKickTime); } /** @@ -288,7 +271,9 @@ contract PoolInfoUtils { * @return poolActualUtilization_ The current pool actual utilization, in `WAD` units. * @return poolTargetUtilization_ The current pool Target utilization, in `WAD` units. */ - function poolUtilizationInfo(address ajnaPool_) + function poolUtilizationInfo( + address ajnaPool_ + ) external view returns ( @@ -300,8 +285,8 @@ contract PoolInfoUtils { { IPool pool = IPool(ajnaPool_); - (uint256 poolDebt,,,) = pool.debtInfo(); - uint256 poolCollateral = pool.pledgedCollateral(); + (uint256 poolDebt, , , ) = pool.debtInfo(); + uint256 poolCollateral = pool.pledgedCollateral(); (, , uint256 noOfLoans) = pool.loansInfo(); if (poolDebt != 0) poolMinDebtAmount_ = _minDebtAmount(poolDebt, noOfLoans); @@ -320,48 +305,36 @@ contract PoolInfoUtils { * the remainder accumulates in reserves. * @param ajnaPool_ Address of `Ajna` pool. * @return lenderInterestMargin_ Lender interest margin in pool. - */ - function lenderInterestMargin(address ajnaPool_) - external - view - returns (uint256 lenderInterestMargin_) - { + */ + function lenderInterestMargin(address ajnaPool_) external view returns (uint256 lenderInterestMargin_) { IPool pool = IPool(ajnaPool_); - uint256 utilization = pool.depositUtilization(); + uint256 utilization = pool.depositUtilization(); lenderInterestMargin_ = PoolCommons.lenderInterestMargin(utilization); } /** * @notice Returns bucket price for a given bucket index. - */ - function indexToPrice( - uint256 index_ - ) external pure returns (uint256) - { + */ + function indexToPrice(uint256 index_) external pure returns (uint256) { return _priceAt(index_); } /** * @notice Returns bucket index for a given bucket price. - */ - function priceToIndex( - uint256 price_ - ) external pure returns (uint256) - { + */ + function priceToIndex(uint256 price_) external pure returns (uint256) { return _indexOf(price_); } /** * @notice Returns current `LUP` for a given pool. - */ - function lup( - address ajnaPool_ - ) external view returns (uint256) { + */ + function lup(address ajnaPool_) external view returns (uint256) { IPool pool = IPool(ajnaPool_); - (uint256 debt,,,) = pool.debtInfo(); + (uint256 debt, , , ) = pool.debtInfo(); uint256 currentLupIndex = pool.depositIndex(debt); return _priceAt(currentLupIndex); @@ -369,23 +342,19 @@ contract PoolInfoUtils { /** * @notice Returns current `LUP` index for a given pool. - */ - function lupIndex( - address ajnaPool_ - ) external view returns (uint256) { + */ + function lupIndex(address ajnaPool_) external view returns (uint256) { IPool pool = IPool(ajnaPool_); - (uint256 debt,,,) = pool.debtInfo(); + (uint256 debt, , , ) = pool.debtInfo(); return pool.depositIndex(debt); } /** * @notice Returns current `HPB` for a given pool. - */ - function hpb( - address ajnaPool_ - ) external view returns (uint256) { + */ + function hpb(address ajnaPool_) external view returns (uint256) { IPool pool = IPool(ajnaPool_); uint256 hbpIndex = pool.depositIndex(1); @@ -395,10 +364,8 @@ contract PoolInfoUtils { /** * @notice Returns current `HPB` index for a given pool. - */ - function hpbIndex( - address ajnaPool_ - ) external view returns (uint256) { + */ + function hpbIndex(address ajnaPool_) external view returns (uint256) { IPool pool = IPool(ajnaPool_); return pool.depositIndex(1); @@ -406,10 +373,8 @@ contract PoolInfoUtils { /** * @notice Returns current `HTP` for a given pool. - */ - function htp( - address ajnaPool_ - ) external view returns (uint256 htp_) { + */ + function htp(address ajnaPool_) external view returns (uint256 htp_) { (, htp_, ) = IPool(ajnaPool_).loansInfo(); } @@ -418,10 +383,8 @@ contract PoolInfoUtils { * @notice Calculated as greater of the current annualized interest rate divided by `52` (one week of interest) or `5` bps. * @return Fee rate calculated from the pool interest rate. */ - function borrowFeeRate( - address ajnaPool_ - ) external view returns (uint256) { - (uint256 interestRate,) = IPool(ajnaPool_).interestRateInfo(); + function borrowFeeRate(address ajnaPool_) external view returns (uint256) { + (uint256 interestRate, ) = IPool(ajnaPool_).interestRateInfo(); return _borrowFeeRate(interestRate); } @@ -430,11 +393,9 @@ contract PoolInfoUtils { * @notice Calculated as current annualized rate divided by `365` (`24` hours of interest). * @return Fee rate calculated from the pool interest rate. */ - function unutilizedDepositFeeRate( - address ajnaPool_ - ) external view returns (uint256) { - (uint256 interestRate,) = IPool(ajnaPool_).interestRateInfo(); - return _depositFeeRate(interestRate); + function unutilizedDepositFeeRate(address ajnaPool_) external view returns (uint256) { + (uint256 interestRate, ) = IPool(ajnaPool_).interestRateInfo(); + return _depositFeeRate(interestRate); } /** @@ -449,14 +410,8 @@ contract PoolInfoUtils { uint256 index_ ) external view returns (uint256 quoteAmount_) { IPool pool = IPool(ajnaPool_); - (uint256 bucketLP_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); - quoteAmount_ = _lpToQuoteToken( - bucketLP_, - bucketCollateral, - bucketDeposit, - lp_, - _priceAt(index_) - ); + (uint256 bucketLP_, uint256 bucketCollateral, , uint256 bucketDeposit, ) = pool.bucketInfo(index_); + quoteAmount_ = _lpToQuoteToken(bucketLP_, bucketCollateral, bucketDeposit, lp_, _priceAt(index_)); } /** @@ -471,63 +426,47 @@ contract PoolInfoUtils { uint256 index_ ) external view returns (uint256 collateralAmount_) { IPool pool = IPool(ajnaPool_); - (uint256 bucketLP_, uint256 bucketCollateral , , uint256 bucketDeposit, ) = pool.bucketInfo(index_); - collateralAmount_ = _lpToCollateral( - bucketCollateral, - bucketLP_, - bucketDeposit, - lp_, - _priceAt(index_) - ); + (uint256 bucketLP_, uint256 bucketCollateral, , uint256 bucketDeposit, ) = pool.bucketInfo(index_); + collateralAmount_ = _lpToCollateral(bucketCollateral, bucketLP_, bucketDeposit, lp_, _priceAt(index_)); } } - /**********************/ - /*** Pool Utilities ***/ - /**********************/ +/**********************/ +/*** Pool Utilities ***/ +/**********************/ - /** - * @notice Calculates encumberance for a debt amount at a given price. - * @param debt_ The debt amount to calculate encumberance for. - * @param price_ The price to calculate encumberance at. - * @return encumberance_ Encumberance value. - */ - function _encumberance( - uint256 debt_, - uint256 price_ - ) pure returns (uint256 encumberance_) { - return price_ != 0 ? Maths.wdiv(Maths.wmul(COLLATERALIZATION_FACTOR , debt_), price_) : 0; - } +/** + * @notice Calculates encumberance for a debt amount at a given price. + * @param debt_ The debt amount to calculate encumberance for. + * @param price_ The price to calculate encumberance at. + * @return encumberance_ Encumberance value. + */ +function _encumberance(uint256 debt_, uint256 price_) pure returns (uint256 encumberance_) { + return price_ != 0 ? Maths.wdiv(Maths.wmul(COLLATERALIZATION_FACTOR, debt_), price_) : 0; +} - /** - * @notice Calculates collateralization for a given debt and collateral amounts, at a given price. - * @param debt_ The debt amount. - * @param collateral_ The collateral amount. - * @param price_ The price to calculate collateralization at. - * @return Collateralization value. `1 WAD` if debt amount is `0`. - */ - function _collateralization( - uint256 debt_, - uint256 collateral_, - uint256 price_ - ) pure returns (uint256) { - // cannot be undercollateralized if there is no debt - if (debt_ == 0) return 1e18; - - // borrower is undercollateralized when lup at MIN_PRICE - if (price_ == MIN_PRICE) return 0; - return Maths.wdiv(Maths.wmul(collateral_, price_), Maths.wmul(COLLATERALIZATION_FACTOR, debt_)); - } +/** + * @notice Calculates collateralization for a given debt and collateral amounts, at a given price. + * @param debt_ The debt amount. + * @param collateral_ The collateral amount. + * @param price_ The price to calculate collateralization at. + * @return Collateralization value. `1 WAD` if debt amount is `0`. + */ +function _collateralization(uint256 debt_, uint256 collateral_, uint256 price_) pure returns (uint256) { + // cannot be undercollateralized if there is no debt + if (debt_ == 0) return 1e18; - /** - * @notice Calculates target utilization for given `EMA` values. - * @param debtColEma_ The `EMA` of debt squared to collateral. - * @param lupt0DebtEma_ The `EMA` of `LUP * t0 debt`. - * @return Target utilization of the pool. - */ - function _targetUtilization( - uint256 debtColEma_, - uint256 lupt0DebtEma_ - ) pure returns (uint256) { - return (lupt0DebtEma_ != 0) ? Maths.wdiv(debtColEma_, lupt0DebtEma_) : Maths.WAD; - } + // borrower is undercollateralized when lup at MIN_PRICE + if (price_ == MIN_PRICE) return 0; + return Maths.wdiv(Maths.wmul(collateral_, price_), Maths.wmul(COLLATERALIZATION_FACTOR, debt_)); +} + +/** + * @notice Calculates target utilization for given `EMA` values. + * @param debtColEma_ The `EMA` of debt squared to collateral. + * @param lupt0DebtEma_ The `EMA` of `LUP * t0 debt`. + * @return Target utilization of the pool. + */ +function _targetUtilization(uint256 debtColEma_, uint256 lupt0DebtEma_) pure returns (uint256) { + return (lupt0DebtEma_ != 0) ? Maths.wdiv(debtColEma_, lupt0DebtEma_) : Maths.WAD; +} diff --git a/packages/ajna-contracts/contracts/ajna/PoolInfoUtilsMulticall.sol b/packages/ajna-contracts/contracts/ajna/PoolInfoUtilsMulticall.sol index 6d07f4cee..d32f738f2 100644 --- a/packages/ajna-contracts/contracts/ajna/PoolInfoUtilsMulticall.sol +++ b/packages/ajna-contracts/contracts/ajna/PoolInfoUtilsMulticall.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.18; import { PoolInfoUtils } from "./PoolInfoUtils.sol"; contract PoolInfoUtilsMulticall { - PoolInfoUtils public immutable poolInfoUtils; struct PoolPriceInfo { @@ -54,10 +53,13 @@ contract PoolInfoUtilsMulticall { * @return poolUtilizationInfo_ Pool utilization info struct * @return bucketInfo_ Bucket info struct */ - function poolDetailsAndBucketInfo(address ajnaPool_, uint256 bucketIndex_) + function poolDetailsAndBucketInfo( + address ajnaPool_, + uint256 bucketIndex_ + ) external view - returns( + returns ( PoolPriceInfo memory poolPriceInfo_, PoolReservesInfo memory poolReservesInfo_, PoolUtilizationInfo memory poolUtilizationInfo_, @@ -87,7 +89,7 @@ contract PoolInfoUtilsMulticall { poolUtilizationInfo_.poolActualUtilization, poolUtilizationInfo_.poolTargetUtilization ) = poolInfoUtils.poolUtilizationInfo(ajnaPool_); - + ( bucketInfo_.price, bucketInfo_.quoteTokens, @@ -105,19 +107,12 @@ contract PoolInfoUtilsMulticall { * @return borrowFeeRate Borrow fee rate calculated from the pool interest ra * @return depositFeeRate Deposit fee rate calculated from the pool interest rate */ - function poolRatesAndFees(address ajnaPool_) - external - view - returns - ( - uint256 lenderInterestMargin, - uint256 borrowFeeRate, - uint256 depositFeeRate - ) - { + function poolRatesAndFees( + address ajnaPool_ + ) external view returns (uint256 lenderInterestMargin, uint256 borrowFeeRate, uint256 depositFeeRate) { lenderInterestMargin = poolInfoUtils.lenderInterestMargin(ajnaPool_); - borrowFeeRate = poolInfoUtils.borrowFeeRate(ajnaPool_); - depositFeeRate = poolInfoUtils.unutilizedDepositFeeRate(ajnaPool_); + borrowFeeRate = poolInfoUtils.borrowFeeRate(ajnaPool_); + depositFeeRate = poolInfoUtils.unutilizedDepositFeeRate(ajnaPool_); } /** @@ -126,21 +121,24 @@ contract PoolInfoUtilsMulticall { * @param args_ Array of serialized function arguments of all read-only functions to called * @return results_ Array of result of all read-only function calls in bytes */ - function multicall(string[] calldata functionSignatures_, string[] calldata args_) external returns (bytes[] memory results_) { + function multicall( + string[] calldata functionSignatures_, + string[] calldata args_ + ) external returns (bytes[] memory results_) { uint256 currentIndex = 0; results_ = new bytes[](functionSignatures_.length); - for(uint256 i = 0; i < functionSignatures_.length; i++) { + for (uint256 i = 0; i < functionSignatures_.length; i++) { string[] memory parameters = _parseFunctionSignature(functionSignatures_[i]); uint256 noOfParams = parameters.length; bytes memory callData; if (noOfParams == 1) { if (keccak256(bytes(parameters[0])) == keccak256(bytes("uint256"))) { uint256 arg = _stringToUint(args_[currentIndex]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg); + callData = abi.encodeWithSignature(functionSignatures_[i], arg); } if (keccak256(bytes(parameters[0])) == keccak256(bytes("address"))) { address arg = _stringToAddress(args_[currentIndex]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg); + callData = abi.encodeWithSignature(functionSignatures_[i], arg); } } @@ -148,12 +146,12 @@ contract PoolInfoUtilsMulticall { if (keccak256(bytes(parameters[1])) == keccak256(bytes("uint256"))) { address arg1 = _stringToAddress(args_[currentIndex]); uint256 arg2 = _stringToUint(args_[currentIndex + 1]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2); + callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2); } if (keccak256(bytes(parameters[1])) == keccak256(bytes("address"))) { address arg1 = _stringToAddress(args_[currentIndex]); address arg2 = _stringToAddress(args_[currentIndex + 1]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2); + callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2); } } @@ -161,7 +159,7 @@ contract PoolInfoUtilsMulticall { address arg1 = _stringToAddress(args_[currentIndex]); uint256 arg2 = _stringToUint(args_[currentIndex + 1]); uint256 arg3 = _stringToUint(args_[currentIndex + 2]); - callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2, arg3); + callData = abi.encodeWithSignature(functionSignatures_[i], arg1, arg2, arg3); } currentIndex += noOfParams; @@ -183,7 +181,11 @@ contract PoolInfoUtilsMulticall { trimmedSignature_ = _trimFunctionName(signature_); // Check if the string starts with '(' and ends with ')' - if (bytes(trimmedSignature_).length >= 2 && bytes(trimmedSignature_)[0] == bytes("(")[0] && bytes(trimmedSignature_)[bytes(trimmedSignature_).length - 1] == bytes(")")[0]) { + if ( + bytes(trimmedSignature_).length >= 2 && + bytes(trimmedSignature_)[0] == bytes("(")[0] && + bytes(trimmedSignature_)[bytes(trimmedSignature_).length - 1] == bytes(")")[0] + ) { // Remove the first and last characters trimmedSignature_ = _substring(trimmedSignature_, 1, bytes(trimmedSignature_).length - 2); } @@ -249,7 +251,11 @@ contract PoolInfoUtilsMulticall { } // Extracts a substring from a given string - function _substring(string memory str_, uint256 startIndex_, uint256 endIndex_) internal pure returns (string memory) { + function _substring( + string memory str_, + uint256 startIndex_, + uint256 endIndex_ + ) internal pure returns (string memory) { require(startIndex_ <= endIndex_, "Invalid substring indices"); bytes memory strBytes = bytes(str_); bytes memory result = new bytes(endIndex_ - startIndex_ + 1); @@ -274,25 +280,27 @@ contract PoolInfoUtilsMulticall { // Converts a hexadecimal character to its decimal value function _hexCharToDecimal(uint8 character_) internal pure returns (uint8) { - if (bytes1(character_) >= bytes1('0') && bytes1(character_) <= bytes1('9')) { - return character_ - uint8(bytes1('0')); + if (bytes1(character_) >= bytes1("0") && bytes1(character_) <= bytes1("9")) { + return character_ - uint8(bytes1("0")); } - if (bytes1(character_) >= bytes1('a') && bytes1(character_) <= bytes1('f')) { - return 10 + character_ - uint8(bytes1('a')); + if (bytes1(character_) >= bytes1("a") && bytes1(character_) <= bytes1("f")) { + return 10 + character_ - uint8(bytes1("a")); } - if (bytes1(character_) >= bytes1('A') && bytes1(character_) <= bytes1('F')) { - return 10 + character_ - uint8(bytes1('A')); + if (bytes1(character_) >= bytes1("A") && bytes1(character_) <= bytes1("F")) { + return 10 + character_ - uint8(bytes1("A")); } return 0; } - + // Converts a hexadecimal string to bytes function _hexStringToBytes(string memory str_) internal pure returns (bytes memory bytesString_) { bytes memory strBytes = bytes(str_); require(strBytes.length % 2 == 0); // length must be even bytesString_ = new bytes(strBytes.length / 2); for (uint i = 1; i < strBytes.length / 2; ++i) { - bytesString_[i] = bytes1(_hexCharToDecimal(uint8(strBytes[2 * i])) * 16 + _hexCharToDecimal(uint8(strBytes[2 * i + 1]))); + bytesString_[i] = bytes1( + _hexCharToDecimal(uint8(strBytes[2 * i])) * 16 + _hexCharToDecimal(uint8(strBytes[2 * i + 1])) + ); } } @@ -305,4 +313,4 @@ contract PoolInfoUtilsMulticall { tempAddress_ := div(mload(add(add(strBytes, 0x20), 1)), 0x1000000000000000000000000) } } -} \ No newline at end of file +} diff --git a/packages/ajna-contracts/contracts/ajna/PositionManager.sol b/packages/ajna-contracts/contracts/ajna/PositionManager.sol index 5e32ec8cc..5a49fc905 100644 --- a/packages/ajna-contracts/contracts/ajna/PositionManager.sol +++ b/packages/ajna-contracts/contracts/ajna/PositionManager.sol @@ -2,30 +2,27 @@ pragma solidity 0.8.18; -import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; -import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import { EnumerableSet } from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; -import { Multicall } from '@openzeppelin/contracts/utils/Multicall.sol'; -import { ReentrancyGuard } from '@openzeppelin/contracts/security/ReentrancyGuard.sol'; -import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IPool } from './interfaces/pool/IPool.sol'; -import { IPositionManager } from './interfaces/position/IPositionManager.sol'; -import { IPositionManagerOwnerActions } from './interfaces/position/IPositionManagerOwnerActions.sol'; -import { IPositionManagerDerivedState } from './interfaces/position/IPositionManagerDerivedState.sol'; +import { IPool } from "./interfaces/pool/IPool.sol"; +import { IPositionManager } from "./interfaces/position/IPositionManager.sol"; +import { IPositionManagerOwnerActions } from "./interfaces/position/IPositionManagerOwnerActions.sol"; +import { IPositionManagerDerivedState } from "./interfaces/position/IPositionManagerDerivedState.sol"; -import { ERC20PoolFactory } from './ERC20PoolFactory.sol'; -import { ERC721PoolFactory } from './ERC721PoolFactory.sol'; +import { ERC20PoolFactory } from "./ERC20PoolFactory.sol"; +import { ERC721PoolFactory } from "./ERC721PoolFactory.sol"; -import { PermitERC721 } from './base/PermitERC721.sol'; +import { PermitERC721 } from "./base/PermitERC721.sol"; -import { - _lpToQuoteToken, - _priceAt -} from './libraries/helpers/PoolHelper.sol'; -import { tokenSymbol } from './libraries/helpers/SafeTokenNamer.sol'; +import { _lpToQuoteToken, _priceAt } from "./libraries/helpers/PoolHelper.sol"; +import { tokenSymbol } from "./libraries/helpers/SafeTokenNamer.sol"; -import { PositionNFTSVG } from './libraries/external/PositionNFTSVG.sol'; +import { PositionNFTSVG } from "./libraries/external/PositionNFTSVG.sol"; /** * @title Position Manager Contract @@ -39,7 +36,7 @@ import { PositionNFTSVG } from './libraries/external/PositionNFTSVG.sol'; */ contract PositionManager is PermitERC721, IPositionManager, Multicall, ReentrancyGuard { using EnumerableSet for EnumerableSet.UintSet; - using SafeERC20 for ERC20; + using SafeERC20 for ERC20; /***********************/ /*** State Variables ***/ @@ -56,7 +53,7 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc /******************/ /// @dev The `ERC20` pools factory contract, used to check if address is an `Ajna` pool. - ERC20PoolFactory private immutable erc20PoolFactory; + ERC20PoolFactory private immutable erc20PoolFactory; /// @dev The `ERC721` pools factory contract, used to check if address is an `Ajna` pool. ERC721PoolFactory private immutable erc721PoolFactory; @@ -66,23 +63,23 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc /// @dev Struct used for `moveLiquidity` function local vars. struct MoveLiquidityLocalVars { - uint256 bucketLP; // [WAD] amount of LP in from bucket + uint256 bucketLP; // [WAD] amount of LP in from bucket uint256 bucketCollateral; // [WAD] amount of collateral in from bucket - uint256 bankruptcyTime; // from bucket bankruptcy time - uint256 bucketDeposit; // [WAD] from bucket deposit - uint256 fromDepositTime; // lender deposit time in from bucket - uint256 fromLP; // [WAD] the LP memorialized in from position - uint256 toDepositTime; // lender deposit time in to bucket - uint256 maxQuote; // [WAD] max amount that can be moved from bucket - uint256 lpbAmountFrom; // [WAD] the LP redeemed from bucket - uint256 lpbAmountTo; // [WAD] the LP awarded in to bucket + uint256 bankruptcyTime; // from bucket bankruptcy time + uint256 bucketDeposit; // [WAD] from bucket deposit + uint256 fromDepositTime; // lender deposit time in from bucket + uint256 fromLP; // [WAD] the LP memorialized in from position + uint256 toDepositTime; // lender deposit time in to bucket + uint256 maxQuote; // [WAD] max amount that can be moved from bucket + uint256 lpbAmountFrom; // [WAD] the LP redeemed from bucket + uint256 lpbAmountTo; // [WAD] the LP awarded in to bucket } /// @dev Struct used for `memorializePositions` function Lenders Local vars struct LendersBucketLocalVars { - uint256 lpBalance; // Lender lp balance in a bucket + uint256 lpBalance; // Lender lp balance in a bucket uint256 depositTime; // Lender deposit time in a bucket - uint256 allowance; // Lp allowance for a bucket + uint256 allowance; // Lp allowance for a bucket } /*****************/ @@ -95,7 +92,6 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc * @param tokenId_ Id of positions `NFT`. */ modifier mayInteract(address pool_, uint256 tokenId_) { - // revert if token id is not a valid / minted id _requireMinted(tokenId_); @@ -116,11 +112,10 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc ERC20PoolFactory erc20Factory_, ERC721PoolFactory erc721Factory_ ) PermitERC721("Ajna Positions NFT-V1", "AJNA-V1-POS", "1") { - if ( - address(erc20Factory_) == address(0) || address(erc721Factory_) == address(0) - ) revert DeployWithZeroAddress(); + if (address(erc20Factory_) == address(0) || address(erc721Factory_) == address(0)) + revert DeployWithZeroAddress(); - erc20PoolFactory = erc20Factory_; + erc20PoolFactory = erc20Factory_; erc721PoolFactory = erc721Factory_; } @@ -142,10 +137,7 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc * @dev === Emit events === * @dev - `Burn` */ - function burn( - address pool_, - uint256 tokenId_ - ) external override mayInteract(pool_, tokenId_) { + function burn(address pool_, uint256 tokenId_) external override mayInteract(pool_, tokenId_) { // revert if trying to burn an positions token that still has liquidity if (positionTokens[tokenId_].positionIndexes.length() != 0) revert LiquidityNotRemoved(); @@ -181,11 +173,11 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc address pool_, uint256 tokenId_, uint256[] calldata indexes_ - ) external mayInteract(pool_, tokenId_) override { + ) external override mayInteract(pool_, tokenId_) { TokenInfo storage tokenInfo = positionTokens[tokenId_]; EnumerableSet.UintSet storage positionIndexes = tokenInfo.positionIndexes; - IPool pool = IPool(pool_); + IPool pool = IPool(pool_); address owner = ownerOf(tokenId_); LendersBucketLocalVars memory vars; @@ -228,7 +220,9 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc // save position in storage tokenInfo.positions[index] = position; - unchecked { ++i; } + unchecked { + ++i; + } } // update pool LP accounting and transfer ownership of LP to PositionManager contract @@ -294,8 +288,8 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc uint256 toIndex_, uint256 expiry_ ) external override nonReentrant mayInteract(pool_, tokenId_) { - TokenInfo storage tokenInfo = positionTokens[tokenId_]; - Position storage fromPosition = tokenInfo.positions[fromIndex_]; + TokenInfo storage tokenInfo = positionTokens[tokenId_]; + Position storage fromPosition = tokenInfo.positions[fromIndex_]; MoveLiquidityLocalVars memory vars; vars.fromDepositTime = fromPosition.depositTime; @@ -307,13 +301,10 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc // ensure bucketDeposit accounts for accrued interest IPool(pool_).updateInterest(); - // retrieve info of bucket from which liquidity is moved - ( - vars.bucketLP, - vars.bucketCollateral, - vars.bankruptcyTime, - vars.bucketDeposit, - ) = IPool(pool_).bucketInfo(fromIndex_); + // retrieve info of bucket from which liquidity is moved + (vars.bucketLP, vars.bucketCollateral, vars.bankruptcyTime, vars.bucketDeposit, ) = IPool(pool_).bucketInfo( + fromIndex_ + ); // check that from bucket hasn't gone bankrupt since memorialization if (vars.fromDepositTime <= vars.bankruptcyTime) revert BucketBankrupt(); @@ -328,10 +319,7 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc ); // move quote tokens in pool - ( - vars.lpbAmountFrom, - vars.lpbAmountTo, - ) = IPool(pool_).moveQuoteToken( + (vars.lpbAmountFrom, vars.lpbAmountTo, ) = IPool(pool_).moveQuoteToken( vars.maxQuote, fromIndex_, toIndex_, @@ -364,14 +352,7 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc (, vars.toDepositTime) = IPool(pool_).lenderInfo(toIndex_, address(this)); toPosition.depositTime = vars.toDepositTime; - emit MoveLiquidity( - ownerOf(tokenId_), - tokenId_, - fromIndex_, - toIndex_, - vars.lpbAmountFrom, - vars.lpbAmountTo - ); + emit MoveLiquidity(ownerOf(tokenId_), tokenId_, fromIndex_, toIndex_, vars.lpbAmountFrom, vars.lpbAmountTo); } /** @@ -426,7 +407,9 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc // remove LP tracked by position manager at bucket index delete tokenInfo.positions[index]; - unchecked { ++i; } + unchecked { + ++i; + } } address owner = ownerOf(tokenId_); @@ -449,18 +432,11 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc * @param subsetHash_ Factory's subset hash pool. * @return `True` if a valid `Ajna` pool, `false` otherwise. */ - function _isAjnaPool( - address pool_, - bytes32 subsetHash_ - ) internal view returns (bool) { + function _isAjnaPool(address pool_, bytes32 subsetHash_) internal view returns (bool) { address collateralAddress = IPool(pool_).collateralAddress(); - address quoteAddress = IPool(pool_).quoteTokenAddress(); + address quoteAddress = IPool(pool_).quoteTokenAddress(); - address erc20DeployedPoolAddress = erc20PoolFactory.deployedPools( - subsetHash_, - collateralAddress, - quoteAddress - ); + address erc20DeployedPoolAddress = erc20PoolFactory.deployedPools(subsetHash_, collateralAddress, quoteAddress); address erc721DeployedPoolAddress = erc721PoolFactory.deployedPools( subsetHash_, collateralAddress, @@ -492,19 +468,14 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc /**********************/ /// @inheritdoc IPositionManagerDerivedState - function getLP( - uint256 tokenId_, - uint256 index_ - ) external override view returns (uint256) { + function getLP(uint256 tokenId_, uint256 index_) external view override returns (uint256) { TokenInfo storage tokenInfo = positionTokens[tokenId_]; Position memory position = tokenInfo.positions[index_]; return _bucketBankruptAfterDeposit(IPool(tokenInfo.pool), index_, position.depositTime) ? 0 : position.lps; } /// @inheritdoc IPositionManagerDerivedState - function getPositionIndexes( - uint256 tokenId_ - ) external view override returns (uint256[] memory) { + function getPositionIndexes(uint256 tokenId_) external view override returns (uint256[] memory) { return positionTokens[tokenId_].positionIndexes.values(); } @@ -524,23 +495,21 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc if (!_bucketBankruptAfterDeposit(pool, indexes[i], tokenInfo.positions[indexes[i]].depositTime)) { filteredIndexes_[filteredIndexesLength++] = indexes[i]; } - unchecked { ++i; } + unchecked { + ++i; + } } // resize array - assembly { mstore(filteredIndexes_, filteredIndexesLength) } + assembly { + mstore(filteredIndexes_, filteredIndexesLength) + } } /// @inheritdoc IPositionManagerDerivedState - function getPositionInfo( - uint256 tokenId_, - uint256 index_ - ) external view override returns (uint256, uint256) { + function getPositionInfo(uint256 tokenId_, uint256 index_) external view override returns (uint256, uint256) { Position memory position = positionTokens[tokenId_].positions[index_]; - return ( - position.lps, - position.depositTime - ); + return (position.lps, position.depositTime); } /// @inheritdoc IPositionManagerDerivedState @@ -549,54 +518,42 @@ contract PositionManager is PermitERC721, IPositionManager, Multicall, Reentranc } /// @inheritdoc IPositionManagerDerivedState - function isAjnaPool( - address pool_, - bytes32 subsetHash_ - ) external override view returns (bool) { + function isAjnaPool(address pool_, bytes32 subsetHash_) external view override returns (bool) { return _isAjnaPool(pool_, subsetHash_); } /// @inheritdoc IPositionManagerDerivedState - function isPositionBucketBankrupt( - uint256 tokenId_, - uint256 index_ - ) external view override returns (bool) { + function isPositionBucketBankrupt(uint256 tokenId_, uint256 index_) external view override returns (bool) { TokenInfo storage tokenInfo = positionTokens[tokenId_]; return _bucketBankruptAfterDeposit(IPool(tokenInfo.pool), index_, tokenInfo.positions[index_].depositTime); } /// @inheritdoc IPositionManagerDerivedState - function isIndexInPosition( - uint256 tokenId_, - uint256 index_ - ) external override view returns (bool) { + function isIndexInPosition(uint256 tokenId_, uint256 index_) external view override returns (bool) { return positionTokens[tokenId_].positionIndexes.contains(index_); } /** * @dev See {IERC721Metadata-tokenURI}. */ - function tokenURI( - uint256 tokenId_ - ) public view override returns (string memory) { + function tokenURI(uint256 tokenId_) public view override returns (string memory) { if (!_exists(tokenId_)) revert NoToken(); TokenInfo storage tokenInfo = positionTokens[tokenId_]; address pool = tokenInfo.pool; address collateralTokenAddress = IPool(pool).collateralAddress(); - address quoteTokenAddress = IPool(pool).quoteTokenAddress(); + address quoteTokenAddress = IPool(pool).quoteTokenAddress(); PositionNFTSVG.ConstructTokenURIParams memory params = PositionNFTSVG.ConstructTokenURIParams({ collateralTokenSymbol: tokenSymbol(collateralTokenAddress), - quoteTokenSymbol: tokenSymbol(quoteTokenAddress), - tokenId: tokenId_, - pool: pool, - owner: ownerOf(tokenId_), - indexes: tokenInfo.positionIndexes.values() + quoteTokenSymbol: tokenSymbol(quoteTokenAddress), + tokenId: tokenId_, + pool: pool, + owner: ownerOf(tokenId_), + indexes: tokenInfo.positionIndexes.values() }); return PositionNFTSVG.constructTokenURI(params); } - } diff --git a/packages/ajna-contracts/contracts/ajna/RewardsManager.sol b/packages/ajna-contracts/contracts/ajna/RewardsManager.sol index f46dfa7ce..4baacc790 100644 --- a/packages/ajna-contracts/contracts/ajna/RewardsManager.sol +++ b/packages/ajna-contracts/contracts/ajna/RewardsManager.sol @@ -2,28 +2,24 @@ pragma solidity 0.8.18; -import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol'; -import { SafeERC20 } from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IPool } from './interfaces/pool/IPool.sol'; -import { IPositionManager } from './interfaces/position/IPositionManager.sol'; -import { IPositionManagerOwnerActions } from './interfaces/position/IPositionManagerOwnerActions.sol'; +import { IPool } from "./interfaces/pool/IPool.sol"; +import { IPositionManager } from "./interfaces/position/IPositionManager.sol"; +import { IPositionManagerOwnerActions } from "./interfaces/position/IPositionManagerOwnerActions.sol"; import { IRewardsManager, IRewardsManagerOwnerActions, IRewardsManagerState, IRewardsManagerDerivedState -} from './interfaces/rewards/IRewardsManager.sol'; -import { - StakeInfo, - BucketState, - PoolRewardsInfo -} from './interfaces/rewards/IRewardsManagerState.sol'; +} from "./interfaces/rewards/IRewardsManager.sol"; +import { StakeInfo, BucketState, PoolRewardsInfo } from "./interfaces/rewards/IRewardsManagerState.sol"; -import { PositionManager } from './PositionManager.sol'; +import { PositionManager } from "./PositionManager.sol"; -import { Maths } from './libraries/internal/Maths.sol'; +import { Maths } from "./libraries/internal/Maths.sol"; /** * @title Rewards (staking) Manager contract @@ -36,7 +32,6 @@ import { Maths } from './libraries/internal/Maths.sol'; * - `unstake` token */ contract RewardsManager is IRewardsManager { - using SafeERC20 for IERC20; /*****************/ @@ -97,9 +92,7 @@ contract RewardsManager is IRewardsManager { * @param positionManager_ Address of the PositionManager contract. */ constructor(address ajnaToken_, IPositionManager positionManager_) { - if ( - ajnaToken_ == address(0) || address(positionManager_) == address(0) - ) revert DeployWithZeroAddress(); + if (ajnaToken_ == address(0) || address(positionManager_) == address(0)) revert DeployWithZeroAddress(); ajnaToken = ajnaToken_; positionManager = positionManager_; @@ -117,11 +110,7 @@ contract RewardsManager is IRewardsManager { * @dev === Emit events === * @dev - `ClaimRewards` */ - function claimRewards( - uint256 tokenId_, - uint256 epochToClaim_, - uint256 minAmount_ - ) external override { + function claimRewards(uint256 tokenId_, uint256 epochToClaim_, uint256 minAmount_) external override { StakeInfo storage stakeInfo = stakes[tokenId_]; if (msg.sender != stakeInfo.owner) revert NotOwnerOfDeposit(); @@ -139,10 +128,7 @@ contract RewardsManager is IRewardsManager { ); // transfer rewards to claimer, ensuring amount is not below specified min amount - _transferAjnaRewards({ - transferAmount_: rewardsEarned, - minAmount_: minAmount_ - }); + _transferAjnaRewards({ transferAmount_: rewardsEarned, minAmount_: minAmount_ }); } /** @@ -152,16 +138,14 @@ contract RewardsManager is IRewardsManager { * @dev === Emit events === * @dev - `Stake` */ - function stake( - uint256 tokenId_ - ) external override { + function stake(uint256 tokenId_) external override { address ajnaPool = positionManager.poolKey(tokenId_); // check that msg.sender is owner of tokenId if (IERC721(address(positionManager)).ownerOf(tokenId_) != msg.sender) revert NotOwnerOfDeposit(); StakeInfo storage stakeInfo = stakes[tokenId_]; - stakeInfo.owner = msg.sender; + stakeInfo.owner = msg.sender; stakeInfo.ajnaPool = ajnaPool; uint256 curBurnEpoch = IPool(ajnaPool).currentBurnEpoch(); @@ -186,7 +170,9 @@ contract RewardsManager is IRewardsManager { bucketState.rateAtStakeTime = IPool(ajnaPool).bucketExchangeRate(bucketId); // iterations are bounded by array length (which is itself bounded), preventing overflow / underflow - unchecked { ++i; } + unchecked { + ++i; + } } emit Stake(msg.sender, ajnaPool, tokenId_); @@ -195,17 +181,10 @@ contract RewardsManager is IRewardsManager { IERC721(address(positionManager)).transferFrom(msg.sender, address(this), tokenId_); // calculate rewards for updating exchange rates, if any - uint256 updateReward = _updateBucketExchangeRates( - ajnaPool, - curBurnEpoch, - positionIndexes - ); + uint256 updateReward = _updateBucketExchangeRates(ajnaPool, curBurnEpoch, positionIndexes); // transfer bucket update rewards to sender even if there's not enough balance for entire amount - _transferAjnaRewards({ - transferAmount_: updateReward, - minAmount_: 0 - }); + _transferAjnaRewards({ transferAmount_: updateReward, minAmount_: 0 }); } /** @@ -216,29 +195,19 @@ contract RewardsManager is IRewardsManager { * @dev - `ClaimRewards` * @dev - `Unstake` */ - function unstake( - uint256 tokenId_ - ) external override { - _unstake({ - tokenId_: tokenId_, - claimRewards_: true - }); + function unstake(uint256 tokenId_) external override { + _unstake({ tokenId_: tokenId_, claimRewards_: true }); } - /** + /** * @inheritdoc IRewardsManagerOwnerActions * @dev === Revert on === * @dev not owner `NotOwnerOfDeposit()` * @dev === Emit events === * @dev - `Unstake` */ - function emergencyUnstake( - uint256 tokenId_ - ) external override { - _unstake({ - tokenId_: tokenId_, - claimRewards_: false - }); + function emergencyUnstake(uint256 tokenId_) external override { + _unstake({ tokenId_: tokenId_, claimRewards_: false }); } /** @@ -257,10 +226,7 @@ contract RewardsManager is IRewardsManager { updateReward = _updateBucketExchangeRates(pool_, IPool(pool_).currentBurnEpoch(), indexes_); // transfer bucket update rewards to sender even if there's not enough balance for entire amount - _transferAjnaRewards({ - transferAmount_: updateReward, - minAmount_: 0 - }); + _transferAjnaRewards({ transferAmount_: updateReward, minAmount_: 0 }); } /*******************************/ @@ -272,36 +238,25 @@ contract RewardsManager is IRewardsManager { uint256 tokenId_, uint256 epochToClaim_ ) external view override returns (uint256 rewards_) { - address ajnaPool = stakes[tokenId_].ajnaPool; + address ajnaPool = stakes[tokenId_].ajnaPool; uint256 lastClaimedEpoch = stakes[tokenId_].lastClaimedEpoch; - uint256 stakingEpoch = stakes[tokenId_].stakingEpoch; + uint256 stakingEpoch = stakes[tokenId_].stakingEpoch; uint256[] memory positionIndexes = positionManager.getPositionIndexesFiltered(tokenId_); // iterate through all burn periods to calculate and claim rewards for (uint256 epoch = lastClaimedEpoch; epoch < epochToClaim_; ) { + rewards_ += _calculateNextEpochRewards(tokenId_, epoch, stakingEpoch, ajnaPool, positionIndexes); - rewards_ += _calculateNextEpochRewards( - tokenId_, - epoch, - stakingEpoch, - ajnaPool, - positionIndexes - ); - - unchecked { ++epoch; } + unchecked { + ++epoch; + } } } /// @inheritdoc IRewardsManagerState - function getStakeInfo( - uint256 tokenId_ - ) external view override returns (address, address, uint256) { - return ( - stakes[tokenId_].owner, - stakes[tokenId_].ajnaPool, - stakes[tokenId_].lastClaimedEpoch - ); + function getStakeInfo(uint256 tokenId_) external view override returns (address, address, uint256) { + return (stakes[tokenId_].owner, stakes[tokenId_].ajnaPool, stakes[tokenId_].lastClaimedEpoch); } /// @inheritdoc IRewardsManagerState @@ -325,18 +280,12 @@ contract RewardsManager is IRewardsManager { } /// @inheritdoc IRewardsManagerState - function getRewardsClaimed( - address pool_, - uint256 epoch_ - ) external view override returns (uint256) { + function getRewardsClaimed(address pool_, uint256 epoch_) external view override returns (uint256) { return poolRewardsInfo[pool_].rewardsClaimed[epoch_]; } /// @inheritdoc IRewardsManagerState - function getUpdateRewardsClaimed( - address pool_, - uint256 epoch_ - ) external view override returns (uint256) { + function getUpdateRewardsClaimed(address pool_, uint256 epoch_) external view override returns (uint256) { return poolRewardsInfo[pool_].updateBucketRewardsClaimed[epoch_]; } @@ -355,9 +304,9 @@ contract RewardsManager is IRewardsManager { uint256 tokenId_, uint256 epochToClaim_ ) internal returns (uint256 rewards_) { - address ajnaPool = stakes[tokenId_].ajnaPool; + address ajnaPool = stakes[tokenId_].ajnaPool; uint256 lastClaimedEpoch = stakes[tokenId_].lastClaimedEpoch; - uint256 stakingEpoch = stakes[tokenId_].stakingEpoch; + uint256 stakingEpoch = stakes[tokenId_].stakingEpoch; mapping(uint256 => uint256) storage rewardsClaimed = poolRewardsInfo[ajnaPool].rewardsClaimed; @@ -365,7 +314,6 @@ contract RewardsManager is IRewardsManager { // iterate through all burn periods to calculate and claim rewards for (uint256 epoch = lastClaimedEpoch; epoch < epochToClaim_; ) { - uint256 nextEpochRewards = _calculateNextEpochRewards( tokenId_, epoch, @@ -376,7 +324,9 @@ contract RewardsManager is IRewardsManager { rewards_ += nextEpochRewards; - unchecked { ++epoch; } + unchecked { + ++epoch; + } // update epoch token claim trackers rewardsClaimed[epoch] += nextEpochRewards; @@ -401,7 +351,6 @@ contract RewardsManager is IRewardsManager { address ajnaPool_, uint256[] memory positionIndexes_ ) internal view returns (uint256 epochRewards_) { - uint256 nextEpoch = epoch_ + 1; uint256 claimedRewardsInNextEpoch = poolRewardsInfo[ajnaPool_].rewardsClaimed[nextEpoch]; uint256 bucketIndex; @@ -416,11 +365,9 @@ contract RewardsManager is IRewardsManager { uint256 bucketRate; if (epoch_ != stakingEpoch_) { - // if staked in a previous epoch then use the initial exchange rate of epoch bucketRate = poolRewardsInfo[ajnaPool_].bucketExchangeRates[bucketIndex][epoch_]; } else { - // if staked during the epoch then use the bucket rate at the time of staking bucketRate = bucketSnapshot.rateAtStakeTime; } @@ -433,17 +380,14 @@ contract RewardsManager is IRewardsManager { bucketSnapshot.lpsAtStakeTime, bucketRate ); - unchecked { ++i; } + unchecked { + ++i; + } } // calculate and accumulate rewards if interest earned if (interestEarned != 0) { - epochRewards_ = _calculateNewRewards( - ajnaPool_, - interestEarned, - nextEpoch, - claimedRewardsInNextEpoch - ); + epochRewards_ = _calculateNewRewards(ajnaPool_, interestEarned, nextEpoch, claimedRewardsInNextEpoch); } } @@ -463,19 +407,15 @@ contract RewardsManager is IRewardsManager { uint256 bucketLP_, uint256 exchangeRate_ ) internal view returns (uint256 interestEarned_) { - if (exchangeRate_ != 0) { - uint256 nextExchangeRate = poolRewardsInfo[pool_].bucketExchangeRates[bucketIndex_][nextEventEpoch_]; // calculate interest earned only if next exchange rate is higher than current exchange rate if (nextExchangeRate > exchangeRate_) { - // calculate the equivalent amount of quote tokens given the stakes lp balance, // and the exchange rate at the next and current burn events interestEarned_ = Maths.wmul(nextExchangeRate - exchangeRate_, bucketLP_); } - } } @@ -502,19 +442,17 @@ contract RewardsManager is IRewardsManager { ) = _getEpochInfo(ajnaPool_, nextEpoch_); // calculate rewards earned - newRewards_ = totalInterestEarnedInPeriod == 0 ? 0 : Maths.floorWdiv( - Maths.wmul( - Maths.wmul(interestEarned_, totalBurnedInPeriod), - REWARD_FACTOR - ), - totalInterestEarnedInPeriod - ); + newRewards_ = totalInterestEarnedInPeriod == 0 + ? 0 + : Maths.floorWdiv( + Maths.wmul(Maths.wmul(interestEarned_, totalBurnedInPeriod), REWARD_FACTOR), + totalInterestEarnedInPeriod + ); uint256 rewardsCapped = Maths.wmul(REWARD_CAP, totalBurnedInPeriod); // Check rewards claimed - check that less than 80% of the tokens for a given burn event have been claimed. if (rewardsClaimedInEpoch_ + newRewards_ > rewardsCapped) { - // set claim reward to difference between cap and reward newRewards_ = rewardsClaimedInEpoch_ > rewardsCapped ? 0 : rewardsCapped - rewardsClaimedInEpoch_; } @@ -536,7 +474,6 @@ contract RewardsManager is IRewardsManager { bool validateEpoch_, address ajnaPool_ ) internal returns (uint256 rewardsEarned_) { - // revert if higher epoch to claim than current burn epoch if (validateEpoch_ && epochToClaim_ > curBurnEpoch_) revert EpochNotAvailable(); @@ -551,18 +488,9 @@ contract RewardsManager is IRewardsManager { rewardsEarned_ += _calculateAndClaimStakingRewards(tokenId_, epochToClaim_); } - uint256[] memory burnEpochsClaimed = _getBurnEpochsClaimed( - stakeInfo_.lastClaimedEpoch, - epochToClaim_ - ); + uint256[] memory burnEpochsClaimed = _getBurnEpochsClaimed(stakeInfo_.lastClaimedEpoch, epochToClaim_); - emit ClaimRewards( - msg.sender, - ajnaPool_, - tokenId_, - burnEpochsClaimed, - rewardsEarned_ - ); + emit ClaimRewards(msg.sender, ajnaPool_, tokenId_, burnEpochsClaimed, rewardsEarned_); // update last interaction burn event stakeInfo_.lastClaimedEpoch = uint96(epochToClaim_); @@ -607,37 +535,32 @@ contract RewardsManager is IRewardsManager { uint256 curBurnEpoch_, uint256[] memory indexes_ ) internal returns (uint256 updatedRewards_) { - // retrieve epoch values used to determine if updater receives rewards - ( - uint256 curBurnTime, - uint256 totalBurnedInEpoch, - uint256 totalInterestEarned - ) = _getEpochInfo(pool_, curBurnEpoch_); + (uint256 curBurnTime, uint256 totalBurnedInEpoch, uint256 totalInterestEarned) = _getEpochInfo( + pool_, + curBurnEpoch_ + ); // Update exchange rates without reward if first epoch or if the epoch does not have burned tokens associated with it if (totalBurnedInEpoch == 0) { uint256 noOfIndexes = indexes_.length; for (uint256 i = 0; i < noOfIndexes; ) { - _updateBucketExchangeRate( - pool_, - indexes_[i], - curBurnEpoch_ - ); + _updateBucketExchangeRate(pool_, indexes_[i], curBurnEpoch_); // iterations are bounded by array length (which is itself bounded), preventing overflow / underflow - unchecked { ++i; } + unchecked { + ++i; + } } - } - else { + } else { if (block.timestamp <= curBurnTime + UPDATE_PERIOD) { - mapping(uint256 => uint256) storage updateRewardsClaimed = poolRewardsInfo[pool_].updateBucketRewardsClaimed; + mapping(uint256 => uint256) storage updateRewardsClaimed = poolRewardsInfo[pool_] + .updateBucketRewardsClaimed; // update exchange rates and calculate rewards if tokens were burned and within allowed time period uint256 noOfIndexes = indexes_.length; for (uint256 i = 0; i < noOfIndexes; ) { - // calculate rewards earned for updating bucket exchange rate updatedRewards_ += _updateBucketExchangeRateAndCalculateRewards( pool_, @@ -648,10 +571,12 @@ contract RewardsManager is IRewardsManager { ); // iterations are bounded by array length (which is itself bounded), preventing overflow / underflow - unchecked { ++i; } + unchecked { + ++i; + } } - uint256 rewardsCap = Maths.wmul(UPDATE_CAP, totalBurnedInEpoch); + uint256 rewardsCap = Maths.wmul(UPDATE_CAP, totalBurnedInEpoch); uint256 rewardsClaimedInEpoch = updateRewardsClaimed[curBurnEpoch_]; // update total tokens claimed for updating bucket exchange rates tracker @@ -678,13 +603,11 @@ contract RewardsManager is IRewardsManager { * @param bucketIndex_ Bucket index to update exchange rate. * @param burnEpoch_ Current burn epoch of the pool. */ - function _updateBucketExchangeRate( - address pool_, - uint256 bucketIndex_, - uint256 burnEpoch_ - ) internal { + function _updateBucketExchangeRate(address pool_, uint256 bucketIndex_, uint256 burnEpoch_) internal { // cache storage pointer for reduced gas - mapping(uint256 => uint256) storage _bucketExchangeRates = poolRewardsInfo[pool_].bucketExchangeRates[bucketIndex_]; + mapping(uint256 => uint256) storage _bucketExchangeRates = poolRewardsInfo[pool_].bucketExchangeRates[ + bucketIndex_ + ]; uint256 burnExchangeRate = _bucketExchangeRates[burnEpoch_]; // update bucket exchange rate at epoch only if it wasn't previously updated @@ -713,7 +636,9 @@ contract RewardsManager is IRewardsManager { uint256 interestEarned_ ) internal returns (uint256 rewards_) { // cache storage pointer for reduced gas - mapping(uint256 => uint256) storage _bucketExchangeRates = poolRewardsInfo[pool_].bucketExchangeRates[bucketIndex_]; + mapping(uint256 => uint256) storage _bucketExchangeRates = poolRewardsInfo[pool_].bucketExchangeRates[ + bucketIndex_ + ]; uint256 burnExchangeRate = _bucketExchangeRates[burnEpoch_]; // update bucket exchange rate at epoch only if it wasn't previously updated @@ -729,23 +654,21 @@ contract RewardsManager is IRewardsManager { // skip reward calculation if update at the previous epoch was missed and if exchange rate decreased due to bad debt // prevents excess rewards from being provided from using a 0 value as an input to the interestFactor calculation below. if (prevBucketExchangeRate != 0 && prevBucketExchangeRate < curBucketExchangeRate) { - // retrieve current deposit of the bucket (, , , uint256 bucketDeposit, ) = IPool(pool_).bucketInfo(bucketIndex_); uint256 burnFactor = Maths.wmul(totalBurned_, bucketDeposit); // calculate rewards earned for updating bucket exchange rate - rewards_ = interestEarned_ == 0 ? 0 : Maths.wdiv( - Maths.wmul( - UPDATE_CLAIM_REWARD, + rewards_ = interestEarned_ == 0 + ? 0 + : Maths.wdiv( Maths.wmul( - burnFactor, - curBucketExchangeRate - prevBucketExchangeRate - ) - ), - Maths.wmul(curBucketExchangeRate, interestEarned_) - ); + UPDATE_CLAIM_REWARD, + Maths.wmul(burnFactor, curBucketExchangeRate - prevBucketExchangeRate) + ), + Maths.wmul(curBucketExchangeRate, interestEarned_) + ); } } } @@ -785,7 +708,9 @@ contract RewardsManager is IRewardsManager { for (uint256 i = 0; i < noOfIndexes; ) { delete stakeInfo.snapshot[positionIndexes[i]]; // reset BucketState struct for current position - unchecked { ++i; } + unchecked { + ++i; + } } // remove recorded stake info @@ -795,10 +720,7 @@ contract RewardsManager is IRewardsManager { // gracefully unstake, transfer rewards to claimer ensuring entire amount if (claimRewards_) { - _transferAjnaRewards({ - transferAmount_: rewardsEarned, - minAmount_: rewardsEarned - }); + _transferAjnaRewards({ transferAmount_: rewardsEarned, minAmount_: rewardsEarned }); } // transfer LP NFT from contract to sender @@ -829,43 +751,33 @@ contract RewardsManager is IRewardsManager { } } - /**********************/ - /** Rewards Utilities */ - /**********************/ - - /** - * @notice Retrieve the total ajna tokens burned and total interest earned over a given epoch. - * @param pool_ Address of the `Ajna` pool to retrieve accumulators of. - * @param epoch_ time window used to identify time between Ajna burn events (kickReserve and takeReserve actions). - * @return currentBurnTime_ timestamp of the latest burn event. - * @return tokensBurned_ total `Ajna` tokens burned in epoch. - * @return interestEarned_ total interest earned in epoch. - */ - function _getEpochInfo( - address pool_, - uint256 epoch_ - ) view returns (uint256 currentBurnTime_, uint256 tokensBurned_, uint256 interestEarned_) { - - // 0 epoch won't have any ajna burned or interest associated with it - if (epoch_ != 0) { - - uint256 totalInterestLatest; - uint256 totalBurnedLatest; - - ( - currentBurnTime_, - totalInterestLatest, - totalBurnedLatest - ) = IPool(pool_).burnInfo(epoch_); - - ( - , - uint256 totalInterestPrev, - uint256 totalBurnedPrev - ) = IPool(pool_).burnInfo(epoch_ - 1); +/**********************/ +/** Rewards Utilities */ +/**********************/ - // calculate total tokens burned and interest earned in epoch - tokensBurned_ = totalBurnedLatest != 0 ? totalBurnedLatest - totalBurnedPrev : 0; - interestEarned_ = totalInterestLatest != 0 ? totalInterestLatest - totalInterestPrev : 0; - } +/** + * @notice Retrieve the total ajna tokens burned and total interest earned over a given epoch. + * @param pool_ Address of the `Ajna` pool to retrieve accumulators of. + * @param epoch_ time window used to identify time between Ajna burn events (kickReserve and takeReserve actions). + * @return currentBurnTime_ timestamp of the latest burn event. + * @return tokensBurned_ total `Ajna` tokens burned in epoch. + * @return interestEarned_ total interest earned in epoch. + */ +function _getEpochInfo( + address pool_, + uint256 epoch_ +) view returns (uint256 currentBurnTime_, uint256 tokensBurned_, uint256 interestEarned_) { + // 0 epoch won't have any ajna burned or interest associated with it + if (epoch_ != 0) { + uint256 totalInterestLatest; + uint256 totalBurnedLatest; + + (currentBurnTime_, totalInterestLatest, totalBurnedLatest) = IPool(pool_).burnInfo(epoch_); + + (, uint256 totalInterestPrev, uint256 totalBurnedPrev) = IPool(pool_).burnInfo(epoch_ - 1); + + // calculate total tokens burned and interest earned in epoch + tokensBurned_ = totalBurnedLatest != 0 ? totalBurnedLatest - totalBurnedPrev : 0; + interestEarned_ = totalInterestLatest != 0 ? totalInterestLatest - totalInterestPrev : 0; } +} From 073e8694162d91d303603c40805df40eec750686 Mon Sep 17 00:00:00 2001 From: halaprix Date: Mon, 4 Dec 2023 12:30:37 +0100 Subject: [PATCH 4/6] chore: deploy to base --- .../ajna/ajna-actions/AjnaProxyActions.sol | 1632 +++++++++-------- packages/ajna-contracts/hardhat.config.ts | 7 + .../ajna-contracts/scripts/common/config.ts | 52 +- .../scripts/common/deployment.utils.ts | 7 +- .../ajna-contracts/scripts/common/types.ts | 2 +- packages/ajna-contracts/scripts/deploy-apa.ts | 7 +- .../configs/base.conf.ts | 10 +- .../configs/hardhat.conf.ts | 4 +- .../configs/mainnet.conf.ts | 4 +- .../configs/test/mainnet.conf.ts | 4 +- 10 files changed, 909 insertions(+), 820 deletions(-) diff --git a/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol b/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol index f6b1e5801..a1c6cfefe 100644 --- a/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol +++ b/packages/ajna-contracts/contracts/ajna/ajna-actions/AjnaProxyActions.sol @@ -13,473 +13,531 @@ import { IRewardsManager } from "../interfaces/rewards/IRewardsManager.sol"; import { IAccountGuard } from "../../interfaces/dpm/IAccountGuard.sol"; import { IWETH } from "../../interfaces/tokens/IWETH.sol"; -import { console } from "hardhat/console.sol"; interface IAjnaProxyActions { - function positionManager() external view returns (IPositionManager); + function positionManager() external view returns (IPositionManager); - function rewardsManager() external view returns (IRewardsManager); + function rewardsManager() external view returns (IRewardsManager); - function ARC() external view returns (address); + function ARC() external view returns (address); } contract AjnaProxyActions is IAjnaProxyActions { - IAjnaPoolUtilsInfo public immutable poolInfoUtils; - IERC20 public immutable ajnaToken; - address public immutable WETH; - address public immutable GUARD; - address public immutable deployer; - string public constant ajnaVersion = "Ajna_rc10"; - IAjnaProxyActions public immutable self; - IPositionManager public positionManager; - IRewardsManager public rewardsManager; - address public ARC; - - using SafeERC20 for IERC20; - - constructor(IAjnaPoolUtilsInfo _poolInfoUtils, IERC20 _ajnaToken, address _WETH, address _GUARD) { - require(address(_poolInfoUtils) != address(0), "apa/zero-address"); - require(address(_ajnaToken) != address(0), "apa/zero-address"); - require(_WETH != address(0), "apa/zero-address"); - require(_GUARD != address(0), "apa/zero-address"); - poolInfoUtils = _poolInfoUtils; - ajnaToken = _ajnaToken; - WETH = _WETH; - GUARD = _GUARD; - self = this; - deployer = msg.sender; - } - - function initialize(address _positionManager, address _rewardsManager, address _ARC) external { - require(address(_positionManager) != address(0), "apa/zero-address"); - require(address(_rewardsManager) != address(0), "apa/zero-address"); - require(_ARC != address(0), "apa/zero-address"); - require(msg.sender == deployer, "apa/not-deployer"); - require( - address(positionManager) == address(0) && address(rewardsManager) == address(0) && ARC == address(0), - "apa/already-initialized" - ); - positionManager = IPositionManager(_positionManager); - rewardsManager = IRewardsManager(_rewardsManager); - ARC = _ARC; - } - - /** - * @dev Emitted once an Operation has completed execution - * @param name Name of the operation - **/ - event ProxyActionsOperation(bytes32 indexed name); - - /** - * @dev Emitted when a new position is created - * @param proxyAddress The address of the newly created position proxy contract - * @param protocol The name of the protocol associated with the position - * @param positionType The type of position being created (e.g. borrow or earn) - * @param collateralToken The address of the collateral token being used for the position - * @param debtToken The address of the debt token being used for the position - **/ - event CreatePosition( - address indexed proxyAddress, - string protocol, - string positionType, - address collateralToken, - address debtToken + IAjnaPoolUtilsInfo public immutable poolInfoUtils; + IERC20 public immutable ajnaToken; + address public immutable WETH; + address public immutable GUARD; + address public immutable deployer; + string public constant ajnaVersion = "Ajna_rc10"; + IAjnaProxyActions public immutable self; + IPositionManager public positionManager; + IRewardsManager public rewardsManager; + address public ARC; + + using SafeERC20 for IERC20; + + constructor(IAjnaPoolUtilsInfo _poolInfoUtils, IERC20 _ajnaToken, address _WETH, address _GUARD) { + require(address(_poolInfoUtils) != address(0), "apa/pool-info-utils-zero-address"); + require(address(_ajnaToken) != address(0) || block.chainid != 1, "apa/ajna-token-zero-address"); + require(_WETH != address(0), "apa/weth-zero-address"); + require(_GUARD != address(0), "apa/guard-zero-address"); + poolInfoUtils = _poolInfoUtils; + ajnaToken = _ajnaToken; + WETH = _WETH; + GUARD = _GUARD; + self = this; + deployer = msg.sender; + } + + function initialize(address _positionManager, address _rewardsManager, address _ARC) external { + require(address(_positionManager) != address(0), "apa/zero-address"); + require(address(_rewardsManager) != address(0), "apa/zero-address"); + require(_ARC != address(0), "apa/zero-address"); + require(msg.sender == deployer, "apa/not-deployer"); + require( + address(positionManager) == address(0) && + address(rewardsManager) == address(0) && + ARC == address(0), + "apa/already-initialized" ); + positionManager = IPositionManager(_positionManager); + rewardsManager = IRewardsManager(_rewardsManager); + ARC = _ARC; + } + + /** + * @dev Emitted once an Operation has completed execution + * @param name Name of the operation + **/ + event ProxyActionsOperation(bytes32 indexed name); + + /** + * @dev Emitted when a new position is created + * @param proxyAddress The address of the newly created position proxy contract + * @param protocol The name of the protocol associated with the position + * @param positionType The type of position being created (e.g. borrow or earn) + * @param collateralToken The address of the collateral token being used for the position + * @param debtToken The address of the debt token being used for the position + **/ + event CreatePosition( + address indexed proxyAddress, + string protocol, + string positionType, + address collateralToken, + address debtToken + ); + + function _send(address token, uint256 amount) internal { + if (token == WETH) { + IWETH(WETH).withdraw(amount); + payable(msg.sender).transfer(amount); + } else { + IERC20(token).safeTransfer(msg.sender, amount); + } + } + + function _pull(address token, uint256 amount) internal { + if (token == WETH) { + IWETH(WETH).deposit{ value: amount }(); + } else { + IERC20(token).safeTransferFrom(msg.sender, address(this), amount); + } + } + + function _stampLoan(IERC20Pool pool, bool stamploanEnabled) internal { + if (stamploanEnabled) { + pool.stampLoan(); + } + } + + /** + * @notice Mints and empty NFT for the user, NFT is bound to a specific pool. + * @param pool Address of the Ajana Pool. + * @return tokenId - id of the minted NFT + */ + function _mintNft(IERC20Pool pool) internal returns (uint256 tokenId) { + address _ARC = self.ARC(); + tokenId = self.positionManager().mint( + address(pool), + address(this), + keccak256("ERC20_NON_SUBSET_HASH") + ); + if (!IAccountGuard(GUARD).canCall(address(this), _ARC)) { + IAccountGuard(GUARD).permit(_ARC, address(this), true); + } + } + + /** + * @notice Redeem bucket from NFT + * @param price Price of the momorialized bucket + * @param tokenId Nft ID + * @param pool Pool address + */ + + function _redeemPosition(uint256 price, uint256 tokenId, address pool) internal { + uint256 index = convertPriceToIndex(price); + uint256[] memory indexes = new uint256[](1); + indexes[0] = index; + address[] memory addresses = new address[](1); + addresses[0] = address(self.positionManager()); + IERC20Pool(pool).approveLPTransferors(addresses); + self.positionManager().redeemPositions(address(pool), tokenId, indexes); + } + + /** + * @notice Memorialize bucket in NFT + * @param price Price of the momorialized bucket + * @param tokenId Nft ID + */ + function _memorializeLiquidity(uint256 price, uint256 tokenId, IERC20Pool pool) internal { + uint256 index = convertPriceToIndex(price); + + (uint256 lpCount, ) = IERC20Pool(pool).lenderInfo(index, address(this)); + uint256[] memory indexes = new uint256[](1); + indexes[0] = index; + uint256[] memory lpCounts = new uint256[](1); + lpCounts[0] = lpCount; + IERC20Pool(pool).increaseLPAllowance(address(self.positionManager()), indexes, lpCounts); + self.positionManager().memorializePositions(address(pool), tokenId, indexes); + IERC721(address(self.positionManager())).approve(address(self.rewardsManager()), tokenId); + } + + /** + * @notice Move LP from one bucket to another while momorialzied in NFT, requires unstaked NFT + * @param oldPrice Old price of the momorialized bucket + * @param newPrice New price of the momorialized bucket + * @param tokenId Nft ID + * @param pool Pool address + */ + function _moveLiquidity( + uint256 oldPrice, + uint256 newPrice, + uint256 tokenId, + address pool + ) internal { + uint256 oldIndex = convertPriceToIndex(oldPrice); + uint256 newIndex = convertPriceToIndex(newPrice); + + self.positionManager().moveLiquidity(pool, tokenId, oldIndex, newIndex, block.timestamp + 1); + IERC721(address(self.positionManager())).approve(address(self.rewardsManager()), tokenId); + } + + /** + * @notice Called internally to add an amount of credit at a specified price bucket. + * @param pool Address of the Ajana Pool. + * @param amount The maximum amount of quote token to be moved by a lender. + * @param price The price the bucket to which the quote tokens will be added. + * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance + * @dev 1WBTC = 16,990.23 USDC translates to: 16990230 + */ + function _supplyQuote( + IERC20Pool pool, + uint256 amount, + uint256 price + ) internal returns (uint256 bucketLP, uint256 addedAmount) { + address debtToken = pool.quoteTokenAddress(); + _pull(debtToken, amount); + uint256 index = convertPriceToIndex(price); + IERC20(debtToken).forceApprove(address(pool), amount); + (bucketLP, addedAmount) = pool.addQuoteToken( + amount * pool.quoteTokenScale(), + index, + block.timestamp + 1 + ); + } + + /** + * @notice Called internally to move max amount of credit from a specified price bucket to another specified price bucket. + * @param pool Address of the Ajana Pool. + * @param oldPrice The price of the bucket from which the quote tokens will be removed. + * @param newPrice The price of the bucket to which the quote tokens will be added. + */ + function _moveQuote(IERC20Pool pool, uint256 oldPrice, uint256 newPrice) internal { + uint256 oldIndex = convertPriceToIndex(oldPrice); + pool.moveQuoteToken( + type(uint256).max, + oldIndex, + convertPriceToIndex(newPrice), + block.timestamp + 1 + ); + } + + /** + * @notice Called internally to remove an amount of credit at a specified price bucket. + * @param pool Address of the Ajana Pool. + * @param amount The maximum amount of quote token to be moved by a lender. + * @param price The price the bucket to which the quote tokens will be added. + * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance + * @dev 1WBTC = 16,990.23 USDC translates to: 16990230 + */ + function _withdrawQuote(IERC20Pool pool, uint256 amount, uint256 price) internal { + address debtToken = pool.quoteTokenAddress(); + uint256 index = convertPriceToIndex(price); + uint256 withdrawnBalanceWAD; + if (amount == type(uint256).max) { + (withdrawnBalanceWAD, ) = pool.removeQuoteToken(type(uint256).max, index); + } else { + (withdrawnBalanceWAD, ) = pool.removeQuoteToken((amount * pool.quoteTokenScale()), index); + } + uint256 withdrawnBalance = _roundToScale(withdrawnBalanceWAD, pool.quoteTokenScale()) / + pool.quoteTokenScale(); + _send(debtToken, withdrawnBalance); + } + + /** + * @notice Reclaims collateral from liquidated bucket + * @param pool Address of the Ajana Pool. + * @param price Price of the bucket to redeem. + */ + function _removeCollateral( + IERC20Pool pool, + uint256 price + ) internal returns (uint256 withdrawnBalance) { + address collateralToken = pool.collateralAddress(); + uint256 index = convertPriceToIndex(price); + (uint256 withdrawnBalanceWAD, ) = pool.removeCollateral(type(uint256).max, index); + withdrawnBalance = + _roundToScale(withdrawnBalanceWAD, pool.collateralScale()) / + pool.collateralScale(); + _send(collateralToken, withdrawnBalance); + } + + // BORROWER ACTIONS + + /** + * @notice Deposit collateral + * @param pool Pool address + * @param collateralAmount Amount of collateral to deposit + * @param price Price of the bucket + * @param stamploanEnabled Whether to stamp the loan or not + */ + function depositCollateral( + IERC20Pool pool, + uint256 collateralAmount, + uint256 price, + bool stamploanEnabled + ) public payable { + address collateralToken = pool.collateralAddress(); + _pull(collateralToken, collateralAmount); + + uint256 index = convertPriceToIndex(price); + IERC20(collateralToken).forceApprove(address(pool), collateralAmount); + pool.drawDebt(address(this), 0, index, collateralAmount * pool.collateralScale()); + _stampLoan(pool, stamploanEnabled); + emit ProxyActionsOperation("AjnaDeposit"); + } + + /** + * @notice Draw debt + * @param pool Pool address + * @param debtAmount Amount of debt to draw + * @param price Price of the bucket + */ + function drawDebt(IERC20Pool pool, uint256 debtAmount, uint256 price) public { + address debtToken = pool.quoteTokenAddress(); + uint256 index = convertPriceToIndex(price); + + pool.drawDebt(address(this), debtAmount * pool.quoteTokenScale(), index, 0); + _send(debtToken, debtAmount); + emit ProxyActionsOperation("AjnaBorrow"); + } + + /** + * @notice Deposit collateral and draw debt + * @param pool Pool address + * @param debtAmount Amount of debt to draw + * @param collateralAmount Amount of collateral to deposit + * @param price Price of the bucket + */ + function depositCollateralAndDrawDebt( + IERC20Pool pool, + uint256 debtAmount, + uint256 collateralAmount, + uint256 price + ) public { + address debtToken = pool.quoteTokenAddress(); + address collateralToken = pool.collateralAddress(); + uint256 index = convertPriceToIndex(price); + _pull(collateralToken, collateralAmount); + IERC20(collateralToken).forceApprove(address(pool), collateralAmount); + pool.drawDebt( + address(this), + debtAmount * pool.quoteTokenScale(), + index, + collateralAmount * pool.collateralScale() + ); + _send(debtToken, debtAmount); + emit ProxyActionsOperation("AjnaDepositBorrow"); + } + + /** + * @notice Deposit collateral and draw debt + * @param pool Pool address + * @param debtAmount Amount of debt to borrow + * @param collateralAmount Amount of collateral to deposit + * @param price Price of the bucket + * @param stamploanEnabled Whether to stamp the loan or not + */ + function depositAndDraw( + IERC20Pool pool, + uint256 debtAmount, + uint256 collateralAmount, + uint256 price, + bool stamploanEnabled + ) public payable { + if (debtAmount > 0 && collateralAmount > 0) { + depositCollateralAndDrawDebt(pool, debtAmount, collateralAmount, price); + } else if (debtAmount > 0) { + drawDebt(pool, debtAmount, price); + } else if (collateralAmount > 0) { + depositCollateral(pool, collateralAmount, price, stamploanEnabled); + } + } + + /** + * @notice Repay debt + * @param pool Pool address + * @param amount Amount of debt to repay + * @param stamploanEnabled Whether to stamp the loan or not + */ + function repayDebt(IERC20Pool pool, uint256 amount, bool stamploanEnabled) public payable { + address debtToken = pool.quoteTokenAddress(); + _pull(debtToken, amount); + IERC20(debtToken).forceApprove(address(pool), amount); + (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); + uint256 repaidAmountWAD = pool.repayDebt( + address(this), + amount * pool.quoteTokenScale(), + 0, + address(this), + lupIndex_ + ); + _stampLoan(pool, stamploanEnabled); + uint256 repaidAmount = _roundUpToScale(repaidAmountWAD, pool.quoteTokenScale()) / + pool.quoteTokenScale(); + uint256 leftoverBalance = amount - repaidAmount; + if (leftoverBalance > 0) { + _send(debtToken, leftoverBalance); + } + IERC20(debtToken).safeApprove(address(pool), 0); + emit ProxyActionsOperation("AjnaRepay"); + } + + /** + * @notice Withdraw collateral + * @param pool Pool address + * @param amount Amount of collateral to withdraw + */ + function withdrawCollateral(IERC20Pool pool, uint256 amount) public { + address collateralToken = pool.collateralAddress(); + (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); + pool.repayDebt(address(this), 0, amount * pool.collateralScale(), address(this), lupIndex_); + _send(collateralToken, amount); + emit ProxyActionsOperation("AjnaWithdraw"); + } + + /** + * @notice Repay debt and withdraw collateral + * @param pool Pool address + * @param debtAmount Amount of debt to repay + * @param collateralAmount Amount of collateral to withdraw + */ + function repayDebtAndWithdrawCollateral( + IERC20Pool pool, + uint256 debtAmount, + uint256 collateralAmount + ) public { + address debtToken = pool.quoteTokenAddress(); + address collateralToken = pool.collateralAddress(); + _pull(debtToken, debtAmount); + IERC20(debtToken).forceApprove(address(pool), debtAmount); + (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); + uint256 repaidAmountWAD = pool.repayDebt( + address(this), + debtAmount * pool.quoteTokenScale(), + collateralAmount * pool.collateralScale(), + address(this), + lupIndex_ + ); + _send(collateralToken, collateralAmount); + uint256 repaidAmount = _roundUpToScale(repaidAmountWAD, pool.quoteTokenScale()) / + pool.quoteTokenScale(); + uint256 quoteLeftoverBalance = debtAmount - repaidAmount; + if (quoteLeftoverBalance > 0) { + _send(debtToken, quoteLeftoverBalance); + } + IERC20(debtToken).safeApprove(address(pool), 0); + emit ProxyActionsOperation("AjnaRepayWithdraw"); + } + + /** + * @notice Repay debt and withdraw collateral for msg.sender + * @param pool Pool address + * @param debtAmount Amount of debt to repay + * @param collateralAmount Amount of collateral to withdraw + * @param stamploanEnabled Whether to stamp the loan or not + */ + function repayWithdraw( + IERC20Pool pool, + uint256 debtAmount, + uint256 collateralAmount, + bool stamploanEnabled + ) external payable { + if (debtAmount > 0 && collateralAmount > 0) { + repayDebtAndWithdrawCollateral(pool, debtAmount, collateralAmount); + } else if (debtAmount > 0) { + repayDebt(pool, debtAmount, stamploanEnabled); + } else if (collateralAmount > 0) { + withdrawCollateral(pool, collateralAmount); + } + } + + /** + * @notice Repay debt and close position for msg.sender + * @param pool Pool address + */ + function repayAndClose(IERC20Pool pool) public payable { + address collateralToken = pool.collateralAddress(); + address debtToken = pool.quoteTokenAddress(); + + (uint256 debt, uint256 collateral, ) = poolInfoUtils.borrowerInfo(address(pool), address(this)); + uint256 debtPlusBuffer = _roundUpToScale(debt, pool.quoteTokenScale()); + uint256 amountDebt = debtPlusBuffer / pool.quoteTokenScale(); + _pull(debtToken, amountDebt); + + IERC20(debtToken).forceApprove(address(pool), amountDebt); + (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); + pool.repayDebt(address(this), debtPlusBuffer, collateral, address(this), lupIndex_); + + uint256 amountCollateral = collateral / pool.collateralScale(); + _send(collateralToken, amountCollateral); + IERC20(debtToken).safeApprove(address(pool), 0); + emit ProxyActionsOperation("AjnaRepayAndClose"); + } + + /** + * @notice Open position for msg.sender + * @param pool Pool address + * @param debtAmount Amount of debt to borrow + * @param collateralAmount Amount of collateral to deposit + * @param price Price of the bucket + */ + function openPosition( + IERC20Pool pool, + uint256 debtAmount, + uint256 collateralAmount, + uint256 price + ) public payable { + emit CreatePosition( + address(this), + ajnaVersion, + "Borrow", + pool.collateralAddress(), + pool.quoteTokenAddress() + ); + depositAndDraw(pool, debtAmount, collateralAmount, price, false); + } + + /** + * @notice Open Earn position for msg.sender + * @param pool Pool address + * @param depositAmount Amount of debt to borrow + * @param price Price of the bucket + */ + function openEarnPosition(IERC20Pool pool, uint256 depositAmount, uint256 price) public payable { + emit CreatePosition( + address(this), + ajnaVersion, + "Earn", + pool.collateralAddress(), + pool.quoteTokenAddress() + ); + _supplyQuote(pool, depositAmount, price); + emit ProxyActionsOperation("AjnaSupplyQuote"); + } + + /** + * @notice Open Earn (with NFT) position for msg.sender + * @param pool Pool address + * @param depositAmount Amount of debt to borrow + * @param price Price of the bucket + */ + function openEarnPositionNft( + IERC20Pool pool, + uint256 depositAmount, + uint256 price + ) public payable { + emit CreatePosition( + address(this), + ajnaVersion, + "Earn", + pool.collateralAddress(), + pool.quoteTokenAddress() + ); + supplyQuoteMintNftAndStake(pool, depositAmount, price); + } - function _send(address token, uint256 amount) internal { - if (token == WETH) { - IWETH(WETH).withdraw(amount); - payable(msg.sender).transfer(amount); - } else { - IERC20(token).safeTransfer(msg.sender, amount); - } - } - - function _pull(address token, uint256 amount) internal { - if (token == WETH) { - IWETH(WETH).deposit{ value: amount }(); - } else { - IERC20(token).safeTransferFrom(msg.sender, address(this), amount); - } - } - - function _stampLoan(IERC20Pool pool, bool stamploanEnabled) internal { - if (stamploanEnabled) { - pool.stampLoan(); - } - } - - /** - * @notice Mints and empty NFT for the user, NFT is bound to a specific pool. - * @param pool Address of the Ajana Pool. - * @return tokenId - id of the minted NFT - */ - function _mintNft(IERC20Pool pool) internal returns (uint256 tokenId) { - address _ARC = self.ARC(); - tokenId = self.positionManager().mint(address(pool), address(this), keccak256("ERC20_NON_SUBSET_HASH")); - if (!IAccountGuard(GUARD).canCall(address(this), _ARC)) { - IAccountGuard(GUARD).permit(_ARC, address(this), true); - } - } - - /** - * @notice Redeem bucket from NFT - * @param price Price of the momorialized bucket - * @param tokenId Nft ID - * @param pool Pool address - */ - - function _redeemPosition(uint256 price, uint256 tokenId, address pool) internal { - uint256 index = convertPriceToIndex(price); - uint256[] memory indexes = new uint256[](1); - indexes[0] = index; - address[] memory addresses = new address[](1); - addresses[0] = address(self.positionManager()); - IERC20Pool(pool).approveLPTransferors(addresses); - self.positionManager().redeemPositions(address(pool), tokenId, indexes); - } - - /** - * @notice Memorialize bucket in NFT - * @param price Price of the momorialized bucket - * @param tokenId Nft ID - */ - function _memorializeLiquidity(uint256 price, uint256 tokenId, IERC20Pool pool) internal { - uint256 index = convertPriceToIndex(price); - - (uint256 lpCount, ) = IERC20Pool(pool).lenderInfo(index, address(this)); - uint256[] memory indexes = new uint256[](1); - indexes[0] = index; - uint256[] memory lpCounts = new uint256[](1); - lpCounts[0] = lpCount; - IERC20Pool(pool).increaseLPAllowance(address(self.positionManager()), indexes, lpCounts); - self.positionManager().memorializePositions(address(pool), tokenId, indexes); - IERC721(address(self.positionManager())).approve(address(self.rewardsManager()), tokenId); - } - - /** - * @notice Move LP from one bucket to another while momorialzied in NFT, requires unstaked NFT - * @param oldPrice Old price of the momorialized bucket - * @param newPrice New price of the momorialized bucket - * @param tokenId Nft ID - * @param pool Pool address - */ - function _moveLiquidity(uint256 oldPrice, uint256 newPrice, uint256 tokenId, address pool) internal { - uint256 oldIndex = convertPriceToIndex(oldPrice); - uint256 newIndex = convertPriceToIndex(newPrice); - - self.positionManager().moveLiquidity(pool, tokenId, oldIndex, newIndex, block.timestamp + 1); - IERC721(address(self.positionManager())).approve(address(self.rewardsManager()), tokenId); - } - - /** - * @notice Called internally to add an amount of credit at a specified price bucket. - * @param pool Address of the Ajana Pool. - * @param amount The maximum amount of quote token to be moved by a lender. - * @param price The price the bucket to which the quote tokens will be added. - * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance - * @dev 1WBTC = 16,990.23 USDC translates to: 16990230 - */ - function _supplyQuote( - IERC20Pool pool, - uint256 amount, - uint256 price - ) internal returns (uint256 bucketLP, uint256 addedAmount) { - address debtToken = pool.quoteTokenAddress(); - _pull(debtToken, amount); - uint256 index = convertPriceToIndex(price); - IERC20(debtToken).forceApprove(address(pool), amount); - (bucketLP, addedAmount) = pool.addQuoteToken(amount * pool.quoteTokenScale(), index, block.timestamp + 1); - } - - /** - * @notice Called internally to move max amount of credit from a specified price bucket to another specified price bucket. - * @param pool Address of the Ajana Pool. - * @param oldPrice The price of the bucket from which the quote tokens will be removed. - * @param newPrice The price of the bucket to which the quote tokens will be added. - */ - function _moveQuote(IERC20Pool pool, uint256 oldPrice, uint256 newPrice) internal { - uint256 oldIndex = convertPriceToIndex(oldPrice); - pool.moveQuoteToken(type(uint256).max, oldIndex, convertPriceToIndex(newPrice), block.timestamp + 1); - } - - /** - * @notice Called internally to remove an amount of credit at a specified price bucket. - * @param pool Address of the Ajana Pool. - * @param amount The maximum amount of quote token to be moved by a lender. - * @param price The price the bucket to which the quote tokens will be added. - * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance - * @dev 1WBTC = 16,990.23 USDC translates to: 16990230 - */ - function _withdrawQuote(IERC20Pool pool, uint256 amount, uint256 price) internal { - address debtToken = pool.quoteTokenAddress(); - uint256 index = convertPriceToIndex(price); - uint256 withdrawnBalanceWAD; - if (amount == type(uint256).max) { - (withdrawnBalanceWAD, ) = pool.removeQuoteToken(type(uint256).max, index); - } else { - (withdrawnBalanceWAD, ) = pool.removeQuoteToken((amount * pool.quoteTokenScale()), index); - } - uint256 withdrawnBalance = _roundToScale(withdrawnBalanceWAD, pool.quoteTokenScale()) / pool.quoteTokenScale(); - _send(debtToken, withdrawnBalance); - } - - /** - * @notice Reclaims collateral from liquidated bucket - * @param pool Address of the Ajana Pool. - * @param price Price of the bucket to redeem. - */ - function _removeCollateral(IERC20Pool pool, uint256 price) internal returns (uint256 withdrawnBalance) { - address collateralToken = pool.collateralAddress(); - uint256 index = convertPriceToIndex(price); - (uint256 withdrawnBalanceWAD, ) = pool.removeCollateral(type(uint256).max, index); - withdrawnBalance = _roundToScale(withdrawnBalanceWAD, pool.collateralScale()) / pool.collateralScale(); - _send(collateralToken, withdrawnBalance); - } - - // BORROWER ACTIONS - - /** - * @notice Deposit collateral - * @param pool Pool address - * @param collateralAmount Amount of collateral to deposit - * @param price Price of the bucket - * @param stamploanEnabled Whether to stamp the loan or not - */ - function depositCollateral( - IERC20Pool pool, - uint256 collateralAmount, - uint256 price, - bool stamploanEnabled - ) public payable { - address collateralToken = pool.collateralAddress(); - _pull(collateralToken, collateralAmount); - - uint256 index = convertPriceToIndex(price); - IERC20(collateralToken).forceApprove(address(pool), collateralAmount); - pool.drawDebt(address(this), 0, index, collateralAmount * pool.collateralScale()); - _stampLoan(pool, stamploanEnabled); - emit ProxyActionsOperation("AjnaDeposit"); - } - - /** - * @notice Draw debt - * @param pool Pool address - * @param debtAmount Amount of debt to draw - * @param price Price of the bucket - */ - function drawDebt(IERC20Pool pool, uint256 debtAmount, uint256 price) public { - address debtToken = pool.quoteTokenAddress(); - uint256 index = convertPriceToIndex(price); - - pool.drawDebt(address(this), debtAmount * pool.quoteTokenScale(), index, 0); - _send(debtToken, debtAmount); - emit ProxyActionsOperation("AjnaBorrow"); - } - - /** - * @notice Deposit collateral and draw debt - * @param pool Pool address - * @param debtAmount Amount of debt to draw - * @param collateralAmount Amount of collateral to deposit - * @param price Price of the bucket - */ - function depositCollateralAndDrawDebt( - IERC20Pool pool, - uint256 debtAmount, - uint256 collateralAmount, - uint256 price - ) public { - address debtToken = pool.quoteTokenAddress(); - address collateralToken = pool.collateralAddress(); - uint256 index = convertPriceToIndex(price); - _pull(collateralToken, collateralAmount); - IERC20(collateralToken).forceApprove(address(pool), collateralAmount); - pool.drawDebt( - address(this), - debtAmount * pool.quoteTokenScale(), - index, - collateralAmount * pool.collateralScale() - ); - _send(debtToken, debtAmount); - emit ProxyActionsOperation("AjnaDepositBorrow"); - } - - /** - * @notice Deposit collateral and draw debt - * @param pool Pool address - * @param debtAmount Amount of debt to borrow - * @param collateralAmount Amount of collateral to deposit - * @param price Price of the bucket - * @param stamploanEnabled Whether to stamp the loan or not - */ - function depositAndDraw( - IERC20Pool pool, - uint256 debtAmount, - uint256 collateralAmount, - uint256 price, - bool stamploanEnabled - ) public payable { - if (debtAmount > 0 && collateralAmount > 0) { - depositCollateralAndDrawDebt(pool, debtAmount, collateralAmount, price); - } else if (debtAmount > 0) { - drawDebt(pool, debtAmount, price); - } else if (collateralAmount > 0) { - depositCollateral(pool, collateralAmount, price, stamploanEnabled); - } - } - - /** - * @notice Repay debt - * @param pool Pool address - * @param amount Amount of debt to repay - * @param stamploanEnabled Whether to stamp the loan or not - */ - function repayDebt(IERC20Pool pool, uint256 amount, bool stamploanEnabled) public payable { - address debtToken = pool.quoteTokenAddress(); - _pull(debtToken, amount); - IERC20(debtToken).forceApprove(address(pool), amount); - (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); - uint256 repaidAmountWAD = pool.repayDebt( - address(this), - amount * pool.quoteTokenScale(), - 0, - address(this), - lupIndex_ - ); - _stampLoan(pool, stamploanEnabled); - uint256 repaidAmount = _roundUpToScale(repaidAmountWAD, pool.quoteTokenScale()) / pool.quoteTokenScale(); - uint256 leftoverBalance = amount - repaidAmount; - if (leftoverBalance > 0) { - _send(debtToken, leftoverBalance); - } - IERC20(debtToken).safeApprove(address(pool), 0); - emit ProxyActionsOperation("AjnaRepay"); - } - - /** - * @notice Withdraw collateral - * @param pool Pool address - * @param amount Amount of collateral to withdraw - */ - function withdrawCollateral(IERC20Pool pool, uint256 amount) public { - address collateralToken = pool.collateralAddress(); - (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); - pool.repayDebt(address(this), 0, amount * pool.collateralScale(), address(this), lupIndex_); - _send(collateralToken, amount); - emit ProxyActionsOperation("AjnaWithdraw"); - } - - /** - * @notice Repay debt and withdraw collateral - * @param pool Pool address - * @param debtAmount Amount of debt to repay - * @param collateralAmount Amount of collateral to withdraw - */ - function repayDebtAndWithdrawCollateral(IERC20Pool pool, uint256 debtAmount, uint256 collateralAmount) public { - address debtToken = pool.quoteTokenAddress(); - address collateralToken = pool.collateralAddress(); - _pull(debtToken, debtAmount); - IERC20(debtToken).forceApprove(address(pool), debtAmount); - (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); - uint256 repaidAmountWAD = pool.repayDebt( - address(this), - debtAmount * pool.quoteTokenScale(), - collateralAmount * pool.collateralScale(), - address(this), - lupIndex_ - ); - _send(collateralToken, collateralAmount); - uint256 repaidAmount = _roundUpToScale(repaidAmountWAD, pool.quoteTokenScale()) / pool.quoteTokenScale(); - uint256 quoteLeftoverBalance = debtAmount - repaidAmount; - if (quoteLeftoverBalance > 0) { - _send(debtToken, quoteLeftoverBalance); - } - IERC20(debtToken).safeApprove(address(pool), 0); - emit ProxyActionsOperation("AjnaRepayWithdraw"); - } - - /** - * @notice Repay debt and withdraw collateral for msg.sender - * @param pool Pool address - * @param debtAmount Amount of debt to repay - * @param collateralAmount Amount of collateral to withdraw - * @param stamploanEnabled Whether to stamp the loan or not - */ - function repayWithdraw( - IERC20Pool pool, - uint256 debtAmount, - uint256 collateralAmount, - bool stamploanEnabled - ) external payable { - if (debtAmount > 0 && collateralAmount > 0) { - repayDebtAndWithdrawCollateral(pool, debtAmount, collateralAmount); - } else if (debtAmount > 0) { - repayDebt(pool, debtAmount, stamploanEnabled); - } else if (collateralAmount > 0) { - withdrawCollateral(pool, collateralAmount); - } - } - - /** - * @notice Repay debt and close position for msg.sender - * @param pool Pool address - */ - function repayAndClose(IERC20Pool pool) public payable { - address collateralToken = pool.collateralAddress(); - address debtToken = pool.quoteTokenAddress(); - - (uint256 debt, uint256 collateral, ) = poolInfoUtils.borrowerInfo(address(pool), address(this)); - uint256 debtPlusBuffer = _roundUpToScale(debt, pool.quoteTokenScale()); - uint256 amountDebt = debtPlusBuffer / pool.quoteTokenScale(); - _pull(debtToken, amountDebt); - - IERC20(debtToken).forceApprove(address(pool), amountDebt); - (, , , , , uint256 lupIndex_) = poolInfoUtils.poolPricesInfo(address(pool)); - pool.repayDebt(address(this), debtPlusBuffer, collateral, address(this), lupIndex_); - - uint256 amountCollateral = collateral / pool.collateralScale(); - _send(collateralToken, amountCollateral); - IERC20(debtToken).safeApprove(address(pool), 0); - emit ProxyActionsOperation("AjnaRepayAndClose"); - } - - /** - * @notice Open position for msg.sender - * @param pool Pool address - * @param debtAmount Amount of debt to borrow - * @param collateralAmount Amount of collateral to deposit - * @param price Price of the bucket - */ - function openPosition(IERC20Pool pool, uint256 debtAmount, uint256 collateralAmount, uint256 price) public payable { - emit CreatePosition(address(this), ajnaVersion, "Borrow", pool.collateralAddress(), pool.quoteTokenAddress()); - depositAndDraw(pool, debtAmount, collateralAmount, price, false); - } - - /** - * @notice Open Earn position for msg.sender - * @param pool Pool address - * @param depositAmount Amount of debt to borrow - * @param price Price of the bucket - */ - function openEarnPosition(IERC20Pool pool, uint256 depositAmount, uint256 price) public payable { - emit CreatePosition(address(this), ajnaVersion, "Earn", pool.collateralAddress(), pool.quoteTokenAddress()); - _supplyQuote(pool, depositAmount, price); - emit ProxyActionsOperation("AjnaSupplyQuote"); - } - - /** - * @notice Open Earn (with NFT) position for msg.sender - * @param pool Pool address - * @param depositAmount Amount of debt to borrow - * @param price Price of the bucket - */ - function openEarnPositionNft(IERC20Pool pool, uint256 depositAmount, uint256 price) public payable { - emit CreatePosition(address(this), ajnaVersion, "Earn", pool.collateralAddress(), pool.quoteTokenAddress()); - supplyQuoteMintNftAndStake(pool, depositAmount, price); - } - - /** + /** * @notice Called by lenders to add an amount of credit at a specified price bucket. * @param pool Address of the Ajana Pool. * @param amount The maximum amount of quote token to be moved by a lender. @@ -488,37 +546,37 @@ contract AjnaProxyActions is IAjnaProxyActions { * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance * @dev 1WBTC = 16,990.23 USDC translates to: 16990230 */ - function supplyQuote(IERC20Pool pool, uint256 amount, uint256 price) public payable { - _supplyQuote(pool, amount, price); - emit ProxyActionsOperation("AjnaSupplyQuote"); - } - - /** - * @notice Called by lenders to remove an amount of credit at a specified price bucket. - * @param pool Address of the Ajana Pool. - * @param amount The maximum amount of quote token to be moved by a lender. - * @param price The price the bucket to which the quote tokens will be added. - * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance - * @dev 1WBTC = 16,990.23 USDC translates to: 16990230 - */ - function withdrawQuote(IERC20Pool pool, uint256 amount, uint256 price) public { - _withdrawQuote(pool, amount, price); - emit ProxyActionsOperation("AjnaWithdrawQuote"); - } - - /** + function supplyQuote(IERC20Pool pool, uint256 amount, uint256 price) public payable { + _supplyQuote(pool, amount, price); + emit ProxyActionsOperation("AjnaSupplyQuote"); + } + + /** + * @notice Called by lenders to remove an amount of credit at a specified price bucket. + * @param pool Address of the Ajana Pool. + * @param amount The maximum amount of quote token to be moved by a lender. + * @param price The price the bucket to which the quote tokens will be added. + * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 3 decimal points for instance + * @dev 1WBTC = 16,990.23 USDC translates to: 16990230 + */ + function withdrawQuote(IERC20Pool pool, uint256 amount, uint256 price) public { + _withdrawQuote(pool, amount, price); + emit ProxyActionsOperation("AjnaWithdrawQuote"); + } + + /** * @notice Called by lenders to move max amount of credit from a specified price bucket to another specified price bucket. * @param pool Address of the Ajana Pool. * @param oldPrice The price of the bucket from which the quote tokens will be removed. * @param newPrice The price of the bucket to which the quote tokens will be added. */ - function moveQuote(IERC20Pool pool, uint256 oldPrice, uint256 newPrice) public { - _moveQuote(pool, oldPrice, newPrice); - emit ProxyActionsOperation("AjnaMoveQuote"); - } + function moveQuote(IERC20Pool pool, uint256 oldPrice, uint256 newPrice) public { + _moveQuote(pool, oldPrice, newPrice); + emit ProxyActionsOperation("AjnaMoveQuote"); + } - /** + /** * @notice Called by lenders to move an amount of credit from a specified price bucket to another specified price bucket, * @notice whilst adding additional amount. * @param pool Address of the Ajana Pool. @@ -527,18 +585,18 @@ contract AjnaProxyActions is IAjnaProxyActions { * @param newPrice The price of the bucket to which the quote tokens will be added. */ - function supplyAndMoveQuote( - IERC20Pool pool, - uint256 amountToAdd, - uint256 oldPrice, - uint256 newPrice - ) public payable { - _supplyQuote(pool, amountToAdd, newPrice); - _moveQuote(pool, oldPrice, newPrice); - emit ProxyActionsOperation("AjnaSupplyAndMoveQuote"); - } + function supplyAndMoveQuote( + IERC20Pool pool, + uint256 amountToAdd, + uint256 oldPrice, + uint256 newPrice + ) public payable { + _supplyQuote(pool, amountToAdd, newPrice); + _moveQuote(pool, oldPrice, newPrice); + emit ProxyActionsOperation("AjnaSupplyAndMoveQuote"); + } - /** + /** * @notice Called by lenders to move an amount of credit from a specified price bucket to another specified price bucket, * @notice whilst withdrawing additional amount. * @param pool Address of the Ajana Pool. @@ -547,163 +605,173 @@ contract AjnaProxyActions is IAjnaProxyActions { * @param newPrice The price of the bucket to which the quote tokens will be added. */ - function withdrawAndMoveQuote( - IERC20Pool pool, - uint256 amountToWithdraw, - uint256 oldPrice, - uint256 newPrice - ) public { - _withdrawQuote(pool, amountToWithdraw, oldPrice); - _moveQuote(pool, oldPrice, newPrice); - emit ProxyActionsOperation("AjnaWithdrawAndMoveQuote"); - } - - // REWARDS - - /** - * @notice Mints and NFT, memorizes the LPs of the user and stakes the NFT. - * @param pool Address of the Ajana Pool. - * @param price Price of the LPs to be memoriazed. - * @return tokenId Id of the minted NFT - */ - function _mintAndStakeNft(IERC20Pool pool, uint256 price) internal returns (uint256 tokenId) { - tokenId = _mintNft(pool); - - _memorializeLiquidity(price, tokenId, pool); - - self.rewardsManager().stake(tokenId); - } - - /** - * @notice Unstakes NFT and redeems position - * @param tokenId ID of the NFT to modify - * @param pool Address of the Ajana Pool. - * @param price Price of the bucket to redeem. - * @param burn Whether to burn the NFT or not - */ - function _unstakeNftAndRedeem(uint256 tokenId, IERC20Pool pool, uint256 price, bool burn) internal { - address _ARC = self.ARC(); - self.rewardsManager().unstake(tokenId); - - _redeemPosition(price, tokenId, address(pool)); - - if (burn) { - self.positionManager().burn(address(pool), tokenId); - if (IAccountGuard(GUARD).canCall(address(this), _ARC)) { - IAccountGuard(GUARD).permit(_ARC, address(this), false); - } - } - } - - /** - * @notice Supplies quote token, mints and NFT, memorizes the LPs of the user and stakes the NFT. - * @param pool Address of the Ajana Pool. - * @param amount The maximum amount of quote token to be deposited by a lender. - * @param price Price of the bucket to which the quote tokens will be added. - * @return tokenId Id of the minted NFT - */ - function supplyQuoteMintNftAndStake( - IERC20Pool pool, - uint256 amount, - uint256 price - ) public payable returns (uint256 tokenId) { - _supplyQuote(pool, amount, price); - tokenId = _mintAndStakeNft(pool, price); - emit ProxyActionsOperation("AjnaSupplyQuoteMintNftAndStake"); - } - - /** - * @notice Adds quote token to existing position and moves to different bucket - * @param pool Address of the Ajana Pool. - * @param amountToAdd The maximum amount of quote token to be deposited by a lender. - * @param oldPrice Index of the bucket to move from. - * @param newPrice Index of the bucket to move to. - * @param tokenId ID of the NFT to modify - */ - function supplyAndMoveQuoteNft( - IERC20Pool pool, - uint256 amountToAdd, - uint256 oldPrice, - uint256 newPrice, - uint256 tokenId - ) public payable { - self.rewardsManager().unstake(tokenId); - - _moveLiquidity(oldPrice, newPrice, tokenId, address(pool)); - _supplyQuote(pool, amountToAdd, newPrice); - _memorializeLiquidity(newPrice, tokenId, pool); - - self.rewardsManager().stake(tokenId); - emit ProxyActionsOperation("AjnaSupplyAndMoveQuoteNft"); - } - - /** - * @notice Adds quote token to existing NFT position - * @param pool Address of the Ajana Pool. - * @param amountToAdd The maximum amount of quote token to be deposited by a lender. - * @param price Price of the bucket to move from. - * @param tokenId ID of the NFT to modify - */ - function supplyQuoteNft(IERC20Pool pool, uint256 amountToAdd, uint256 price, uint256 tokenId) public payable { - self.rewardsManager().unstake(tokenId); - - _supplyQuote(pool, amountToAdd, price); - _memorializeLiquidity(price, tokenId, pool); - - self.rewardsManager().stake(tokenId); - emit ProxyActionsOperation("AjnaSupplyQuoteNft"); - } - - /** - * @notice Withdraws quote token to existing position and moves to different bucket - * @param pool Address of the Ajana Pool. - * @param amountToWithdraw The maximum amount of quote token to be withdrawn by a lender. - * @param oldPrice Index of the bucket to move from. - * @param newPrice Index of the bucket to move to. - * @param tokenId ID of the NFT to modify - */ - function withdrawAndMoveQuoteNft( - IERC20Pool pool, - uint256 amountToWithdraw, - uint256 oldPrice, - uint256 newPrice, - uint256 tokenId - ) public payable { - self.rewardsManager().unstake(tokenId); - - _moveLiquidity(oldPrice, newPrice, tokenId, address(pool)); - _redeemPosition(newPrice, tokenId, address(pool)); - _withdrawQuote(pool, amountToWithdraw, newPrice); - _memorializeLiquidity(newPrice, tokenId, pool); - - self.rewardsManager().stake(tokenId); - emit ProxyActionsOperation("AjnaWithdrawAndMoveQuoteNft"); - } - - /** - * @notice Withdraws quote token from existing NFT position - * @param pool Address of the Ajana Pool. - * @param amountToWithdraw The maximum amount of quote token to be withdrawn by a lender. - * @param price Price of the bucket to withdraw from - * @param tokenId ID of the NFT to modify - */ - function withdrawQuoteNft( - IERC20Pool pool, - uint256 amountToWithdraw, - uint256 price, - uint256 tokenId - ) public payable { - self.rewardsManager().unstake(tokenId); - - _redeemPosition(price, tokenId, address(pool)); - _withdrawQuote(pool, amountToWithdraw, price); - _memorializeLiquidity(price, tokenId, pool); - - self.rewardsManager().stake(tokenId); - emit ProxyActionsOperation("AjnaWithdrawQuoteNft"); - } - - /** + function withdrawAndMoveQuote( + IERC20Pool pool, + uint256 amountToWithdraw, + uint256 oldPrice, + uint256 newPrice + ) public { + _withdrawQuote(pool, amountToWithdraw, oldPrice); + _moveQuote(pool, oldPrice, newPrice); + emit ProxyActionsOperation("AjnaWithdrawAndMoveQuote"); + } + + // REWARDS + + /** + * @notice Mints and NFT, memorizes the LPs of the user and stakes the NFT. + * @param pool Address of the Ajana Pool. + * @param price Price of the LPs to be memoriazed. + * @return tokenId Id of the minted NFT + */ + function _mintAndStakeNft(IERC20Pool pool, uint256 price) internal returns (uint256 tokenId) { + tokenId = _mintNft(pool); + + _memorializeLiquidity(price, tokenId, pool); + + self.rewardsManager().stake(tokenId); + } + + /** + * @notice Unstakes NFT and redeems position + * @param tokenId ID of the NFT to modify + * @param pool Address of the Ajana Pool. + * @param price Price of the bucket to redeem. + * @param burn Whether to burn the NFT or not + */ + function _unstakeNftAndRedeem( + uint256 tokenId, + IERC20Pool pool, + uint256 price, + bool burn + ) internal { + address _ARC = self.ARC(); + self.rewardsManager().unstake(tokenId); + + _redeemPosition(price, tokenId, address(pool)); + + if (burn) { + self.positionManager().burn(address(pool), tokenId); + if (IAccountGuard(GUARD).canCall(address(this), _ARC)) { + IAccountGuard(GUARD).permit(_ARC, address(this), false); + } + } + } + + /** + * @notice Supplies quote token, mints and NFT, memorizes the LPs of the user and stakes the NFT. + * @param pool Address of the Ajana Pool. + * @param amount The maximum amount of quote token to be deposited by a lender. + * @param price Price of the bucket to which the quote tokens will be added. + * @return tokenId Id of the minted NFT + */ + function supplyQuoteMintNftAndStake( + IERC20Pool pool, + uint256 amount, + uint256 price + ) public payable returns (uint256 tokenId) { + _supplyQuote(pool, amount, price); + tokenId = _mintAndStakeNft(pool, price); + emit ProxyActionsOperation("AjnaSupplyQuoteMintNftAndStake"); + } + + /** + * @notice Adds quote token to existing position and moves to different bucket + * @param pool Address of the Ajana Pool. + * @param amountToAdd The maximum amount of quote token to be deposited by a lender. + * @param oldPrice Index of the bucket to move from. + * @param newPrice Index of the bucket to move to. + * @param tokenId ID of the NFT to modify + */ + function supplyAndMoveQuoteNft( + IERC20Pool pool, + uint256 amountToAdd, + uint256 oldPrice, + uint256 newPrice, + uint256 tokenId + ) public payable { + self.rewardsManager().unstake(tokenId); + + _moveLiquidity(oldPrice, newPrice, tokenId, address(pool)); + _supplyQuote(pool, amountToAdd, newPrice); + _memorializeLiquidity(newPrice, tokenId, pool); + + self.rewardsManager().stake(tokenId); + emit ProxyActionsOperation("AjnaSupplyAndMoveQuoteNft"); + } + + /** + * @notice Adds quote token to existing NFT position + * @param pool Address of the Ajana Pool. + * @param amountToAdd The maximum amount of quote token to be deposited by a lender. + * @param price Price of the bucket to move from. + * @param tokenId ID of the NFT to modify + */ + function supplyQuoteNft( + IERC20Pool pool, + uint256 amountToAdd, + uint256 price, + uint256 tokenId + ) public payable { + self.rewardsManager().unstake(tokenId); + + _supplyQuote(pool, amountToAdd, price); + _memorializeLiquidity(price, tokenId, pool); + + self.rewardsManager().stake(tokenId); + emit ProxyActionsOperation("AjnaSupplyQuoteNft"); + } + + /** + * @notice Withdraws quote token to existing position and moves to different bucket + * @param pool Address of the Ajana Pool. + * @param amountToWithdraw The maximum amount of quote token to be withdrawn by a lender. + * @param oldPrice Index of the bucket to move from. + * @param newPrice Index of the bucket to move to. + * @param tokenId ID of the NFT to modify + */ + function withdrawAndMoveQuoteNft( + IERC20Pool pool, + uint256 amountToWithdraw, + uint256 oldPrice, + uint256 newPrice, + uint256 tokenId + ) public payable { + self.rewardsManager().unstake(tokenId); + + _moveLiquidity(oldPrice, newPrice, tokenId, address(pool)); + _redeemPosition(newPrice, tokenId, address(pool)); + _withdrawQuote(pool, amountToWithdraw, newPrice); + _memorializeLiquidity(newPrice, tokenId, pool); + + self.rewardsManager().stake(tokenId); + emit ProxyActionsOperation("AjnaWithdrawAndMoveQuoteNft"); + } + + /** + * @notice Withdraws quote token from existing NFT position + * @param pool Address of the Ajana Pool. + * @param amountToWithdraw The maximum amount of quote token to be withdrawn by a lender. + * @param price Price of the bucket to withdraw from + * @param tokenId ID of the NFT to modify + */ + function withdrawQuoteNft( + IERC20Pool pool, + uint256 amountToWithdraw, + uint256 price, + uint256 tokenId + ) public payable { + self.rewardsManager().unstake(tokenId); + + _redeemPosition(price, tokenId, address(pool)); + _withdrawQuote(pool, amountToWithdraw, price); + _memorializeLiquidity(price, tokenId, pool); + + self.rewardsManager().stake(tokenId); + emit ProxyActionsOperation("AjnaWithdrawQuoteNft"); + } + + /** * @notice Called by lenders to move an amount of credit from a specified price bucket to another * @notice specified price bucket using staked NFT. * @param oldPrice Index of the bucket to move from. @@ -711,129 +779,143 @@ contract AjnaProxyActions is IAjnaProxyActions { * @param tokenId ID of the NFT to modify */ - function moveQuoteNft(IERC20Pool pool, uint256 oldPrice, uint256 newPrice, uint256 tokenId) public payable { - self.rewardsManager().unstake(tokenId); - _moveLiquidity(oldPrice, newPrice, tokenId, address(pool)); - self.rewardsManager().stake(tokenId); - emit ProxyActionsOperation("AjnaMoveQuoteNft"); - } - - /** - * @notice Claim staking rewards - * @param pool Address of the Ajana Pool. - * @param tokenId TokenId to claim rewards for - */ - function claimRewardsAndSendToOwner(IERC20Pool pool, uint256 tokenId) public { - uint256 currentEpoch = IERC20Pool(pool).currentBurnEpoch(); - uint256 minAmount = self.rewardsManager().calculateRewards(tokenId, currentEpoch); - self.rewardsManager().claimRewards(tokenId, currentEpoch, minAmount); - ajnaToken.transfer(msg.sender, ajnaToken.balanceOf(address(this))); - } - - /** - * @notice Unstakes NFT and withdraws quote token - * @param pool Address of the Ajana Pool. - * @param price Price of the bucket to redeem. - * @param tokenId ID of the NFT to unstake - */ - function unstakeNftAndWithdrawQuote(IERC20Pool pool, uint256 price, uint256 tokenId) public { - _unstakeNftAndRedeem(tokenId, pool, price, true); - _withdrawQuote(pool, type(uint256).max, price); - emit ProxyActionsOperation("AjnaUnstakeNftAndWithdrawQuote"); - } - - /** - * @notice Unstakes NFT and withdraws quote token and reclaims collateral from liquidated bucket - * @param pool Address of the Ajana Pool. - * @param price Price of the bucket to redeem. - * @param tokenId ID of the NFT to unstake - */ - function unstakeNftAndClaimCollateral(IERC20Pool pool, uint256 price, uint256 tokenId) public { - _unstakeNftAndRedeem(tokenId, pool, price, true); - _removeCollateral(pool, price); - emit ProxyActionsOperation("AjnaUnstakeNftAndClaimCollateral"); - } - - /** - * @notice Reclaims collateral from liquidated bucket - * @param pool Address of the Ajana Pool. - * @param price Price of the bucket to redeem. - */ - function removeCollateral(IERC20Pool pool, uint256 price) public { - _removeCollateral(pool, price); - emit ProxyActionsOperation("AjnaRemoveCollateral"); - } - - // OPT IN AND OUT - - /** - * @notice Mints and NFT, memorizes the LPs of the user and stakes the NFT. - * @param pool Address of the Ajana Pool. - * @param price Price of the LPs to be memoriazed. - * @return tokenId Id of the minted NFT - */ - function optInStaking(IERC20Pool pool, uint256 price) public returns (uint256 tokenId) { - tokenId = _mintAndStakeNft(pool, price); - emit ProxyActionsOperation("AjnaOptInStaking"); - } - - /** - * @notice Unstakes the NFT, burns it and redeems invested LP tokens, memorized by the user. - * @param pool Address of the Ajana Pool. - * @param tokenId Id of the NFT to unstake and burn. - * @param price Price of the LPs to be redeemed. - * @dev This function unstakes the NFT which was previously staked and also calls "_unstakeNftAndRedeem" to redeem invested LP tokens. - */ - function optOutStaking(IERC20Pool pool, uint256 tokenId, uint256 price) public { - _unstakeNftAndRedeem(tokenId, pool, price, true); - emit ProxyActionsOperation("AjnaOptOutStaking"); - } - - // VIEW FUNCTIONS - /** - * @notice Converts price to index - * @param price price of uint (10**decimals) collateral token in debt token (10**decimals) with 18 decimal points for instance - * @return index index of the bucket - * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 18 decimal points for instance - * @dev 1WBTC = 16,990.23 USDC translates to: 16990230000000000000000 - */ - function convertPriceToIndex(uint256 price) public view returns (uint256) { - return poolInfoUtils.priceToIndex(price); - } - - /** - * @notice Get the amount of quote token deposited to a specific bucket - * @param pool Address of the Ajana Pool. - * @param price Price of the bucket to query - * @return quoteAmount Amount of quote token deposited to dpecific bucket - * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 18 decimal points for instance - * @dev 1WBTC = 16,990.23 USDC translates to: 16990230000000000000000 - */ - function getQuoteAmount(IERC20Pool pool, uint256 price) public view returns (uint256 quoteAmount) { - uint256 index = convertPriceToIndex(price); - - (uint256 lpCount, ) = pool.lenderInfo(index, address(this)); - quoteAmount = poolInfoUtils.lpToQuoteTokens(address(pool), lpCount, index); - } - - /** - * @notice Rounds a token amount down to the minimum amount permissible by the token scale. - * @param amount_ Value to be rounded. - * @param tokenScale_ Scale of the token, presented as a power of `10`. - * @return scaledAmount_ Rounded value. - */ - function _roundToScale(uint256 amount_, uint256 tokenScale_) internal pure returns (uint256 scaledAmount_) { - scaledAmount_ = (amount_ / tokenScale_) * tokenScale_; - } - - /** - * @notice Rounds a token amount up to the next amount permissible by the token scale. - * @param amount_ Value to be rounded. - * @param tokenScale_ Scale of the token, presented as a power of `10`. - * @return scaledAmount_ Rounded value. - */ - function _roundUpToScale(uint256 amount_, uint256 tokenScale_) internal pure returns (uint256 scaledAmount_) { - if (amount_ % tokenScale_ == 0) scaledAmount_ = amount_; - else scaledAmount_ = _roundToScale(amount_, tokenScale_) + tokenScale_; - } + function moveQuoteNft( + IERC20Pool pool, + uint256 oldPrice, + uint256 newPrice, + uint256 tokenId + ) public payable { + self.rewardsManager().unstake(tokenId); + _moveLiquidity(oldPrice, newPrice, tokenId, address(pool)); + self.rewardsManager().stake(tokenId); + emit ProxyActionsOperation("AjnaMoveQuoteNft"); + } + + /** + * @notice Claim staking rewards + * @param pool Address of the Ajana Pool. + * @param tokenId TokenId to claim rewards for + */ + function claimRewardsAndSendToOwner(IERC20Pool pool, uint256 tokenId) public { + uint256 currentEpoch = IERC20Pool(pool).currentBurnEpoch(); + uint256 minAmount = self.rewardsManager().calculateRewards(tokenId, currentEpoch); + self.rewardsManager().claimRewards(tokenId, currentEpoch, minAmount); + ajnaToken.transfer(msg.sender, ajnaToken.balanceOf(address(this))); + } + + /** + * @notice Unstakes NFT and withdraws quote token + * @param pool Address of the Ajana Pool. + * @param price Price of the bucket to redeem. + * @param tokenId ID of the NFT to unstake + */ + function unstakeNftAndWithdrawQuote(IERC20Pool pool, uint256 price, uint256 tokenId) public { + _unstakeNftAndRedeem(tokenId, pool, price, true); + _withdrawQuote(pool, type(uint256).max, price); + emit ProxyActionsOperation("AjnaUnstakeNftAndWithdrawQuote"); + } + + /** + * @notice Unstakes NFT and withdraws quote token and reclaims collateral from liquidated bucket + * @param pool Address of the Ajana Pool. + * @param price Price of the bucket to redeem. + * @param tokenId ID of the NFT to unstake + */ + function unstakeNftAndClaimCollateral(IERC20Pool pool, uint256 price, uint256 tokenId) public { + _unstakeNftAndRedeem(tokenId, pool, price, true); + _removeCollateral(pool, price); + emit ProxyActionsOperation("AjnaUnstakeNftAndClaimCollateral"); + } + + /** + * @notice Reclaims collateral from liquidated bucket + * @param pool Address of the Ajana Pool. + * @param price Price of the bucket to redeem. + */ + function removeCollateral(IERC20Pool pool, uint256 price) public { + _removeCollateral(pool, price); + emit ProxyActionsOperation("AjnaRemoveCollateral"); + } + + // OPT IN AND OUT + + /** + * @notice Mints and NFT, memorizes the LPs of the user and stakes the NFT. + * @param pool Address of the Ajana Pool. + * @param price Price of the LPs to be memoriazed. + * @return tokenId Id of the minted NFT + */ + function optInStaking(IERC20Pool pool, uint256 price) public returns (uint256 tokenId) { + tokenId = _mintAndStakeNft(pool, price); + emit ProxyActionsOperation("AjnaOptInStaking"); + } + + /** + * @notice Unstakes the NFT, burns it and redeems invested LP tokens, memorized by the user. + * @param pool Address of the Ajana Pool. + * @param tokenId Id of the NFT to unstake and burn. + * @param price Price of the LPs to be redeemed. + * @dev This function unstakes the NFT which was previously staked and also calls "_unstakeNftAndRedeem" to redeem invested LP tokens. + */ + function optOutStaking(IERC20Pool pool, uint256 tokenId, uint256 price) public { + _unstakeNftAndRedeem(tokenId, pool, price, true); + emit ProxyActionsOperation("AjnaOptOutStaking"); + } + + // VIEW FUNCTIONS + /** + * @notice Converts price to index + * @param price price of uint (10**decimals) collateral token in debt token (10**decimals) with 18 decimal points for instance + * @return index index of the bucket + * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 18 decimal points for instance + * @dev 1WBTC = 16,990.23 USDC translates to: 16990230000000000000000 + */ + function convertPriceToIndex(uint256 price) public view returns (uint256) { + return poolInfoUtils.priceToIndex(price); + } + + /** + * @notice Get the amount of quote token deposited to a specific bucket + * @param pool Address of the Ajana Pool. + * @param price Price of the bucket to query + * @return quoteAmount Amount of quote token deposited to dpecific bucket + * @dev price of uint (10**decimals) collateral token in debt token (10**decimals) with 18 decimal points for instance + * @dev 1WBTC = 16,990.23 USDC translates to: 16990230000000000000000 + */ + function getQuoteAmount( + IERC20Pool pool, + uint256 price + ) public view returns (uint256 quoteAmount) { + uint256 index = convertPriceToIndex(price); + + (uint256 lpCount, ) = pool.lenderInfo(index, address(this)); + quoteAmount = poolInfoUtils.lpToQuoteTokens(address(pool), lpCount, index); + } + + /** + * @notice Rounds a token amount down to the minimum amount permissible by the token scale. + * @param amount_ Value to be rounded. + * @param tokenScale_ Scale of the token, presented as a power of `10`. + * @return scaledAmount_ Rounded value. + */ + function _roundToScale( + uint256 amount_, + uint256 tokenScale_ + ) internal pure returns (uint256 scaledAmount_) { + scaledAmount_ = (amount_ / tokenScale_) * tokenScale_; + } + + /** + * @notice Rounds a token amount up to the next amount permissible by the token scale. + * @param amount_ Value to be rounded. + * @param tokenScale_ Scale of the token, presented as a power of `10`. + * @return scaledAmount_ Rounded value. + */ + function _roundUpToScale( + uint256 amount_, + uint256 tokenScale_ + ) internal pure returns (uint256 scaledAmount_) { + if (amount_ % tokenScale_ == 0) scaledAmount_ = amount_; + else scaledAmount_ = _roundToScale(amount_, tokenScale_) + tokenScale_; + } } diff --git a/packages/ajna-contracts/hardhat.config.ts b/packages/ajna-contracts/hardhat.config.ts index f94568051..c08352018 100644 --- a/packages/ajna-contracts/hardhat.config.ts +++ b/packages/ajna-contracts/hardhat.config.ts @@ -28,6 +28,13 @@ const config: HardhatUserConfig = { // FIXME: uncomment when we have the env variable properly set up in gh actions // accounts: [process.env.PRIV_KEY_MAINNET || ""], }, + base: { + url: process.env.BASE_URL || "", + // initialBaseFeePerGas: 1000000000, + // gasPrice: 45000000000, // 45 gwei + // FIXME: uncomment when we have the env variable properly set up in gh actions + accounts: [process.env.PRIV_KEY_BASE || ""], + }, goerli: { url: process.env.GOERLI_URL || "", // FIXME: uncomment when we have the env variable properly set up in gh actions diff --git a/packages/ajna-contracts/scripts/common/config.ts b/packages/ajna-contracts/scripts/common/config.ts index e72d719a8..228fe79ab 100644 --- a/packages/ajna-contracts/scripts/common/config.ts +++ b/packages/ajna-contracts/scripts/common/config.ts @@ -12,21 +12,21 @@ export const ADDRESSES = { }, mainnet: { AJNA_PROXY_ACTIONS: "0x0000000000000000000000000000000000000000", - ERC20_POOL_FACTORY: "0x0000000000000000000000000000000000000000", - POOL_INFO_UTILS: "0x0000000000000000000000000000000000000000", - POSITION_MANAGER: "0x0000000000000000000000000000000000000000", + ERC20_POOL_FACTORY: "0x03907900D4120956BA3E253e7580B29e618091a4", + POOL_INFO_UTILS: "0xF086B79D71d55806493fD8CA4DB39a81C82e28d4", + POSITION_MANAGER: "0x64D67777cA15d445cD29fE8c25B6eB31cbe853A4", REWARD_MANAGER: "0x0000000000000000000000000000000000000000", - GUARD: "0x0000000000000000000000000000000000000000", - SERVICE_REGISTRY: "0x0000000000000000000000000000000000000000", + GUARD: "0xCe91349d2A4577BBd0fC91Fe6019600e047f2847", + SERVICE_REGISTRY: "0x5e81a7515f956ab642eb698821a449fe8fe7498e", }, base: { - AJNA_PROXY_ACTIONS: "0x0000000000000000000000000000000000000000", - ERC20_POOL_FACTORY: "0x0000000000000000000000000000000000000000", - POOL_INFO_UTILS: "0x0000000000000000000000000000000000000000", - POSITION_MANAGER: "0x0000000000000000000000000000000000000000", + AJNA_PROXY_ACTIONS: "0xa7840fa682506117f4549e918930c80c1fc3a46c", + ERC20_POOL_FACTORY: "0x80A21A780f1300aa37Df1CCA0F96981FBc2785BD", + POOL_INFO_UTILS: "0xA3A1e968Bd6C578205E11256c8e6929f21742aAF", + POSITION_MANAGER: "0x8a7F5aFb7E3c3fD1f3Cc9D874b454b6De11EBbC9", REWARD_MANAGER: "0x0000000000000000000000000000000000000000", - GUARD: "0x0000000000000000000000000000000000000000", - SERVICE_REGISTRY: "0x0000000000000000000000000000000000000000", + GUARD: "0x83c8BFfD11913f0e94C1C0B615fC2Fdb1B17A27e", + SERVICE_REGISTRY: "0x0c1EDa5544EA63cf3d365912343161913a8f19Eb", }, }; //TOKENS @@ -58,7 +58,7 @@ export const TOKENS = { RETH: "0x0000000000000000000000000000000000000000", WSTETH: "0x0000000000000000000000000000000000000000", WBTC: "0x0000000000000000000000000000000000000000", - USDC: "0x6e4c6e76b3C1D834c0e3c4c2bAec8d58B8421A99", + USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", DAI: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", CBETH: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", AJNA: "0x0000000000000000000000000000000000000000", @@ -67,20 +67,20 @@ export const TOKENS = { }; export const POOLS: Pool[] = [ - { pair: "WBTC-USDC", amount: 5000, price: 20000, deposit: true }, - { pair: "WBTC-DAI", amount: 5000, price: 20000, deposit: true }, - { pair: "USDC-WBTC", amount: 1, price: 0.000030769, deposit: true }, - { pair: "WETH-USDC", amount: 5000, price: 1500, deposit: true }, - { pair: "WETH-DAI", amount: 5000, price: 1500, deposit: true }, - { pair: "USDC-WETH", amount: 1, price: 0.0005, deposit: true }, - { pair: "RETH-DAI", amount: 5000, price: 1500, deposit: true }, - { pair: "RETH-USDC", amount: 5000, price: 1500, deposit: true }, - { pair: "RETH-WETH", amount: 1, price: 1.07, deposit: true }, - { pair: "WSTETH-DAI", amount: 5000, price: 1500, deposit: true }, - { pair: "WSTETH-USDC", amount: 5000, price: 1500, deposit: true }, - { pair: "WSTETH-WETH", amount: 1, price: 1.09, deposit: true }, - { pair: "CBETH-WETH", amount: 1, price: 1.09, deposit: false }, - { pair: "TBTC-WBTC", amount: 1, price: 1.01, deposit: true }, + { pair: "WBTC-USDC", amount: 5000, price: 20000, deposit: false, deploy: false, rate: "50000000000000000" }, + { pair: "WBTC-DAI", amount: 5000, price: 20000, deposit: false, deploy: false, rate: "50000000000000000" }, + { pair: "USDC-WBTC", amount: 1, price: 0.000030769, deposit: false, deploy: false, rate: "40000000000000000" }, + { pair: "WETH-USDC", amount: 5000, price: 1500, deposit: false, deploy: true, rate: "50000000000000000" }, + { pair: "WETH-DAI", amount: 5000, price: 1500, deposit: false, deploy: false, rate: "50000000000000000" }, + { pair: "USDC-WETH", amount: 1, price: 0.0005, deposit: false, deploy: false, rate: "40000000000000000" }, + { pair: "RETH-DAI", amount: 5000, price: 1500, deposit: false, deploy: false, rate: "50000000000000000" }, + { pair: "RETH-USDC", amount: 5000, price: 1500, deposit: false, deploy: false, rate: "50000000000000000" }, + { pair: "RETH-WETH", amount: 1, price: 1.07, deposit: false, deploy: false, rate: "40000000000000000" }, + { pair: "WSTETH-DAI", amount: 5000, price: 1500, deposit: false, deploy: false, rate: "50000000000000000" }, + { pair: "WSTETH-USDC", amount: 5000, price: 1500, deposit: false, deploy: false, rate: "50000000000000000" }, + { pair: "WSTETH-WETH", amount: 1, price: 1.09, deposit: false, deploy: false, rate: "40000000000000000" }, + { pair: "CBETH-WETH", amount: 1, price: 1.09, deposit: false, deploy: true, rate: "40000000000000000" }, + { pair: "TBTC-WBTC", amount: 1, price: 1.01, deposit: false, deploy: false, rate: "40000000000000000" }, ]; export const CONFIG = { diff --git a/packages/ajna-contracts/scripts/common/deployment.utils.ts b/packages/ajna-contracts/scripts/common/deployment.utils.ts index dde705a41..d08fefb2b 100644 --- a/packages/ajna-contracts/scripts/common/deployment.utils.ts +++ b/packages/ajna-contracts/scripts/common/deployment.utils.ts @@ -184,16 +184,19 @@ export async function deployPoolFactory( return { erc20PoolFactory, erc721PoolFactory }; } + +// default rate = 4% export async function deployPool( erc20PoolFactory: ERC20PoolFactory, collateral: string, quote: string, - deployPools = true + deployPools = true, + rate = "40000000000000000" ): Promise { const hash = await erc20PoolFactory.ERC20_NON_SUBSET_HASH(); let poolAddress = await erc20PoolFactory.deployedPools(hash, collateral, quote); if (poolAddress === hre.ethers.constants.AddressZero && deployPools) { - const tx = await erc20PoolFactory.deployPool(collateral, quote, "25000000000000000"); + const tx = await erc20PoolFactory.deployPool(collateral, quote, rate); await tx.wait(); poolAddress = await erc20PoolFactory.deployedPools(hash, collateral, quote); } diff --git a/packages/ajna-contracts/scripts/common/types.ts b/packages/ajna-contracts/scripts/common/types.ts index 7ca11d5ed..ff2edc992 100644 --- a/packages/ajna-contracts/scripts/common/types.ts +++ b/packages/ajna-contracts/scripts/common/types.ts @@ -7,4 +7,4 @@ export interface User { proxy: IAccountImplementation; } -export type Pool = { pair: string; amount: number; price: number; deposit: boolean }; +export type Pool = { pair: string; amount: number; price: number; deposit: boolean; deploy: boolean; rate: string }; diff --git a/packages/ajna-contracts/scripts/deploy-apa.ts b/packages/ajna-contracts/scripts/deploy-apa.ts index 474793d94..96f5af112 100644 --- a/packages/ajna-contracts/scripts/deploy-apa.ts +++ b/packages/ajna-contracts/scripts/deploy-apa.ts @@ -20,8 +20,6 @@ async function main() { const network = hre.network.name === "hardhat" || hre.network.name === "local" ? "mainnet" : hre.network.name; const initializeStakingRewards = CONFIG.initializeStakingRewards || false; - const deployPools = CONFIG.deployPools || false; - const erc20PoolFactory = await utils.getContract( "ERC20PoolFactory", ADDRESSES[network].ERC20_POOL_FACTORY @@ -61,11 +59,10 @@ async function main() { console.log(`AjnaRewardsClaimer Deployed: ${arc.address}`); } } - await deployAjnaPools(deployPools, network, erc20PoolFactory, apa, signer); + await deployAjnaPools(network, erc20PoolFactory, apa, signer); } async function deployAjnaPools( - deployPools: boolean, network: string, erc20PoolFactory: ERC20PoolFactory, apa: AjnaProxyActions, @@ -82,7 +79,7 @@ async function deployAjnaPools( continue; } try { - const deployedPool = await deployPool(erc20PoolFactory, collateralToken, quoteToken, deployPools); + const deployedPool = await deployPool(erc20PoolFactory, collateralToken, quoteToken, pool.deploy, pool.rate); deployedPool.address === hre.ethers.constants.AddressZero ? console.info(chalk.red(`Pool ${pool.pair} not yet deployed`)) : console.info(chalk.green(`Pool ${pool.pair} deployed at ${deployedPool.address}`)); diff --git a/packages/deploy-configurations/configs/base.conf.ts b/packages/deploy-configurations/configs/base.conf.ts index 0bcf5d9f1..e6cbcbd67 100644 --- a/packages/deploy-configurations/configs/base.conf.ts +++ b/packages/deploy-configurations/configs/base.conf.ts @@ -1082,12 +1082,12 @@ export const config: SystemConfig = { ajna: { AjnaPoolInfo: { name: 'AjnaPoolInfo', - address: '0x0000000000000000000000000000000000000000', - serviceRegistryName: undefined, + address: '0xA3A1e968Bd6C578205E11256c8e6929f21742aAF', + serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.AJNA_POOL_UTILS_INFO, }, AjnaProxyActions: { name: 'AjnaProxyActions', - address: '0x0000000000000000000000000000000000000000', + address: '0xa7840fa682506117f4549e918930c80c1fc3a46c', }, AjnaPoolPairs_ETHDAI: { name: 'AjnaPoolPairs_ETHDAI', @@ -1208,8 +1208,8 @@ export const config: SystemConfig = { }, ERC20PoolFactory: { name: 'ERC20PoolFactory', - address: '0x0000000000000000000000000000000000000000', - serviceRegistryName: undefined, + address: '0x80A21A780f1300aa37Df1CCA0F96981FBc2785BD', + serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.ERC20_POOL_FACTORY, }, }, morphoblue: { diff --git a/packages/deploy-configurations/configs/hardhat.conf.ts b/packages/deploy-configurations/configs/hardhat.conf.ts index 7e5db2920..7a812f7ed 100644 --- a/packages/deploy-configurations/configs/hardhat.conf.ts +++ b/packages/deploy-configurations/configs/hardhat.conf.ts @@ -1173,7 +1173,7 @@ export const config: SystemConfig = { ajna: { AjnaPoolInfo: { name: 'AjnaPoolInfo', - address: '0x154FFf344f426F99E328bacf70f4Eb632210ecdc', + address: '0xF086B79D71d55806493fD8CA4DB39a81C82e28d4', serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.AJNA_POOL_UTILS_INFO, }, AjnaProxyActions: { @@ -1299,7 +1299,7 @@ export const config: SystemConfig = { }, ERC20PoolFactory: { name: 'ERC20PoolFactory', - address: '0xe6f4d9711121e5304b30ac2aae57e3b085ad3c4d', + address: '0x03907900D4120956BA3E253e7580B29e618091a4', serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.ERC20_POOL_FACTORY, }, }, diff --git a/packages/deploy-configurations/configs/mainnet.conf.ts b/packages/deploy-configurations/configs/mainnet.conf.ts index 7684cce9a..10eda9b79 100644 --- a/packages/deploy-configurations/configs/mainnet.conf.ts +++ b/packages/deploy-configurations/configs/mainnet.conf.ts @@ -1199,7 +1199,7 @@ export const config: SystemConfig = { ajna: { AjnaPoolInfo: { name: 'AjnaPoolInfo', - address: '0x154FFf344f426F99E328bacf70f4Eb632210ecdc', + address: '0xF086B79D71d55806493fD8CA4DB39a81C82e28d4', serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.AJNA_POOL_UTILS_INFO, }, AjnaProxyActions: { @@ -1325,7 +1325,7 @@ export const config: SystemConfig = { }, ERC20PoolFactory: { name: 'ERC20PoolFactory', - address: '0xe6f4d9711121e5304b30ac2aae57e3b085ad3c4d', + address: '0x03907900D4120956BA3E253e7580B29e618091a4', serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.ERC20_POOL_FACTORY, }, }, diff --git a/packages/deploy-configurations/configs/test/mainnet.conf.ts b/packages/deploy-configurations/configs/test/mainnet.conf.ts index 9826c43e0..93465264b 100644 --- a/packages/deploy-configurations/configs/test/mainnet.conf.ts +++ b/packages/deploy-configurations/configs/test/mainnet.conf.ts @@ -984,7 +984,7 @@ export const config: SystemConfig = { ajna: { AjnaPoolInfo: { name: 'AjnaPoolInfo', - address: '0x154FFf344f426F99E328bacf70f4Eb632210ecdc', + address: '0xF086B79D71d55806493fD8CA4DB39a81C82e28d4', serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.AJNA_POOL_UTILS_INFO, }, AjnaProxyActions: { @@ -1110,7 +1110,7 @@ export const config: SystemConfig = { }, ERC20PoolFactory: { name: 'ERC20PoolFactory', - address: '0xe6f4d9711121e5304b30ac2aae57e3b085ad3c4d', + address: '0x03907900D4120956BA3E253e7580B29e618091a4', serviceRegistryName: SERVICE_REGISTRY_NAMES.ajna.ERC20_POOL_FACTORY, }, }, From e611dfb0a72bc197cde7c365dea1a74f5ff506a1 Mon Sep 17 00:00:00 2001 From: halaprix Date: Mon, 4 Dec 2023 15:27:26 +0100 Subject: [PATCH 5/6] chore: deploy mainnet ajna proxy actions --- packages/ajna-contracts/scripts/common/config.ts | 6 +++--- packages/deploy-configurations/configs/hardhat.conf.ts | 2 +- packages/deploy-configurations/configs/mainnet.conf.ts | 2 +- packages/deploy-configurations/configs/test/mainnet.conf.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ajna-contracts/scripts/common/config.ts b/packages/ajna-contracts/scripts/common/config.ts index 228fe79ab..1a13aea91 100644 --- a/packages/ajna-contracts/scripts/common/config.ts +++ b/packages/ajna-contracts/scripts/common/config.ts @@ -11,7 +11,7 @@ export const ADDRESSES = { SERVICE_REGISTRY: "0x5A5277B8c8a42e6d8Ab517483D7D59b4ca03dB7F", }, mainnet: { - AJNA_PROXY_ACTIONS: "0x0000000000000000000000000000000000000000", + AJNA_PROXY_ACTIONS: "0x53B1f1B3f34b5B3C7dA8BD60a7E8ee2eFd175603", ERC20_POOL_FACTORY: "0x03907900D4120956BA3E253e7580B29e618091a4", POOL_INFO_UTILS: "0xF086B79D71d55806493fD8CA4DB39a81C82e28d4", POSITION_MANAGER: "0x64D67777cA15d445cD29fE8c25B6eB31cbe853A4", @@ -69,7 +69,7 @@ export const TOKENS = { export const POOLS: Pool[] = [ { pair: "WBTC-USDC", amount: 5000, price: 20000, deposit: false, deploy: false, rate: "50000000000000000" }, { pair: "WBTC-DAI", amount: 5000, price: 20000, deposit: false, deploy: false, rate: "50000000000000000" }, - { pair: "USDC-WBTC", amount: 1, price: 0.000030769, deposit: false, deploy: false, rate: "40000000000000000" }, + { pair: "USDC-WBTC", amount: 1, price: 0.000030769, deposit: false, deploy: true, rate: "40000000000000000" }, { pair: "WETH-USDC", amount: 5000, price: 1500, deposit: false, deploy: true, rate: "50000000000000000" }, { pair: "WETH-DAI", amount: 5000, price: 1500, deposit: false, deploy: false, rate: "50000000000000000" }, { pair: "USDC-WETH", amount: 1, price: 0.0005, deposit: false, deploy: false, rate: "40000000000000000" }, @@ -79,7 +79,7 @@ export const POOLS: Pool[] = [ { pair: "WSTETH-DAI", amount: 5000, price: 1500, deposit: false, deploy: false, rate: "50000000000000000" }, { pair: "WSTETH-USDC", amount: 5000, price: 1500, deposit: false, deploy: false, rate: "50000000000000000" }, { pair: "WSTETH-WETH", amount: 1, price: 1.09, deposit: false, deploy: false, rate: "40000000000000000" }, - { pair: "CBETH-WETH", amount: 1, price: 1.09, deposit: false, deploy: true, rate: "40000000000000000" }, + { pair: "CBETH-WETH", amount: 1, price: 1.09, deposit: false, deploy: false, rate: "40000000000000000" }, { pair: "TBTC-WBTC", amount: 1, price: 1.01, deposit: false, deploy: false, rate: "40000000000000000" }, ]; diff --git a/packages/deploy-configurations/configs/hardhat.conf.ts b/packages/deploy-configurations/configs/hardhat.conf.ts index 7a812f7ed..8d22dd535 100644 --- a/packages/deploy-configurations/configs/hardhat.conf.ts +++ b/packages/deploy-configurations/configs/hardhat.conf.ts @@ -1178,7 +1178,7 @@ export const config: SystemConfig = { }, AjnaProxyActions: { name: 'AjnaProxyActions', - address: '0xFBcB0bf3A7BcD1a368e8e8Ad2Ab601160088b39C', + address: '0x53B1f1B3f34b5B3C7dA8BD60a7E8ee2eFd175603', }, AjnaPoolPairs_ETHDAI: { name: 'AjnaPoolPairs_ETHDAI', diff --git a/packages/deploy-configurations/configs/mainnet.conf.ts b/packages/deploy-configurations/configs/mainnet.conf.ts index 10eda9b79..4af542bfe 100644 --- a/packages/deploy-configurations/configs/mainnet.conf.ts +++ b/packages/deploy-configurations/configs/mainnet.conf.ts @@ -1204,7 +1204,7 @@ export const config: SystemConfig = { }, AjnaProxyActions: { name: 'AjnaProxyActions', - address: '0xFBcB0bf3A7BcD1a368e8e8Ad2Ab601160088b39C', + address: '0x53B1f1B3f34b5B3C7dA8BD60a7E8ee2eFd175603', }, AjnaPoolPairs_ETHDAI: { name: 'AjnaPoolPairs_ETHDAI', diff --git a/packages/deploy-configurations/configs/test/mainnet.conf.ts b/packages/deploy-configurations/configs/test/mainnet.conf.ts index 93465264b..8f46cc1a9 100644 --- a/packages/deploy-configurations/configs/test/mainnet.conf.ts +++ b/packages/deploy-configurations/configs/test/mainnet.conf.ts @@ -989,7 +989,7 @@ export const config: SystemConfig = { }, AjnaProxyActions: { name: 'AjnaProxyActions', - address: '0xFBcB0bf3A7BcD1a368e8e8Ad2Ab601160088b39C', + address: '0x53B1f1B3f34b5B3C7dA8BD60a7E8ee2eFd175603', }, AjnaPoolPairs_ETHDAI: { name: 'AjnaPoolPairs_ETHDAI', From 8276645acd418995b07a65a7ec4272a7c9e51128 Mon Sep 17 00:00:00 2001 From: halaprix Date: Mon, 4 Dec 2023 15:50:01 +0100 Subject: [PATCH 6/6] chore: fix private key in base --- packages/ajna-contracts/hardhat.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ajna-contracts/hardhat.config.ts b/packages/ajna-contracts/hardhat.config.ts index c08352018..5ef472305 100644 --- a/packages/ajna-contracts/hardhat.config.ts +++ b/packages/ajna-contracts/hardhat.config.ts @@ -33,7 +33,7 @@ const config: HardhatUserConfig = { // initialBaseFeePerGas: 1000000000, // gasPrice: 45000000000, // 45 gwei // FIXME: uncomment when we have the env variable properly set up in gh actions - accounts: [process.env.PRIV_KEY_BASE || ""], + // accounts: [process.env.PRIV_KEY_BASE || ""], }, goerli: { url: process.env.GOERLI_URL || "",