From 2aca3a066de58c310a3cf51974ae24f0642050b6 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Thu, 4 May 2023 18:56:48 +0800 Subject: [PATCH 01/99] feat: improve auto compound Signed-off-by: GopherJ --- contracts/interfaces/IPoolApeStaking.sol | 12 +- contracts/protocol/pool/PoolApeStaking.sol | 151 +++++++++++------- .../libraries/ApeStakingLogic.sol | 8 +- 3 files changed, 106 insertions(+), 65 deletions(-) diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 2b3ad94b8..7fb8fc603 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -100,11 +100,15 @@ interface IPoolApeStaking { * @param nftAsset Contract address of BAYC/MAYC * @param users array of user address * @param tokenIds array of user tokenId array + * @param minUsdcApePrice minimum usdc/ape price + * @param minWethApePrice minimum weth/ape price */ function claimApeAndCompound( address nftAsset, address[] calldata users, - uint256[][] calldata tokenIds + uint256[][] calldata tokenIds, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) external; /** @@ -112,11 +116,15 @@ interface IPoolApeStaking { * @param nftAsset Contract address of BAYC/MAYC * @param users array of user address * @param _nftPairs Array of Paired BAYC/MAYC NFT's + * @param minUsdcApePrice minimum usdc/ape price + * @param minWethApePrice minimum weth/ape price */ function claimPairedApeAndCompound( address nftAsset, address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs + ApeCoinStaking.PairNft[][] calldata _nftPairs, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) external; /** diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 99273a952..59a201bb2 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -11,6 +11,7 @@ import "../../interfaces/IXTokenType.sol"; import "../../interfaces/INTokenApeStaking.sol"; import {ValidationLogic} from "../libraries/logic/ValidationLogic.sol"; import {IPoolAddressesProvider} from "../../interfaces/IPoolAddressesProvider.sol"; +import {IPool} from "../../interfaces/IPool.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; import {ReserveLogic} from "../libraries/logic/ReserveLogic.sol"; import {GenericLogic} from "../libraries/logic/GenericLogic.sol"; @@ -48,11 +49,11 @@ contract PoolApeStaking is IERC20 internal immutable USDC; ISwapRouter internal immutable SWAP_ROUTER; - uint256 internal constant DEFAULT_MAX_SLIPPAGE = 500; // 5% + uint256 internal constant DEFAULT_MAX_SLIPPAGE = 300; // 3% uint24 internal immutable APE_WETH_FEE; uint24 internal immutable WETH_USDC_FEE; address internal immutable WETH; - address internal immutable APE_COMPOUND_TREASURY; + address internal immutable APE_COMPOUND_BOT; event ReserveUsedAsCollateralEnabled( address indexed reserve, @@ -70,10 +71,13 @@ contract PoolApeStaking is address[] transferredTokenOwners; DataTypes.ApeCompoundStrategy[] options; uint256 totalAmount; - uint256 totalNonDepositAmount; uint256 compoundFee; - bytes usdcSwapPath; - bytes wethSwapPath; + uint256 totalUsdcSwapAmount; + uint256 totalWethSwapAmount; + uint256 minUsdcApePrice; + uint256 minWethApePrice; + address pUSDCAddress; + address pWETHAddress; } /** @@ -89,12 +93,9 @@ contract PoolApeStaking is address weth, uint24 apeWethFee, uint24 wethUsdcFee, - address apeCompoundTreasury + address apeCompoundBot ) { - require( - apeCompoundTreasury != address(0), - Errors.ZERO_ADDRESS_NOT_VALID - ); + require(apeCompoundBot != address(0), Errors.ZERO_ADDRESS_NOT_VALID); ADDRESSES_PROVIDER = provider; APE_COMPOUND = apeCompound; APE_COIN = apeCoin; @@ -103,7 +104,7 @@ contract PoolApeStaking is WETH = weth; APE_WETH_FEE = apeWethFee; WETH_USDC_FEE = wethUsdcFee; - APE_COMPOUND_TREASURY = apeCompoundTreasury; + APE_COMPOUND_BOT = apeCompoundBot; } function getRevision() internal pure virtual override returns (uint256) { @@ -150,8 +151,6 @@ contract PoolApeStaking is ); } INTokenApeStaking(xTokenAddress).claimApeCoin(_nfts, msg.sender); - - _checkUserHf(ps, msg.sender, true); } /// @inheritdoc IPoolApeStaking @@ -455,12 +454,15 @@ contract PoolApeStaking is function claimApeAndCompound( address nftAsset, address[] calldata users, - uint256[][] calldata tokenIds + uint256[][] calldata tokenIds, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) external nonReentrant { require( users.length == tokenIds.length, Errors.INCONSISTENT_PARAMS_LENGTH ); + require(msg.sender == APE_COMPOUND_BOT, Errors.CALLER_NOT_OPERATOR); DataTypes.PoolStorage storage ps = poolStorage(); _checkSApeIsNotPaused(ps); @@ -469,6 +471,8 @@ contract PoolApeStaking is nftAsset, users.length ); + localVar.minUsdcApePrice = minUsdcApePrice; + localVar.minWethApePrice = minWethApePrice; for (uint256 i = 0; i < users.length; i++) { for (uint256 j = 0; j < tokenIds[i].length; j++) { @@ -494,12 +498,15 @@ contract PoolApeStaking is function claimPairedApeAndCompound( address nftAsset, address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs + ApeCoinStaking.PairNft[][] calldata _nftPairs, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) external nonReentrant { require( users.length == _nftPairs.length, Errors.INCONSISTENT_PARAMS_LENGTH ); + require(msg.sender == APE_COMPOUND_BOT, Errors.CALLER_NOT_OPERATOR); DataTypes.PoolStorage storage ps = poolStorage(); ApeStakingLocalVars memory localVar = _compoundCache( @@ -507,6 +514,8 @@ contract PoolApeStaking is nftAsset, users.length ); + localVar.minUsdcApePrice = minUsdcApePrice; + localVar.minWethApePrice = minWethApePrice; for (uint256 i = 0; i < _nftPairs.length; i++) { localVar.transferredTokenOwners = new address[]( @@ -596,7 +605,14 @@ contract PoolApeStaking is localVar.swapAmounts[i] = localVar.amounts[i].percentMul( localVar.options[i].swapPercent ); - localVar.totalNonDepositAmount += localVar.swapAmounts[i]; + if ( + localVar.options[i].swapTokenOut == + DataTypes.ApeCompoundTokenOut.USDC + ) { + localVar.totalUsdcSwapAmount += localVar.swapAmounts[i]; + } else { + localVar.totalWethSwapAmount += localVar.swapAmounts[i]; + } } } @@ -664,54 +680,83 @@ contract PoolApeStaking is ApeStakingLocalVars memory localVar, address[] calldata users ) internal { - if (localVar.totalAmount != localVar.totalNonDepositAmount) { - APE_COMPOUND.deposit( + { + uint256 totalSwapAmount = localVar.totalUsdcSwapAmount + + localVar.totalWethSwapAmount; + if (localVar.totalAmount > totalSwapAmount) { + APE_COMPOUND.deposit( + address(this), + localVar.totalAmount - totalSwapAmount + ); + } + } + + { + uint256 compoundFee = localVar + .totalAmount + .percentDiv( + PercentageMath.PERCENTAGE_FACTOR - localVar.compoundFee + ) + .percentMul(localVar.compoundFee); + if (compoundFee > 0) { + APE_COIN.safeTransfer(APE_COMPOUND_BOT, compoundFee); + } + } + + if (localVar.totalUsdcSwapAmount > 0) { + bytes memory usdcSwapPath = abi.encodePacked( + APE_COIN, + APE_WETH_FEE, + WETH, + WETH_USDC_FEE, + USDC + ); + localVar.pUSDCAddress = IPool(ADDRESSES_PROVIDER.getPool()) + .getReserveData(address(USDC)) + .xTokenAddress; + _swapAndSupplyForUser( + ps, + address(USDC), + localVar.totalUsdcSwapAmount, + usdcSwapPath, address(this), - localVar.totalAmount - localVar.totalNonDepositAmount + localVar.minUsdcApePrice ); } - uint256 compoundFee = localVar - .totalAmount - .percentDiv(PercentageMath.PERCENTAGE_FACTOR - localVar.compoundFee) - .percentMul(localVar.compoundFee); - if (compoundFee > 0) { - APE_COMPOUND.deposit(APE_COMPOUND_TREASURY, compoundFee); - } - uint256 usdcPrice = _getApeRelativePrice(address(USDC), 1E6); - uint256 wethPrice = _getApeRelativePrice(address(WETH), 1E18); - localVar.usdcSwapPath = abi.encodePacked( - APE_COIN, - APE_WETH_FEE, - WETH, - WETH_USDC_FEE, - USDC - ); - localVar.wethSwapPath = abi.encodePacked(APE_COIN, APE_WETH_FEE, WETH); + if (localVar.totalWethSwapAmount > 0) { + bytes memory wethSwapPath = abi.encodePacked( + APE_COIN, + APE_WETH_FEE, + WETH + ); + localVar.pWETHAddress = IPool(ADDRESSES_PROVIDER.getPool()) + .getReserveData(address(WETH)) + .xTokenAddress; + _swapAndSupplyForUser( + ps, + address(WETH), + localVar.totalWethSwapAmount, + wethSwapPath, + address(this), + localVar.minWethApePrice + ); + } for (uint256 i = 0; i < users.length; i++) { address swapTokenOut; - bytes memory swapPath; - uint256 price; if ( localVar.options[i].swapTokenOut == DataTypes.ApeCompoundTokenOut.USDC ) { - swapTokenOut = address(USDC); - swapPath = localVar.usdcSwapPath; - price = usdcPrice; + swapTokenOut = localVar.pUSDCAddress; } else { - swapTokenOut = address(WETH); - swapPath = localVar.wethSwapPath; - price = wethPrice; + swapTokenOut = localVar.pWETHAddress; } - _swapAndSupplyForUser( - ps, - swapTokenOut, - localVar.swapAmounts[i], - swapPath, + + IERC20(swapTokenOut).safeTransfer( users[i], - price + localVar.swapAmounts[i] ); _repayAndSupplyForUser( ps, @@ -805,12 +850,6 @@ contract PoolApeStaking is referralCode: 0 }) ); - Helpers.setAssetUsedAsCollateral( - userConfig, - ps._reserves, - asset, - onBehalfOf - ); } function _repayForUser( diff --git a/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol b/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol index 57c53e4f0..d0a7cd868 100644 --- a/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol +++ b/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol @@ -269,12 +269,6 @@ library ApeStakingLogic { tokenId ); - uint256 apeReward = _apeCoinStaking.pendingRewards( - poolId, - address(this), - tokenId - ); - (uint256 bakcTokenId, bool isPaired) = _apeCoinStaking.mainToBakc( poolId, tokenId @@ -288,6 +282,6 @@ library ApeStakingLogic { apeStakedAmount += bakcStakedAmount; } - return apeStakedAmount + apeReward; + return apeStakedAmount; } } From 09205a596e3c46afce0be7745096be35a0f12d80 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Thu, 4 May 2023 19:02:09 +0800 Subject: [PATCH 02/99] chore: fallback to onchain slippage Signed-off-by: GopherJ --- contracts/interfaces/IPoolApeStaking.sol | 12 ++---------- contracts/protocol/pool/PoolApeStaking.sol | 17 +++++++---------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 7fb8fc603..2b3ad94b8 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -100,15 +100,11 @@ interface IPoolApeStaking { * @param nftAsset Contract address of BAYC/MAYC * @param users array of user address * @param tokenIds array of user tokenId array - * @param minUsdcApePrice minimum usdc/ape price - * @param minWethApePrice minimum weth/ape price */ function claimApeAndCompound( address nftAsset, address[] calldata users, - uint256[][] calldata tokenIds, - uint256 minUsdcApePrice, - uint256 minWethApePrice + uint256[][] calldata tokenIds ) external; /** @@ -116,15 +112,11 @@ interface IPoolApeStaking { * @param nftAsset Contract address of BAYC/MAYC * @param users array of user address * @param _nftPairs Array of Paired BAYC/MAYC NFT's - * @param minUsdcApePrice minimum usdc/ape price - * @param minWethApePrice minimum weth/ape price */ function claimPairedApeAndCompound( address nftAsset, address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs, - uint256 minUsdcApePrice, - uint256 minWethApePrice + ApeCoinStaking.PairNft[][] calldata _nftPairs ) external; /** diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 59a201bb2..93a1f9e35 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -454,9 +454,7 @@ contract PoolApeStaking is function claimApeAndCompound( address nftAsset, address[] calldata users, - uint256[][] calldata tokenIds, - uint256 minUsdcApePrice, - uint256 minWethApePrice + uint256[][] calldata tokenIds ) external nonReentrant { require( users.length == tokenIds.length, @@ -471,8 +469,6 @@ contract PoolApeStaking is nftAsset, users.length ); - localVar.minUsdcApePrice = minUsdcApePrice; - localVar.minWethApePrice = minWethApePrice; for (uint256 i = 0; i < users.length; i++) { for (uint256 j = 0; j < tokenIds[i].length; j++) { @@ -498,9 +494,7 @@ contract PoolApeStaking is function claimPairedApeAndCompound( address nftAsset, address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs, - uint256 minUsdcApePrice, - uint256 minWethApePrice + ApeCoinStaking.PairNft[][] calldata _nftPairs ) external nonReentrant { require( users.length == _nftPairs.length, @@ -514,8 +508,6 @@ contract PoolApeStaking is nftAsset, users.length ); - localVar.minUsdcApePrice = minUsdcApePrice; - localVar.minWethApePrice = minWethApePrice; for (uint256 i = 0; i < _nftPairs.length; i++) { localVar.transferredTokenOwners = new address[]( @@ -711,6 +703,7 @@ contract PoolApeStaking is WETH_USDC_FEE, USDC ); + localVar.minUsdcApePrice = _getApeRelativePrice(address(USDC), 1E6); localVar.pUSDCAddress = IPool(ADDRESSES_PROVIDER.getPool()) .getReserveData(address(USDC)) .xTokenAddress; @@ -730,6 +723,10 @@ contract PoolApeStaking is APE_WETH_FEE, WETH ); + localVar.minWethApePrice = _getApeRelativePrice( + address(WETH), + 1E18 + ); localVar.pWETHAddress = IPool(ADDRESSES_PROVIDER.getPool()) .getReserveData(address(WETH)) .xTokenAddress; From 4935ac595cf86817bebfd2fa872ed5f3f137eb90 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Thu, 4 May 2023 19:52:41 +0800 Subject: [PATCH 03/99] feat: change p2p fee logic Signed-off-by: GopherJ --- contracts/apestaking/P2PPairStaking.sol | 33 ++++++++++--------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index 165eb17cf..12a3cdb5a 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -370,6 +370,7 @@ contract P2PPairStaking is external nonReentrant { + require(msg.sender == matchingOperator, "no permission to compound"); _claimForMatchedOrdersAndCompound(orderHashes); } @@ -380,24 +381,21 @@ contract P2PPairStaking is uint256 cApeExchangeRate = ICApe(cApe).getPooledApeByShares(WAD); uint256 _compoundFee = compoundFee; uint256 totalReward; - uint256 totalFeeShare; + uint256 totalFee; uint256 orderCounts = orderHashes.length; for (uint256 index = 0; index < orderCounts; index++) { bytes32 orderHash = orderHashes[index]; - ( - uint256 reward, - uint256 feeShare - ) = _claimForMatchedOrderAndCompound( - orderHash, - cApeExchangeRate, - _compoundFee - ); + (uint256 reward, uint256 fee) = _claimForMatchedOrderAndCompound( + orderHash, + cApeExchangeRate, + _compoundFee + ); totalReward += reward; - totalFeeShare += feeShare; + totalFee += fee; } if (totalReward > 0) { IAutoCompoundApe(cApe).deposit(address(this), totalReward); - _depositCApeShareForUser(address(this), totalFeeShare); + IERC20(apeCoin).safeTransfer(matchingOperator, totalFee); } } @@ -536,10 +534,9 @@ contract P2PPairStaking is return (0, 0); } + uint256 feeAmount = rewardAmount.percentMul(_compoundFee); + rewardAmount -= feeAmount; uint256 rewardShare = (rewardAmount * WAD) / cApeExchangeRate; - //compound fee - uint256 _compoundFeeShare = rewardShare.percentMul(_compoundFee); - rewardShare -= _compoundFeeShare; _depositCApeShareForUser( IERC721(_getApeNTokenAddress(order.apeToken)).ownerOf( @@ -556,9 +553,9 @@ contract P2PPairStaking is rewardShare.percentMul(order.apeCoinShare) ); - emit OrderClaimedAndCompounded(orderHash, rewardAmount); + emit OrderClaimedAndCompounded(orderHash, rewardAmount + feeAmount); - return (rewardAmount, _compoundFeeShare); + return (rewardAmount, feeAmount); } function _depositCApeShareForUser(address user, uint256 amount) internal { @@ -707,10 +704,6 @@ contract P2PPairStaking is } } - function claimCompoundFee(address receiver) external onlyOwner { - this.claimCApeReward(receiver); - } - function rescueERC20( address token, address to, From 35f9863f841d38726d52c29291b71606004a645b Mon Sep 17 00:00:00 2001 From: GopherJ Date: Sat, 6 May 2023 08:41:24 +0800 Subject: [PATCH 04/99] feat: seperate compoundBot & matchingOperator Signed-off-by: GopherJ --- contracts/apestaking/P2PPairStaking.sol | 11 ++++++++++- contracts/interfaces/IP2PPairStaking.sol | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index 12a3cdb5a..7546890ba 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -58,6 +58,7 @@ contract P2PPairStaking is uint256 private baycMatchedCap; uint256 private maycMatchedCap; uint256 private bakcMatchedCap; + address public compoundBot; constructor( address _bayc, @@ -370,7 +371,7 @@ contract P2PPairStaking is external nonReentrant { - require(msg.sender == matchingOperator, "no permission to compound"); + require(msg.sender == compoundBot, "no permission to compound"); _claimForMatchedOrdersAndCompound(orderHashes); } @@ -704,6 +705,14 @@ contract P2PPairStaking is } } + function setCompoundBot(address _compoundBot) external onlyOwner { + address oldValue = compoundBot; + if (oldValue != _compoundBot) { + compoundBot = _compoundBot; + emit CompoundBotUpdated(oldValue, _compoundBot); + } + } + function rescueERC20( address token, address to, diff --git a/contracts/interfaces/IP2PPairStaking.sol b/contracts/interfaces/IP2PPairStaking.sol index e617e5481..cb38631cd 100644 --- a/contracts/interfaces/IP2PPairStaking.sol +++ b/contracts/interfaces/IP2PPairStaking.sol @@ -101,6 +101,13 @@ interface IP2PPairStaking { **/ event CompoundFeeUpdated(uint256 oldFee, uint256 newFee); + /** + * @dev Emitted during setCompoundBot() + * @param oldBot The address of the old compound bot + * @param newBot The address of the new compound bot + **/ + event CompoundBotUpdated(address oldBot, address newBot); + /** * @notice Cancel a listing order, order canceled cannot be matched. * @param listingOrder the detail info of the order to be canceled From 807b96621ee7bc855b18e079a0b739fd55587392 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Sat, 6 May 2023 08:57:31 +0800 Subject: [PATCH 05/99] feat: put slippage offchain Signed-off-by: GopherJ --- contracts/interfaces/IPoolApeStaking.sol | 12 ++++- contracts/interfaces/IPoolParameters.sol | 14 +++++ .../protocol/libraries/types/DataTypes.sol | 2 + contracts/protocol/pool/PoolApeStaking.sol | 53 +++++++------------ contracts/protocol/pool/PoolParameters.sol | 10 ++++ 5 files changed, 56 insertions(+), 35 deletions(-) diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 2b3ad94b8..2d7be7d76 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -100,11 +100,15 @@ interface IPoolApeStaking { * @param nftAsset Contract address of BAYC/MAYC * @param users array of user address * @param tokenIds array of user tokenId array + * @param minUsdcApePrice minimum usdc ape relative price + * @param minWethApePrice minimum weth ape relative price */ function claimApeAndCompound( address nftAsset, address[] calldata users, - uint256[][] calldata tokenIds + uint256[][] calldata tokenIds, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) external; /** @@ -112,11 +116,15 @@ interface IPoolApeStaking { * @param nftAsset Contract address of BAYC/MAYC * @param users array of user address * @param _nftPairs Array of Paired BAYC/MAYC NFT's + * @param minUsdcApePrice minimum usdc ape relative price + * @param minWethApePrice minimum weth ape relative price */ function claimPairedApeAndCompound( address nftAsset, address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs + ApeCoinStaking.PairNft[][] calldata _nftPairs, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) external; /** diff --git a/contracts/interfaces/IPoolParameters.sol b/contracts/interfaces/IPoolParameters.sol index 2bbd6e7dd..be17782a1 100644 --- a/contracts/interfaces/IPoolParameters.sol +++ b/contracts/interfaces/IPoolParameters.sol @@ -31,6 +31,14 @@ interface IPoolParameters { **/ event ClaimApeForYieldIncentiveUpdated(uint256 oldValue, uint256 newValue); + /** + * @dev Emitted when the address of claim for yield incentive bot update + **/ + event ClaimApeForYieldIncentiveBotUpdated( + address oldValue, + address newValue + ); + /** * @notice Initializes a reserve, activating it, assigning an xToken and debt tokens and an * interest rate strategy @@ -136,6 +144,12 @@ interface IPoolParameters { */ function setClaimApeForCompoundFee(uint256 fee) external; + /** + * @notice undate compound bot for claim ape for compound + * @param bot new compound bot + */ + function setClaimApeForCompoundBot(address bot) external; + /** * @notice undate ape compound strategy * @param strategy new compound strategy diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index a527d07e5..0cec1d03a 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -403,6 +403,8 @@ library DataTypes { uint16 _apeCompoundFee; // Map of user's ape compound strategies mapping(address => ApeCompoundStrategy) _apeCompoundStrategies; + // Address of ape compound bot + address _apeCompoundBot; } struct ReserveConfigData { diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 93a1f9e35..3ca4c8120 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -49,11 +49,9 @@ contract PoolApeStaking is IERC20 internal immutable USDC; ISwapRouter internal immutable SWAP_ROUTER; - uint256 internal constant DEFAULT_MAX_SLIPPAGE = 300; // 3% uint24 internal immutable APE_WETH_FEE; uint24 internal immutable WETH_USDC_FEE; address internal immutable WETH; - address internal immutable APE_COMPOUND_BOT; event ReserveUsedAsCollateralEnabled( address indexed reserve, @@ -104,7 +102,6 @@ contract PoolApeStaking is WETH = weth; APE_WETH_FEE = apeWethFee; WETH_USDC_FEE = wethUsdcFee; - APE_COMPOUND_BOT = apeCompoundBot; } function getRevision() internal pure virtual override returns (uint256) { @@ -454,20 +451,24 @@ contract PoolApeStaking is function claimApeAndCompound( address nftAsset, address[] calldata users, - uint256[][] calldata tokenIds + uint256[][] calldata tokenIds, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) external nonReentrant { require( users.length == tokenIds.length, Errors.INCONSISTENT_PARAMS_LENGTH ); - require(msg.sender == APE_COMPOUND_BOT, Errors.CALLER_NOT_OPERATOR); DataTypes.PoolStorage storage ps = poolStorage(); + require(msg.sender == ps._apeCompoundBot, Errors.CALLER_NOT_OPERATOR); _checkSApeIsNotPaused(ps); ApeStakingLocalVars memory localVar = _compoundCache( ps, nftAsset, - users.length + users.length, + minUsdcApePrice, + minWethApePrice ); for (uint256 i = 0; i < users.length; i++) { @@ -494,19 +495,23 @@ contract PoolApeStaking is function claimPairedApeAndCompound( address nftAsset, address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs + ApeCoinStaking.PairNft[][] calldata _nftPairs, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) external nonReentrant { require( users.length == _nftPairs.length, Errors.INCONSISTENT_PARAMS_LENGTH ); - require(msg.sender == APE_COMPOUND_BOT, Errors.CALLER_NOT_OPERATOR); DataTypes.PoolStorage storage ps = poolStorage(); + require(msg.sender == ps._apeCompoundBot, Errors.CALLER_NOT_OPERATOR); ApeStakingLocalVars memory localVar = _compoundCache( ps, nftAsset, - users.length + users.length, + minUsdcApePrice, + minWethApePrice ); for (uint256 i = 0; i < _nftPairs.length; i++) { @@ -566,7 +571,9 @@ contract PoolApeStaking is function _compoundCache( DataTypes.PoolStorage storage ps, address nftAsset, - uint256 numUsers + uint256 numUsers, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) internal view returns (ApeStakingLocalVars memory localVar) { localVar = _generalCache(ps, nftAsset); localVar.balanceBefore = APE_COIN.balanceOf(address(this)); @@ -574,6 +581,8 @@ contract PoolApeStaking is localVar.swapAmounts = new uint256[](numUsers); localVar.options = new DataTypes.ApeCompoundStrategy[](numUsers); localVar.compoundFee = ps._apeCompoundFee; + localVar.minUsdcApePrice = minUsdcApePrice; + localVar.minWethApePrice = minWethApePrice; } function _addUserToCompoundCache( @@ -691,7 +700,7 @@ contract PoolApeStaking is ) .percentMul(localVar.compoundFee); if (compoundFee > 0) { - APE_COIN.safeTransfer(APE_COMPOUND_BOT, compoundFee); + APE_COIN.safeTransfer(ps._apeCompoundBot, compoundFee); } } @@ -703,7 +712,6 @@ contract PoolApeStaking is WETH_USDC_FEE, USDC ); - localVar.minUsdcApePrice = _getApeRelativePrice(address(USDC), 1E6); localVar.pUSDCAddress = IPool(ADDRESSES_PROVIDER.getPool()) .getReserveData(address(USDC)) .xTokenAddress; @@ -723,10 +731,6 @@ contract PoolApeStaking is APE_WETH_FEE, WETH ); - localVar.minWethApePrice = _getApeRelativePrice( - address(WETH), - 1E18 - ); localVar.pWETHAddress = IPool(ADDRESSES_PROVIDER.getPool()) .getReserveData(address(WETH)) .xTokenAddress; @@ -788,23 +792,6 @@ contract PoolApeStaking is _supplyForUser(ps, tokenOut, address(this), user, amountOut); } - function _getApeRelativePrice(address tokenOut, uint256 tokenOutUnit) - internal - view - returns (uint256) - { - IPriceOracleGetter oracle = IPriceOracleGetter( - ADDRESSES_PROVIDER.getPriceOracle() - ); - uint256 apePrice = oracle.getAssetPrice(address(APE_COIN)); - uint256 tokenOutPrice = oracle.getAssetPrice(tokenOut); - - return - ((apePrice * tokenOutUnit).wadDiv(tokenOutPrice * 1E18)).percentMul( - PercentageMath.PERCENTAGE_FACTOR - DEFAULT_MAX_SLIPPAGE - ); - } - function _repayAndSupplyForUser( DataTypes.PoolStorage storage ps, address asset, diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index ba9684940..429ea3089 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -253,6 +253,16 @@ contract PoolParameters is } } + /// @inheritdoc IPoolParameters + function setClaimApeForCompoundBot(address bot) external onlyPoolAdmin { + DataTypes.PoolStorage storage ps = poolStorage(); + address oldValue = ps._apeCompoundBot; + if (oldValue != bot) { + ps._apeCompoundBot = bot; + emit ClaimApeForYieldIncentiveBotUpdated(oldValue, bot); + } + } + /// @inheritdoc IPoolParameters function setApeCompoundStrategy( DataTypes.ApeCompoundStrategy calldata strategy From 7b42745b6eef87b6d58d509a8b4619fc3c389437 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Sat, 6 May 2023 09:02:19 +0800 Subject: [PATCH 06/99] fix: lint Signed-off-by: GopherJ --- test/auto_compound_ape.spec.ts | 20 ++++++++++++++++---- test/p2p_pair_staking.spec.ts | 13 ++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index e83b5aa36..8627278ab 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -543,6 +543,8 @@ describe("Auto Compound Ape Test", () => { mayc.address, [user1.address, user2.address, user3.address], [[0], [1], [2]], + 0, + 0, {gasLimit: 5000000} ) ); @@ -579,6 +581,8 @@ describe("Auto Compound Ape Test", () => { mayc.address, [user1.address, user2.address, user3.address], [[0], [1], [2]], + 0, + 0, {gasLimit: 5000000} ) ); @@ -641,7 +645,7 @@ describe("Auto Compound Ape Test", () => { await waitForTx( await pool .connect(user2.signer) - .claimApeAndCompound(mayc.address, [user1.address], [[0, 1, 2]], { + .claimApeAndCompound(mayc.address, [user1.address], [[0, 1, 2]], 0, 0, { gasLimit: 5000000, }) ); @@ -661,7 +665,7 @@ describe("Auto Compound Ape Test", () => { await waitForTx( await pool .connect(user2.signer) - .claimApeAndCompound(mayc.address, [user1.address], [[0, 1, 2]], { + .claimApeAndCompound(mayc.address, [user1.address], [[0, 1, 2]], 0, 0, { gasLimit: 5000000, }) ); @@ -801,6 +805,8 @@ describe("Auto Compound Ape Test", () => { mayc.address, [user1.address, user2.address, user3.address], [[0], [1], [2]], + 0, + 0, {gasLimit: 5000000} ) ); @@ -837,6 +843,8 @@ describe("Auto Compound Ape Test", () => { mayc.address, [user1.address, user2.address, user3.address], [[0], [1], [2]], + 0, + 0, {gasLimit: 5000000} ) ); @@ -931,7 +939,9 @@ describe("Auto Compound Ape Test", () => { {mainTokenId: 1, bakcTokenId: 1}, {mainTokenId: 2, bakcTokenId: 2}, ], - ] + ], + 0, + 0 ) ); @@ -957,7 +967,9 @@ describe("Auto Compound Ape Test", () => { {mainTokenId: 1, bakcTokenId: 1}, {mainTokenId: 2, bakcTokenId: 2}, ], - ] + ], + 0, + 0 ) ); }); diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index aee0665a1..6606df365 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -1046,6 +1046,11 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await p2pPairStaking.connect(gatewayAdmin.signer).setCompoundFee(50) ); + await waitForTx( + await p2pPairStaking + .connect(gatewayAdmin.signer) + .setCompoundBot(user1.address) + ); await supplyAndValidate(bayc, "1", user3, true); await mintAndValidate(ape, "1000000", user2); @@ -1116,13 +1121,7 @@ describe("P2P Pair Staking Test", () => { parseEther("2865.6") ); - await waitForTx( - await p2pPairStaking - .connect(gatewayAdmin.signer) - .claimCompoundFee(gatewayAdmin.address) - ); - - almostEqual(await cApe.balanceOf(gatewayAdmin.address), parseEther("18")); + almostEqual(await ape.balanceOf(user1.address), parseEther("18")); }); it("check ape token can be matched twice", async () => { From 6e1c2ddbebe1a2c4a00f553364e95e5ce1289885 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Sat, 6 May 2023 09:04:12 +0800 Subject: [PATCH 07/99] fix: typo Signed-off-by: GopherJ --- contracts/apestaking/P2PPairStaking.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index 7546890ba..dcbd6ee7e 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -396,7 +396,7 @@ contract P2PPairStaking is } if (totalReward > 0) { IAutoCompoundApe(cApe).deposit(address(this), totalReward); - IERC20(apeCoin).safeTransfer(matchingOperator, totalFee); + IERC20(apeCoin).safeTransfer(compoundBot, totalFee); } } From c686c377850cb26bfecfe358314bd1304faaec24 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Sat, 6 May 2023 09:54:32 +0800 Subject: [PATCH 08/99] chore: gas optimize Signed-off-by: GopherJ --- contracts/protocol/pool/PoolApeStaking.sol | 41 +++++++++------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 3ca4c8120..3ac840dda 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -70,6 +70,7 @@ contract PoolApeStaking is DataTypes.ApeCompoundStrategy[] options; uint256 totalAmount; uint256 compoundFee; + address compoundBot; uint256 totalUsdcSwapAmount; uint256 totalWethSwapAmount; uint256 minUsdcApePrice; @@ -460,9 +461,6 @@ contract PoolApeStaking is Errors.INCONSISTENT_PARAMS_LENGTH ); DataTypes.PoolStorage storage ps = poolStorage(); - require(msg.sender == ps._apeCompoundBot, Errors.CALLER_NOT_OPERATOR); - _checkSApeIsNotPaused(ps); - ApeStakingLocalVars memory localVar = _compoundCache( ps, nftAsset, @@ -470,6 +468,7 @@ contract PoolApeStaking is minUsdcApePrice, minWethApePrice ); + require(msg.sender == localVar.compoundBot, Errors.CALLER_NOT_OPERATOR); for (uint256 i = 0; i < users.length; i++) { for (uint256 j = 0; j < tokenIds[i].length; j++) { @@ -504,8 +503,6 @@ contract PoolApeStaking is Errors.INCONSISTENT_PARAMS_LENGTH ); DataTypes.PoolStorage storage ps = poolStorage(); - require(msg.sender == ps._apeCompoundBot, Errors.CALLER_NOT_OPERATOR); - ApeStakingLocalVars memory localVar = _compoundCache( ps, nftAsset, @@ -513,6 +510,7 @@ contract PoolApeStaking is minUsdcApePrice, minWethApePrice ); + require(msg.sender == localVar.compoundBot, Errors.CALLER_NOT_OPERATOR); for (uint256 i = 0; i < _nftPairs.length; i++) { localVar.transferredTokenOwners = new address[]( @@ -581,6 +579,7 @@ contract PoolApeStaking is localVar.swapAmounts = new uint256[](numUsers); localVar.options = new DataTypes.ApeCompoundStrategy[](numUsers); localVar.compoundFee = ps._apeCompoundFee; + localVar.compoundBot = ps._apeCompoundBot; localVar.minUsdcApePrice = minUsdcApePrice; localVar.minWethApePrice = minWethApePrice; } @@ -681,27 +680,21 @@ contract PoolApeStaking is ApeStakingLocalVars memory localVar, address[] calldata users ) internal { - { - uint256 totalSwapAmount = localVar.totalUsdcSwapAmount + - localVar.totalWethSwapAmount; - if (localVar.totalAmount > totalSwapAmount) { - APE_COMPOUND.deposit( - address(this), - localVar.totalAmount - totalSwapAmount - ); - } + uint256 totalSwapAmount = localVar.totalUsdcSwapAmount + + localVar.totalWethSwapAmount; + if (localVar.totalAmount > totalSwapAmount) { + APE_COMPOUND.deposit( + address(this), + localVar.totalAmount - totalSwapAmount + ); } - { - uint256 compoundFee = localVar - .totalAmount - .percentDiv( - PercentageMath.PERCENTAGE_FACTOR - localVar.compoundFee - ) - .percentMul(localVar.compoundFee); - if (compoundFee > 0) { - APE_COIN.safeTransfer(ps._apeCompoundBot, compoundFee); - } + uint256 compoundFee = localVar + .totalAmount + .percentDiv(PercentageMath.PERCENTAGE_FACTOR - localVar.compoundFee) + .percentMul(localVar.compoundFee); + if (compoundFee > 0) { + APE_COIN.safeTransfer(localVar.compoundBot, compoundFee); } if (localVar.totalUsdcSwapAmount > 0) { From cc60418b9a877b5205093ce1fa86d5a73010eb78 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Sat, 6 May 2023 10:02:05 +0800 Subject: [PATCH 09/99] fix: gas optimize Signed-off-by: GopherJ --- contracts/protocol/pool/PoolApeStaking.sol | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 3ac840dda..eb414ab99 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -705,9 +705,7 @@ contract PoolApeStaking is WETH_USDC_FEE, USDC ); - localVar.pUSDCAddress = IPool(ADDRESSES_PROVIDER.getPool()) - .getReserveData(address(USDC)) - .xTokenAddress; + localVar.pUSDCAddress = ps._reserves[address(USDC)].xTokenAddress; _swapAndSupplyForUser( ps, address(USDC), @@ -724,9 +722,7 @@ contract PoolApeStaking is APE_WETH_FEE, WETH ); - localVar.pWETHAddress = IPool(ADDRESSES_PROVIDER.getPool()) - .getReserveData(address(WETH)) - .xTokenAddress; + localVar.pWETHAddress = ps._reserves[address(WETH)].xTokenAddress; _swapAndSupplyForUser( ps, address(WETH), From 4270ed314f4396ffc182805dcda41e97fc1f0b69 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Sat, 6 May 2023 10:11:08 +0800 Subject: [PATCH 10/99] fix: check swapAmount before transfer Signed-off-by: GopherJ --- contracts/protocol/pool/PoolApeStaking.sol | 27 +++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index eb414ab99..cb9c53977 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -734,20 +734,21 @@ contract PoolApeStaking is } for (uint256 i = 0; i < users.length; i++) { - address swapTokenOut; - if ( - localVar.options[i].swapTokenOut == - DataTypes.ApeCompoundTokenOut.USDC - ) { - swapTokenOut = localVar.pUSDCAddress; - } else { - swapTokenOut = localVar.pWETHAddress; + if (localVar.swapAmounts[i] > 0) { + address swapTokenOut; + if ( + localVar.options[i].swapTokenOut == + DataTypes.ApeCompoundTokenOut.USDC + ) { + swapTokenOut = localVar.pUSDCAddress; + } else { + swapTokenOut = localVar.pWETHAddress; + } + IERC20(swapTokenOut).safeTransfer( + users[i], + localVar.swapAmounts[i] + ); } - - IERC20(swapTokenOut).safeTransfer( - users[i], - localVar.swapAmounts[i] - ); _repayAndSupplyForUser( ps, address(APE_COMPOUND), From a174e331f5733f906fa13929a9d4278203d1db9f Mon Sep 17 00:00:00 2001 From: GopherJ Date: Mon, 8 May 2023 09:07:07 +0800 Subject: [PATCH 11/99] fix: part of tests Signed-off-by: GopherJ --- contracts/protocol/pool/PoolApeStaking.sol | 26 +++++++++++--------- test/auto_compound_ape.spec.ts | 28 +++++++++------------- test/p2p_pair_staking.spec.ts | 17 ++++++------- 3 files changed, 33 insertions(+), 38 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index cb9c53977..9d1834c9a 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -73,8 +73,8 @@ contract PoolApeStaking is address compoundBot; uint256 totalUsdcSwapAmount; uint256 totalWethSwapAmount; - uint256 minUsdcApePrice; - uint256 minWethApePrice; + uint256 usdcApePrice; + uint256 wethApePrice; address pUSDCAddress; address pWETHAddress; } @@ -580,8 +580,8 @@ contract PoolApeStaking is localVar.options = new DataTypes.ApeCompoundStrategy[](numUsers); localVar.compoundFee = ps._apeCompoundFee; localVar.compoundBot = ps._apeCompoundBot; - localVar.minUsdcApePrice = minUsdcApePrice; - localVar.minWethApePrice = minWethApePrice; + localVar.usdcApePrice = minUsdcApePrice; + localVar.wethApePrice = minWethApePrice; } function _addUserToCompoundCache( @@ -706,13 +706,13 @@ contract PoolApeStaking is USDC ); localVar.pUSDCAddress = ps._reserves[address(USDC)].xTokenAddress; - _swapAndSupplyForUser( + localVar.usdcApePrice = _swapAndSupplyForUser( ps, address(USDC), localVar.totalUsdcSwapAmount, usdcSwapPath, address(this), - localVar.minUsdcApePrice + localVar.usdcApePrice ); } @@ -723,30 +723,33 @@ contract PoolApeStaking is WETH ); localVar.pWETHAddress = ps._reserves[address(WETH)].xTokenAddress; - _swapAndSupplyForUser( + localVar.wethApePrice = _swapAndSupplyForUser( ps, address(WETH), localVar.totalWethSwapAmount, wethSwapPath, address(this), - localVar.minWethApePrice + localVar.wethApePrice ); } for (uint256 i = 0; i < users.length; i++) { if (localVar.swapAmounts[i] > 0) { address swapTokenOut; + uint256 price; if ( localVar.options[i].swapTokenOut == DataTypes.ApeCompoundTokenOut.USDC ) { swapTokenOut = localVar.pUSDCAddress; + price = localVar.usdcApePrice; } else { swapTokenOut = localVar.pWETHAddress; + price = localVar.wethApePrice; } IERC20(swapTokenOut).safeTransfer( users[i], - localVar.swapAmounts[i] + localVar.swapAmounts[i].wadMul(price) ); } _repayAndSupplyForUser( @@ -766,9 +769,9 @@ contract PoolApeStaking is bytes memory swapPath, address user, uint256 price - ) internal { + ) internal returns (uint256) { if (amountIn == 0) { - return; + return price; } uint256 amountOut = SWAP_ROUTER.exactInput( ISwapRouter.ExactInputParams({ @@ -780,6 +783,7 @@ contract PoolApeStaking is }) ); _supplyForUser(ps, tokenOut, address(this), user, amountOut); + return amountOut.wadDiv(amountIn); } function _repayAndSupplyForUser( diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index 8627278ab..e4387f89f 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -19,11 +19,7 @@ import { getVariableDebtToken, } from "../helpers/contracts-getters"; import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; -import { - advanceTimeAndBlock, - getParaSpaceConfig, - waitForTx, -} from "../helpers/misc-utils"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {deployMockedDelegateRegistry} from "../helpers/contracts-deployments"; import {ETHERSCAN_VERIFICATION} from "../helpers/hardhat-constants"; import {convertToCurrencyDecimals} from "../helpers/contracts-helpers"; @@ -112,6 +108,12 @@ describe("Auto Compound Ape Test", () => { await pool.connect(poolAdmin.signer).setClaimApeForCompoundFee(30) ); + await waitForTx( + await pool + .connect(poolAdmin.signer) + .setClaimApeForCompoundBot(user2.address) + ); + // send extra tokens to the apestaking contract for rewards await waitForTx( await ape @@ -567,9 +569,7 @@ describe("Auto Compound Ape Test", () => { ); // 3600 * 0.003 - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const incentiveBalance = await cApe.balanceOf(treasuryAddress); + const incentiveBalance = await ape.balanceOf(user2.address); almostEqual(incentiveBalance, parseEther("10.8")); await advanceTimeAndBlock(3600); @@ -655,9 +655,7 @@ describe("Auto Compound Ape Test", () => { almostEqual(user1Balance, parseEther("3589.2")); // 3600 * 0.003 - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const incentiveBalance = await cApe.balanceOf(treasuryAddress); + const incentiveBalance = await ape.balanceOf(user2.address); almostEqual(incentiveBalance, parseEther("10.8")); await advanceTimeAndBlock(3600); @@ -829,9 +827,7 @@ describe("Auto Compound Ape Test", () => { ); // 3600 * 0.003 - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const incentiveBalance = await cApe.balanceOf(treasuryAddress); + const incentiveBalance = await ape.balanceOf(user2.address); almostEqual(incentiveBalance, parseEther("10.8")); await advanceTimeAndBlock(3600); @@ -950,9 +946,7 @@ describe("Auto Compound Ape Test", () => { almostEqual(user1Balance, parseEther("3589.2")); // 3600 * 0.003 - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const incentiveBalance = await cApe.balanceOf(treasuryAddress); + const incentiveBalance = await ape.balanceOf(user2.address); almostEqual(incentiveBalance, parseEther("10.8")); await advanceTimeAndBlock(3600); diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index 6606df365..52ff29573 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -22,7 +22,7 @@ describe("P2P Pair Staking Test", () => { const fixture = async () => { testEnv = await loadFixture(testEnvFixture); - const {ape, users, apeCoinStaking} = testEnv; + const {ape, users, apeCoinStaking, gatewayAdmin} = testEnv; const user1 = users[0]; const user2 = users[1]; @@ -60,6 +60,11 @@ describe("P2P Pair Staking Test", () => { .connect(user2.signer) .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) ); + await waitForTx( + await p2pPairStaking + .connect(gatewayAdmin.signer) + .setCompoundBot(user1.address) + ); return testEnv; }; @@ -1046,11 +1051,6 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await p2pPairStaking.connect(gatewayAdmin.signer).setCompoundFee(50) ); - await waitForTx( - await p2pPairStaking - .connect(gatewayAdmin.signer) - .setCompoundBot(user1.address) - ); await supplyAndValidate(bayc, "1", user3, true); await mintAndValidate(ape, "1000000", user2); @@ -1108,10 +1108,7 @@ describe("P2P Pair Staking Test", () => { .claimForMatchedOrderAndCompound([orderHash]) ); - almostEqual( - await p2pPairStaking.pendingCApeReward(p2pPairStaking.address), - parseEther("18") - ); + almostEqual(await ape.balanceOf(user1.address), parseEther("18")); almostEqual( await p2pPairStaking.pendingCApeReward(user3.address), parseEther("716.4") From ec2dc1cfe937ffd50fa08c0a3f7ee6265d17a595 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Mon, 8 May 2023 09:47:17 +0800 Subject: [PATCH 12/99] fix: auto compound tests Signed-off-by: GopherJ --- contracts/protocol/pool/PoolApeStaking.sol | 2 +- test/auto_compound_ape.spec.ts | 30 +++++++++++++++------- test/helpers/uniswapv3-helper.ts | 3 +-- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 9d1834c9a..aa4558dd9 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -783,7 +783,7 @@ contract PoolApeStaking is }) ); _supplyForUser(ps, tokenOut, address(this), user, amountOut); - return amountOut.wadDiv(amountIn); + return (amountOut.wadDiv(amountIn) / 10000) * 10000; } function _repayAndSupplyForUser( diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index e4387f89f..d05930a26 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -537,6 +537,7 @@ describe("Auto Compound Ape Test", () => { swapPercent: 5000, }) ); + const beforeBalance = await ape.balanceOf(user2.address); await waitForTx( await pool @@ -565,12 +566,14 @@ describe("Auto Compound Ape Test", () => { almostEqual( await pUsdc.balanceOf(user3.address), - await convertToCurrencyDecimals(usdc.address, "4059.235923") + await convertToCurrencyDecimals(usdc.address, "4050.668571") ); // 3600 * 0.003 - const incentiveBalance = await ape.balanceOf(user2.address); - almostEqual(incentiveBalance, parseEther("10.8")); + almostEqual( + (await ape.balanceOf(user2.address)).sub(beforeBalance), + parseEther("10.8") + ); await advanceTimeAndBlock(3600); @@ -642,6 +645,7 @@ describe("Auto Compound Ape Test", () => { await advanceTimeAndBlock(3600); + const beforeBalance = await ape.balanceOf(user2.address); await waitForTx( await pool .connect(user2.signer) @@ -655,8 +659,10 @@ describe("Auto Compound Ape Test", () => { almostEqual(user1Balance, parseEther("3589.2")); // 3600 * 0.003 - const incentiveBalance = await ape.balanceOf(user2.address); - almostEqual(incentiveBalance, parseEther("10.8")); + almostEqual( + (await ape.balanceOf(user2.address)).sub(beforeBalance), + parseEther("10.8") + ); await advanceTimeAndBlock(3600); @@ -796,6 +802,7 @@ describe("Auto Compound Ape Test", () => { }) ); + const beforeBalance = await ape.balanceOf(user2.address); await waitForTx( await pool .connect(user2.signer) @@ -827,8 +834,10 @@ describe("Auto Compound Ape Test", () => { ); // 3600 * 0.003 - const incentiveBalance = await ape.balanceOf(user2.address); - almostEqual(incentiveBalance, parseEther("10.8")); + almostEqual( + (await ape.balanceOf(user2.address)).sub(beforeBalance), + parseEther("10.8") + ); await advanceTimeAndBlock(3600); @@ -925,6 +934,7 @@ describe("Auto Compound Ape Test", () => { await advanceTimeAndBlock(3600); + const beforeBalance = await ape.balanceOf(user2.address); await waitForTx( await pool.connect(user2.signer).claimPairedApeAndCompound( mayc.address, @@ -946,8 +956,10 @@ describe("Auto Compound Ape Test", () => { almostEqual(user1Balance, parseEther("3589.2")); // 3600 * 0.003 - const incentiveBalance = await ape.balanceOf(user2.address); - almostEqual(incentiveBalance, parseEther("10.8")); + almostEqual( + (await ape.balanceOf(user2.address)).sub(beforeBalance), + parseEther("10.8") + ); await advanceTimeAndBlock(3600); diff --git a/test/helpers/uniswapv3-helper.ts b/test/helpers/uniswapv3-helper.ts index 5cee6b731..106aa1bde 100644 --- a/test/helpers/uniswapv3-helper.ts +++ b/test/helpers/uniswapv3-helper.ts @@ -8,12 +8,11 @@ import {expect} from "chai"; import {waitForTx} from "../../helpers/misc-utils"; import {MAX_UINT_AMOUNT} from "../../helpers/constants"; import {IUniswapV3Pool__factory} from "../../types"; -import {VERBOSE} from "../../helpers/hardhat-constants"; export function almostEqual(value0: BigNumberish, value1: BigNumberish) { const maxDiff = BigNumber.from(value0.toString()).div("1000").abs(); const abs = BigNumber.from(value0.toString()).sub(value1.toString()).abs(); - if (!abs.lte(maxDiff) && VERBOSE) { + if (!abs.lte(maxDiff)) { console.log("---------value0=" + value0 + ", --------value1=" + value1); } expect(abs.lte(maxDiff)).to.be.equal(true); From b6a3b371f7d62140780d85c9decbfb0e3d4837e6 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Mon, 8 May 2023 10:28:38 +0800 Subject: [PATCH 13/99] chore: cleanup Signed-off-by: GopherJ --- contracts/protocol/pool/PoolApeStaking.sol | 38 +++++++++++++--------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index aa4558dd9..1217a8bc6 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -464,9 +464,7 @@ contract PoolApeStaking is ApeStakingLocalVars memory localVar = _compoundCache( ps, nftAsset, - users.length, - minUsdcApePrice, - minWethApePrice + users.length ); require(msg.sender == localVar.compoundBot, Errors.CALLER_NOT_OPERATOR); @@ -487,7 +485,13 @@ contract PoolApeStaking is _addUserToCompoundCache(ps, localVar, i, users[i]); } - _compoundForUsers(ps, localVar, users); + _compoundForUsers( + ps, + localVar, + users, + minUsdcApePrice, + minWethApePrice + ); } /// @inheritdoc IPoolApeStaking @@ -506,9 +510,7 @@ contract PoolApeStaking is ApeStakingLocalVars memory localVar = _compoundCache( ps, nftAsset, - users.length, - minUsdcApePrice, - minWethApePrice + users.length ); require(msg.sender == localVar.compoundBot, Errors.CALLER_NOT_OPERATOR); @@ -550,7 +552,13 @@ contract PoolApeStaking is _addUserToCompoundCache(ps, localVar, i, users[i]); } - _compoundForUsers(ps, localVar, users); + _compoundForUsers( + ps, + localVar, + users, + minUsdcApePrice, + minWethApePrice + ); } function _generalCache(DataTypes.PoolStorage storage ps, address nftAsset) @@ -569,9 +577,7 @@ contract PoolApeStaking is function _compoundCache( DataTypes.PoolStorage storage ps, address nftAsset, - uint256 numUsers, - uint256 minUsdcApePrice, - uint256 minWethApePrice + uint256 numUsers ) internal view returns (ApeStakingLocalVars memory localVar) { localVar = _generalCache(ps, nftAsset); localVar.balanceBefore = APE_COIN.balanceOf(address(this)); @@ -580,8 +586,6 @@ contract PoolApeStaking is localVar.options = new DataTypes.ApeCompoundStrategy[](numUsers); localVar.compoundFee = ps._apeCompoundFee; localVar.compoundBot = ps._apeCompoundBot; - localVar.usdcApePrice = minUsdcApePrice; - localVar.wethApePrice = minWethApePrice; } function _addUserToCompoundCache( @@ -678,7 +682,9 @@ contract PoolApeStaking is function _compoundForUsers( DataTypes.PoolStorage storage ps, ApeStakingLocalVars memory localVar, - address[] calldata users + address[] calldata users, + uint256 minUsdcApePrice, + uint256 minWethApePrice ) internal { uint256 totalSwapAmount = localVar.totalUsdcSwapAmount + localVar.totalWethSwapAmount; @@ -712,7 +718,7 @@ contract PoolApeStaking is localVar.totalUsdcSwapAmount, usdcSwapPath, address(this), - localVar.usdcApePrice + minUsdcApePrice ); } @@ -729,7 +735,7 @@ contract PoolApeStaking is localVar.totalWethSwapAmount, wethSwapPath, address(this), - localVar.wethApePrice + minWethApePrice ); } From ffef685d3b905aa1af4015626c6ffd1b465241c7 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Mon, 8 May 2023 10:35:59 +0800 Subject: [PATCH 14/99] fix: remove unused bot params Signed-off-by: GopherJ --- contracts/protocol/pool/PoolApeStaking.sol | 4 +--- helpers/contracts-deployments.ts | 6 ------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 1217a8bc6..dce8faff3 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -91,10 +91,8 @@ contract PoolApeStaking is ISwapRouter uniswapV3SwapRouter, address weth, uint24 apeWethFee, - uint24 wethUsdcFee, - address apeCompoundBot + uint24 wethUsdcFee ) { - require(apeCompoundBot != address(0), Errors.ZERO_ADDRESS_NOT_VALID); ADDRESSES_PROVIDER = provider; APE_COMPOUND = apeCompound; APE_COIN = apeCoin; diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index c72568b88..e2bde388d 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -575,8 +575,6 @@ export const deployPoolApeStaking = async ( const allTokens = await getAllTokens(); - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; const poolApeStaking = (await withSaveAndVerify( new PoolApeStaking__factory(apeStakingLibraries, await getFirstSigner()), eContractid.PoolApeStakingImpl, @@ -589,7 +587,6 @@ export const deployPoolApeStaking = async ( allTokens.WETH.address, APE_WETH_FEE, WETH_USDC_FEE, - treasuryAddress, ], verify, false, @@ -879,8 +876,6 @@ export const deployPoolComponents = async ( poolMarketplaceSelectors )) as PoolMarketplace; - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; const poolApeStaking = allTokens.APE ? ((await withSaveAndVerify( new PoolApeStaking__factory( @@ -897,7 +892,6 @@ export const deployPoolComponents = async ( allTokens.WETH.address, APE_WETH_FEE, WETH_USDC_FEE, - treasuryAddress, ], verify, false, From 4f21916fa8784c622b1194ec54194967e522c911 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Mon, 8 May 2023 10:51:37 +0800 Subject: [PATCH 15/99] fix: increase precision Signed-off-by: GopherJ --- contracts/protocol/pool/PoolApeStaking.sol | 10 ++++++++-- test/auto_compound_ape.spec.ts | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index dce8faff3..d90b472fb 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -713,6 +713,7 @@ contract PoolApeStaking is localVar.usdcApePrice = _swapAndSupplyForUser( ps, address(USDC), + localVar.pUSDCAddress, localVar.totalUsdcSwapAmount, usdcSwapPath, address(this), @@ -730,6 +731,7 @@ contract PoolApeStaking is localVar.wethApePrice = _swapAndSupplyForUser( ps, address(WETH), + localVar.pWETHAddress, localVar.totalWethSwapAmount, wethSwapPath, address(this), @@ -753,7 +755,7 @@ contract PoolApeStaking is } IERC20(swapTokenOut).safeTransfer( users[i], - localVar.swapAmounts[i].wadMul(price) + (localVar.swapAmounts[i] * price) / 1e18 ); } _repayAndSupplyForUser( @@ -769,6 +771,7 @@ contract PoolApeStaking is function _swapAndSupplyForUser( DataTypes.PoolStorage storage ps, address tokenOut, + address xTokenAddress, uint256 amountIn, bytes memory swapPath, address user, @@ -786,8 +789,11 @@ contract PoolApeStaking is amountOutMinimum: amountIn.wadMul(price) }) ); + uint256 beforeBalance = IERC20(xTokenAddress).balanceOf(address(this)); _supplyForUser(ps, tokenOut, address(this), user, amountOut); - return (amountOut.wadDiv(amountIn) / 10000) * 10000; + return + ((IERC20(xTokenAddress).balanceOf(address(this)) - beforeBalance) * + 1e18) / amountIn; } function _repayAndSupplyForUser( diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index d05930a26..5d31e17f9 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -566,7 +566,7 @@ describe("Auto Compound Ape Test", () => { almostEqual( await pUsdc.balanceOf(user3.address), - await convertToCurrencyDecimals(usdc.address, "4050.668571") + await convertToCurrencyDecimals(usdc.address, "4059.235479") ); // 3600 * 0.003 From a372a1a3f479e8ff93f3b468e6eb4e5d44a5094f Mon Sep 17 00:00:00 2001 From: GopherJ Date: Mon, 8 May 2023 11:01:24 +0800 Subject: [PATCH 16/99] chore: use real price Signed-off-by: GopherJ --- test/auto_compound_ape.spec.ts | 42 ++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index 5d31e17f9..28c73beef 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -546,8 +546,8 @@ describe("Auto Compound Ape Test", () => { mayc.address, [user1.address, user2.address, user3.address], [[0], [1], [2]], - 0, - 0, + 3827874, + 3506149922170000, {gasLimit: 5000000} ) ); @@ -584,8 +584,8 @@ describe("Auto Compound Ape Test", () => { mayc.address, [user1.address, user2.address, user3.address], [[0], [1], [2]], - 0, - 0, + 3827874, + 3506149922170000, {gasLimit: 5000000} ) ); @@ -649,9 +649,16 @@ describe("Auto Compound Ape Test", () => { await waitForTx( await pool .connect(user2.signer) - .claimApeAndCompound(mayc.address, [user1.address], [[0, 1, 2]], 0, 0, { - gasLimit: 5000000, - }) + .claimApeAndCompound( + mayc.address, + [user1.address], + [[0, 1, 2]], + 3827874, + 3506149922170000, + { + gasLimit: 5000000, + } + ) ); //3600 * 0.997 = 3589.2 @@ -669,9 +676,16 @@ describe("Auto Compound Ape Test", () => { await waitForTx( await pool .connect(user2.signer) - .claimApeAndCompound(mayc.address, [user1.address], [[0, 1, 2]], 0, 0, { - gasLimit: 5000000, - }) + .claimApeAndCompound( + mayc.address, + [user1.address], + [[0, 1, 2]], + 3827874, + 3506149922170000, + { + gasLimit: 5000000, + } + ) ); }); @@ -810,8 +824,8 @@ describe("Auto Compound Ape Test", () => { mayc.address, [user1.address, user2.address, user3.address], [[0], [1], [2]], - 0, - 0, + 3827874, + 3506149922170000, {gasLimit: 5000000} ) ); @@ -848,8 +862,8 @@ describe("Auto Compound Ape Test", () => { mayc.address, [user1.address, user2.address, user3.address], [[0], [1], [2]], - 0, - 0, + 3827874, + 3506149922170000, {gasLimit: 5000000} ) ); From 5980e0069703f8ff5ab5c51b529ab31de39f1335 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Mon, 8 May 2023 22:04:13 +0800 Subject: [PATCH 17/99] chore: use swapPercent instead Signed-off-by: GopherJ --- contracts/protocol/pool/PoolApeStaking.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index d90b472fb..ab4982085 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -603,7 +603,7 @@ contract PoolApeStaking is localVar.totalAmount += localVar.amounts[i]; } - if (localVar.options[i].ty == DataTypes.ApeCompoundType.SwapAndSupply) { + if (localVar.options[i].swapPercent > 0) { localVar.swapAmounts[i] = localVar.amounts[i].percentMul( localVar.options[i].swapPercent ); From c33aee940c2a67f3ed29311ec609271a46612c0d Mon Sep 17 00:00:00 2001 From: GopherJ Date: Wed, 10 May 2023 16:00:57 +0800 Subject: [PATCH 18/99] chore: update verify docs & add shelljs Signed-off-by: GopherJ --- docs/ETHERSCAN-VERIFICATION.md | 102 +++++++++++++++++++++++++++++++++ helpers/contracts-helpers.ts | 20 +++++++ package.json | 1 + yarn.lock | 3 +- 4 files changed, 125 insertions(+), 1 deletion(-) diff --git a/docs/ETHERSCAN-VERIFICATION.md b/docs/ETHERSCAN-VERIFICATION.md index 485b35ce2..1ce6c481d 100644 --- a/docs/ETHERSCAN-VERIFICATION.md +++ b/docs/ETHERSCAN-VERIFICATION.md @@ -412,6 +412,19 @@ proxychains forge verify-contract 0x1Ba6891D74b3B1f84b3EdFa6538D99eE979E8B63 \ ## Oracle +### ParaSpaceOracle + +``` +proxychains forge verify-contract 0x075bC485a618873e7Fb356849Df30C0c1eDca2Bc \ + --chain-id 1 \ + --num-of-optimizations 800 \ + --watch \ + contracts/misc/ParaSpaceOracle.sol:ParaSpaceOracle \ + --constructor-args \ + $(cast abi-encode "constructor(address,address[],address[],address,address,uint256)" "0x45a35124749B061a29f91cc8ddf85606586dcf24" "["0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1","0x82aF49447D8a07e3bd95BD0d56f35241523fBab1","0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8","0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9","0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F","0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f","0x5979d7b546e38e414f7e9822514be443a4800529","0x912ce59144191c1204e64559fe8253a0e49e6548","0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a","0xf97f4df75117a78c1a5a0dbb814af92458539fb4","0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0","0x040d1edc9569d4bab2d15287dc5a4f10f56a56b8","0xba5ddd1f9d7f570dc94a51479a000e3bce967196","0x3082cc23568ea640225c2467653db90e9250aaa0","0xC36442b4a4522E871399CD717aBDD847Ab11FE88"]" "["0xc5c8e77b397e531b8ec06bfb0048328b30e9ecfb","0x639fe6ab55c921f74e7fac1ee960c0b6293ba612","0x50834f3163758fcc1df9973b6e91f0f0f0434ad3","0x3f3f5df88dc9f13eac63df89ec16ef6e7e25dde7","0x0809e3d38d1b4214958faf06d8b1b1a2b73f2ab8","0xd0c7101eacbb49f3decccc166d238410d6d46d57","0x230E0321Cf38F09e247e50Afc7801EA2351fe56F","0x912CE59144191C1204E64559FE8253a0e49E6548","0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a","0x86e53cf1b870786351da77a57575e79cb55812cb","0x9c917083fdb403ab5adbec26ee294f6ecada2720","0xbe5ea816870d11239c543f84b71439511d70b94f","0xad1d5344aade45f43e596773bcc4c423eabdd034","0x20d0fcab0ecfd078b036b6caf1fac69a6453b352","0xBc5ee94c86d9be81E99Cffd18050194E51B8B435"]" "0x0000000000000000000000000000000000000000" "0x0000000000000000000000000000000000000000" "100000000") \ + --compiler-version v0.8.10+commit.fc410830 +``` + ### CLFixedPriceSynchronicityPriceAdapter ``` @@ -530,3 +543,92 @@ proxychains forge verify-contract 0x3F736F58F3c51a7C92d8b6996B77Df19a0b5394F \ $(cast abi-encode "constructor(address,address)" "0x45a35124749B061a29f91cc8ddf85606586dcf24" "0x0000000000000000000000000000000000000000") \ --compiler-version v0.8.10+commit.fc410830 ``` + +## UI + +### UiPoolDataProvider + +``` +proxychains forge verify-contract 0x94bDD135ccC48fF0440D750300A4e4Ba9B216B3A \ + --chain-id 1 \ + --num-of-optimizations 800 \ + --watch \ + contracts/ui/UiPoolDataProvider.sol:UiPoolDataProvider \ + --constructor-args \ + $(cast abi-encode "constructor(address,address)" "0x50834f3163758fcc1df9973b6e91f0f0f0434ad3" "0x50834f3163758fcc1df9973b6e91f0f0f0434ad3") \ + --compiler-version v0.8.10+commit.fc410830 +``` + +### UiIncentiveDataProvider + +``` +proxychains forge verify-contract 0x94bDD135ccC48fF0440D750300A4e4Ba9B216B3A \ + --chain-id 1 \ + --num-of-optimizations 800 \ + --watch \ + contracts/ui/UiIncentiveDataProvider.sol:UiIncentiveDataProvider \ + --compiler-version v0.8.10+commit.fc410830 +``` + +### WETHGateway + +``` +proxychains forge verify-contract 0xCCEaDe52890f49C212B0f993d8a1096eD57Cf747 \ + --chain-id 1 \ + --num-of-optimizations 800 \ + --watch \ + contracts/ui/WETHGateway.sol:WETHGateway \ + --constructor-args \ + $(cast abi-encode "constructor(address,address)" "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1" "0x9E96e796493f630500B1769ff6232b407c8435A3") \ + --compiler-version v0.8.10+commit.fc410830 +``` + +## Seaport + +### Seaport + +``` +proxychains forge verify-contract 0x1B85e8E7a75Bc68e28823Ce7CCD3fAdEA551040c \ + --chain-id 1 \ + --num-of-optimizations 800 \ + --watch \ + contracts/dependencies/seaport/contracts/Seaport.sol:Seaport \ + --constructor-args \ + $(cast abi-encode "constructor(address)" "0x5C2e1E5F0F614C4C3443E98680130191de80dC93") \ + --compiler-version v0.8.10+commit.fc410830 +``` + +### SeaportAdapter + +``` +proxychains forge verify-contract 0xaE40779759Cc4Bf261f12C179A80df728c8d0c75 \ + --chain-id 1 \ + --num-of-optimizations 800 \ + --watch \ + contracts/misc/marketplaces/SeaportAdapter.sol:SeaportAdapter \ + --constructor-args \ + $(cast abi-encode "constructor(address)" "0x45a35124749B061a29f91cc8ddf85606586dcf24") \ + --compiler-version v0.8.10+commit.fc410830 +``` + +### Conduit + +``` +proxychains forge verify-contract 0x7A558886Fee0DeF217405C18cb19Eda213C72019 \ + --chain-id 1 \ + --num-of-optimizations 800 \ + --watch \ + contracts/dependencies/seaport/contracts/conduit/Conduit.sol:Conduit \ + --compiler-version v0.8.10+commit.fc410830 +``` + +### PausableZone + +``` +proxychains forge verify-contract 0x3EBf80B51A4f1560Ebc64937A326a809Eb86A5B4 \ + --chain-id 1 \ + --num-of-optimizations 800 \ + --watch \ + contracts/dependencies/seaport/contracts/zones/PausableZone.sol:PausableZone \ + --compiler-version v0.8.10+commit.fc410830 +``` diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 3954c2835..fba7d4646 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -123,6 +123,7 @@ import { FlashbotsBundleTransaction, } from "@flashbots/ethers-provider-bundle"; import {configureReservesByHelper, initReservesByHelper} from "./init-helpers"; +import shell from "shelljs"; export type ERC20TokenMap = {[symbol: string]: ERC20}; export type ERC721TokenMap = {[symbol: string]: ERC721}; @@ -1088,3 +1089,22 @@ export const initAndConfigureReserves = async ( console.log("configuring reserves"); await configureReservesByHelper(reserves, allTokenAddresses); }; + +export const exec = ( + cmd: string, + options: {fatal: boolean; silent: boolean} = {fatal: true, silent: true} +) => { + console.log(`$ ${cmd}`); + const res = shell.exec(cmd, options); + if (res.code !== 0) { + console.error("Error: Command failed with code", res.code); + console.log(res); + if (options.fatal) { + process.exit(1); + } + } + if (!options.silent) { + console.log(res.stdout.trim()); + } + return res; +}; diff --git a/package.json b/package.json index e75cc1c4c..f5de68122 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "prettier-plugin-solidity": "^1.0.0-alpha.53", "pretty-quick": "^3.1.1", "prompt-sync": "^4.2.0", + "shelljs": "^0.8.5", "solhint": "^3.3.6", "solidity-coverage": "^0.8.2", "solidity-docgen-forked": "^0.6.0-beta.29", diff --git a/yarn.lock b/yarn.lock index fe1765da6..1ef7c4d8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1463,6 +1463,7 @@ __metadata: prettier-plugin-solidity: ^1.0.0-alpha.53 pretty-quick: ^3.1.1 prompt-sync: ^4.2.0 + shelljs: ^0.8.5 solhint: ^3.3.6 solidity-coverage: ^0.8.2 solidity-docgen-forked: ^0.6.0-beta.29 @@ -13421,7 +13422,7 @@ __metadata: languageName: node linkType: hard -"shelljs@npm:0.8.5, shelljs@npm:^0.8.3": +"shelljs@npm:0.8.5, shelljs@npm:^0.8.3, shelljs@npm:^0.8.5": version: 0.8.5 resolution: "shelljs@npm:0.8.5" dependencies: From 922e77a7a23ae81231ef624cac3a38892e978fc3 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Tue, 16 May 2023 13:56:19 +0800 Subject: [PATCH 19/99] chore: update submodules Signed-off-by: GopherJ --- lib/ds-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ds-test b/lib/ds-test index cd98eff28..e282159d5 160000 --- a/lib/ds-test +++ b/lib/ds-test @@ -1 +1 @@ -Subproject commit cd98eff28324bfac652e63a239a60632a761790b +Subproject commit e282159d5170298eb2455a6c05280ab5a73a4ef0 From 3a3f07451e9f28157738c7927092fbe563b8dcad Mon Sep 17 00:00:00 2001 From: GopherJ Date: Tue, 16 May 2023 14:03:03 +0800 Subject: [PATCH 20/99] chore: add safe owner interface Signed-off-by: GopherJ --- contracts/interfaces/ISafe.sol | 18 ++++++++++++++++++ helpers/contracts-helpers.ts | 2 ++ 2 files changed, 20 insertions(+) create mode 100644 contracts/interfaces/ISafe.sol diff --git a/contracts/interfaces/ISafe.sol b/contracts/interfaces/ISafe.sol new file mode 100644 index 000000000..8fe0ebab7 --- /dev/null +++ b/contracts/interfaces/ISafe.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.10; + +interface ISafe { + function addOwnerWithThreshold(address owner, uint256 _threshold) external; + + function removeOwner( + address prevOwner, + address owner, + uint256 _threshold + ) external; + + function swapOwner( + address prevOwner, + address oldOwner, + address newOwner + ) external; +} diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index fba7d4646..489408dca 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -79,6 +79,7 @@ import { NTokenOtherdeed__factory, TimeLock__factory, P2PPairStaking__factory, + ISafe__factory, } from "../types"; import {HardhatRuntimeEnvironment, HttpNetworkConfig} from "hardhat/types"; import {getFirstSigner, getTimeLockExecutor} from "./contracts-getters"; @@ -906,6 +907,7 @@ export const decodeInputData = (data: string) => { ...NTokenOtherdeed__factory.abi, ...TimeLock__factory.abi, ...P2PPairStaking__factory.abi, + ...ISafe__factory.abi, ]; const decoder = new InputDataDecoder(ABI); From 52ca0b2fbf12a837deacc45670ef2227d4be2ebe Mon Sep 17 00:00:00 2001 From: GopherJ Date: Tue, 16 May 2023 14:10:37 +0800 Subject: [PATCH 21/99] chore: use https for ethereumjs-abi Signed-off-by: GopherJ --- package.json | 3 +++ yarn.lock | 39 +++------------------------------------ 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index f5de68122..d1ac692b9 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "artifacts", "types" ], + "resolutions": { + "ethereumjs-abi": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz" + }, "scripts": { "postinstall": "husky install", "prepack": "pinst --disable", diff --git a/yarn.lock b/yarn.lock index 1ef7c4d8d..f74227879 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3899,7 +3899,7 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.10.0, bn.js@npm:^4.11.0, bn.js@npm:^4.11.6, bn.js@npm:^4.11.8, bn.js@npm:^4.11.9, bn.js@npm:^4.8.0": +"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.0, bn.js@npm:^4.11.6, bn.js@npm:^4.11.8, bn.js@npm:^4.11.9": version: 4.12.0 resolution: "bn.js@npm:4.12.0" checksum: 39afb4f15f4ea537b55eaf1446c896af28ac948fdcf47171961475724d1bb65118cca49fa6e3d67706e4790955ec0e74de584e45c8f1ef89f46c812bee5b5a12 @@ -6554,29 +6554,9 @@ __metadata: languageName: node linkType: hard -"ethereumjs-abi@git+https://github.com/ethereumjs/ethereumjs-abi.git": +"ethereumjs-abi@https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz": version: 0.6.8 - resolution: "ethereumjs-abi@https://github.com/ethereumjs/ethereumjs-abi.git#commit=ee3994657fa7a427238e6ba92a84d0b529bbcde0" - dependencies: - bn.js: ^4.11.8 - ethereumjs-util: ^6.0.0 - checksum: ae074be0bb012857ab5d3ae644d1163b908a48dd724b7d2567cfde309dc72222d460438f2411936a70dc949dc604ce1ef7118f7273bd525815579143c907e336 - languageName: node - linkType: hard - -"ethereumjs-abi@npm:0.6.5": - version: 0.6.5 - resolution: "ethereumjs-abi@npm:0.6.5" - dependencies: - bn.js: ^4.10.0 - ethereumjs-util: ^4.3.0 - checksum: 3abdc79dc60614d30b1cefb5e6bfbdab3ca8252b4e742330544103f86d6e49a55921d9b8822a0a47fee3efd9dd2493ec93448b1869d82479a4c71a44001e8337 - languageName: node - linkType: hard - -"ethereumjs-abi@npm:0.6.8, ethereumjs-abi@npm:^0.6.8": - version: 0.6.8 - resolution: "ethereumjs-abi@npm:0.6.8" + resolution: "ethereumjs-abi@https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz" dependencies: bn.js: ^4.11.8 ethereumjs-util: ^6.0.0 @@ -6714,19 +6694,6 @@ __metadata: languageName: node linkType: hard -"ethereumjs-util@npm:^4.3.0": - version: 4.5.1 - resolution: "ethereumjs-util@npm:4.5.1" - dependencies: - bn.js: ^4.8.0 - create-hash: ^1.1.2 - elliptic: ^6.5.2 - ethereum-cryptography: ^0.1.3 - rlp: ^2.0.0 - checksum: ee91fbd29634d40cad9adf90f202158324c089bbc10b405d2ef139f4542090e6f76a616d16c601b52d6b5c5d59ddb6c8387cf60cc732884e732dad9a62b8a539 - languageName: node - linkType: hard - "ethereumjs-util@npm:^5.0.0, ethereumjs-util@npm:^5.0.1, ethereumjs-util@npm:^5.1.1, ethereumjs-util@npm:^5.1.2, ethereumjs-util@npm:^5.1.3, ethereumjs-util@npm:^5.1.5, ethereumjs-util@npm:^5.2.0": version: 5.2.1 resolution: "ethereumjs-util@npm:5.2.1" From 734ae74aa989f573316463718bff2bdeee5970a0 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Tue, 16 May 2023 16:46:00 +0800 Subject: [PATCH 22/99] chore: try fix ci Signed-off-by: GopherJ --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 052c360a8..008e772e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,11 @@ jobs: strategy: matrix: node: [18] - + env: + INFURA_KEY: ${{ secrets.INFURA_KEY }} + DEPLOYER_MNEMONIC: ${{ secrets.DEPLOYER_MNEMONIC }} + ETHERSCAN_KEY: ${{ secrets.ETHERSCAN_KEY }} + MOCHA_JOBS: 0 steps: - name: Checkout Repository uses: actions/checkout@v3 From 3322f6eca0558f86867dacf1b631b271c6021375 Mon Sep 17 00:00:00 2001 From: GopherJ Date: Fri, 19 May 2023 10:22:58 +0800 Subject: [PATCH 23/99] chore: add missing phantom enum items Signed-off-by: GopherJ --- contracts/interfaces/IXTokenType.sol | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/contracts/interfaces/IXTokenType.sol b/contracts/interfaces/IXTokenType.sol index 99addb96b..accf781fe 100644 --- a/contracts/interfaces/IXTokenType.sol +++ b/contracts/interfaces/IXTokenType.sol @@ -24,7 +24,17 @@ enum XTokenType { PTokenCAPE, NTokenOtherdeed, NTokenStakefish, - NTokenChromieSquiggle + NTokenChromieSquiggle, + PhantomData1, + PhantomData2, + PhantomData3, + PhantomData4, + PhantomData5, + PhantomData6, + PhantomData7, + PhantomData8, + PhantomData9, + PhantomData10 } interface IXTokenType { From 833bf75e18845dd9072cac06e63234e7062f0b57 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 13 Jun 2023 15:33:33 +0800 Subject: [PATCH 24/99] chore: remove matching operator feature --- contracts/apestaking/P2PPairStaking.sol | 18 +---- contracts/interfaces/IP2PPairStaking.sol | 13 ---- test/p2p_pair_staking.spec.ts | 87 ------------------------ 3 files changed, 3 insertions(+), 115 deletions(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index dcbd6ee7e..f2c385bbf 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -53,7 +53,7 @@ contract P2PPairStaking is mapping(bytes32 => MatchedOrder) public matchedOrders; mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; mapping(address => uint256) private cApeShareBalance; - address public matchingOperator; + address public __matchingOperator; uint256 public compoundFee; uint256 private baycMatchedCap; uint256 private maycMatchedCap; @@ -282,8 +282,7 @@ contract P2PPairStaking is address apeNTokenOwner = IERC721(apeNToken).ownerOf(order.apeTokenId); address nBakcOwner = IERC721(nBakc).ownerOf(order.bakcTokenId); require( - msg.sender == matchingOperator || - msg.sender == apeNTokenOwner || + msg.sender == apeNTokenOwner || msg.sender == order.apeCoinOfferer || (msg.sender == nBakcOwner && order.stakingType == StakingType.BAKCPairStaking), @@ -608,9 +607,7 @@ contract P2PPairStaking is "order already cancelled" ); - if ( - msg.sender != listingOrder.offerer && msg.sender != matchingOperator - ) { + if (msg.sender != listingOrder.offerer) { require( validateOrderSignature( listingOrder.offerer, @@ -684,15 +681,6 @@ contract P2PPairStaking is return this.onERC721Received.selector; } - function setMatchingOperator(address _matchingOperator) external onlyOwner { - require(_matchingOperator != address(0), "zero address"); - address oldOperator = matchingOperator; - if (oldOperator != _matchingOperator) { - matchingOperator = _matchingOperator; - emit MatchingOperatorUpdated(oldOperator, _matchingOperator); - } - } - function setCompoundFee(uint256 _compoundFee) external onlyOwner { require( _compoundFee < PercentageMath.HALF_PERCENTAGE_FACTOR, diff --git a/contracts/interfaces/IP2PPairStaking.sol b/contracts/interfaces/IP2PPairStaking.sol index cb38631cd..483951905 100644 --- a/contracts/interfaces/IP2PPairStaking.sol +++ b/contracts/interfaces/IP2PPairStaking.sol @@ -87,13 +87,6 @@ interface IP2PPairStaking { uint256 amount ); - /** - * @dev Emitted during setMatchingOperator() - * @param oldOperator The address of the old matching operator - * @param newOperator The address of the new matching operator - **/ - event MatchingOperatorUpdated(address oldOperator, address newOperator); - /** * @dev Emitted during setCompoundFee() * @param oldFee The value of the old compound fee @@ -174,10 +167,4 @@ interface IP2PPairStaking { function getApeCoinStakingCap(StakingType stakingType) external returns (uint256); - - /** - * @notice set a new matching operator, only owner can call this function - * @param _matchingOperator The address of the new matching operator - */ - function setMatchingOperator(address _matchingOperator) external; } diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index 52ff29573..575a30336 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -953,93 +953,6 @@ describe("P2P Pair Staking Test", () => { ); }); - it("matching operator work as expected", async () => { - const { - users: [user1, user2, user3, , user5], - gatewayAdmin, - bayc, - nBAYC, - ape, - } = await loadFixture(fixture); - - await expect( - p2pPairStaking.connect(user2.signer).setMatchingOperator(user5.address) - ).to.be.revertedWith("Ownable: caller is not the owner"); - - await waitForTx( - await p2pPairStaking - .connect(gatewayAdmin.signer) - .setMatchingOperator(user5.address) - ); - - await supplyAndValidate(bayc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - bayc, - 0, - 2000, - user3 - ); - user1SignedOrder.v = 0; - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - cApe, - 0, - 8000, - user2 - ); - user2SignedOrder.v = 0; - - await expect( - p2pPairStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.reverted; - - const txReceipt = await waitForTx( - await p2pPairStaking - .connect(user5.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ); - const logLength = txReceipt.logs.length; - const orderHash = txReceipt.logs[logLength - 1].data; - - await expect( - p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) - ).to.be.revertedWith("no permission to break up"); - - await advanceTimeAndBlock(parseInt("3600")); - - await waitForTx( - await p2pPairStaking.connect(user5.signer).breakUpMatchedOrder(orderHash) - ); - - expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); - almostEqual(await cApe.balanceOf(user2.address), apeAmount); - almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2880") - ); - }); - it("compound fee work as expected", async () => { const { users: [user1, user2, user3], From 8992d7707cb7153b72f73c4b35c44a5602752c2b Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 13 Jun 2023 15:53:11 +0800 Subject: [PATCH 25/99] chore: allow transfer to sender address --- contracts/apestaking/AutoYieldApe.sol | 7 ++++--- contracts/protocol/tokenization/PYieldToken.sol | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/contracts/apestaking/AutoYieldApe.sol b/contracts/apestaking/AutoYieldApe.sol index 842279822..61c2667a4 100644 --- a/contracts/apestaking/AutoYieldApe.sol +++ b/contracts/apestaking/AutoYieldApe.sol @@ -509,9 +509,10 @@ contract AutoYieldApe is address recipient, uint256 amount ) internal override { - require(sender != recipient, Errors.SENDER_SAME_AS_RECEIVER); - _updateYieldIndex(sender, -(amount.toInt256())); - _updateYieldIndex(recipient, amount.toInt256()); + if (sender != recipient) { + _updateYieldIndex(sender, -(amount.toInt256())); + _updateYieldIndex(recipient, amount.toInt256()); + } super._transfer(sender, recipient, amount); } } diff --git a/contracts/protocol/tokenization/PYieldToken.sol b/contracts/protocol/tokenization/PYieldToken.sol index ad2027dd6..0b68d6a63 100644 --- a/contracts/protocol/tokenization/PYieldToken.sol +++ b/contracts/protocol/tokenization/PYieldToken.sol @@ -84,9 +84,10 @@ contract PYieldToken is PToken { uint256 amount, bool validate ) internal override { - require(from != to, Errors.SENDER_SAME_AS_RECEIVER); - _updateUserIndex(from, -(amount.toInt256())); - _updateUserIndex(to, amount.toInt256()); + if (from != to) { + _updateUserIndex(from, -(amount.toInt256())); + _updateUserIndex(to, amount.toInt256()); + } super._transfer(from, to, amount, validate); } From efb8f0d9bbea85cd681cb1ec4916b6c928d5978d Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 13 Jun 2023 16:36:37 +0800 Subject: [PATCH 26/99] chore: add pause and ACL for P2P --- contracts/apestaking/P2PPairStaking.sol | 71 ++++++++++++++++++- contracts/interfaces/IP2PPairStaking.sol | 10 +++ helpers/contracts-deployments.ts | 2 + .../deployments/steps/23_renounceOwnership.ts | 19 +---- test/p2p_pair_staking.spec.ts | 4 +- 5 files changed, 84 insertions(+), 22 deletions(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index f2c385bbf..3a9aa9d19 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -13,6 +13,8 @@ import {IERC721} from "../dependencies/openzeppelin/contracts/IERC721.sol"; import {IERC20, SafeERC20} from "../dependencies/openzeppelin/contracts/SafeERC20.sol"; import {PercentageMath} from "../protocol/libraries/math/PercentageMath.sol"; import {SignatureChecker} from "../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; +import "../protocol/libraries/helpers/Errors.sol"; +import "../interfaces/IACLManager.sol"; contract P2PPairStaking is Initializable, @@ -59,6 +61,8 @@ contract P2PPairStaking is uint256 private maycMatchedCap; uint256 private bakcMatchedCap; address public compoundBot; + bool private paused; + IACLManager private immutable aclManager; constructor( address _bayc, @@ -69,7 +73,8 @@ contract P2PPairStaking is address _nBakc, address _apeCoin, address _cApe, - address _apeCoinStaking + address _apeCoinStaking, + address _aclManager ) { bayc = _bayc; mayc = _mayc; @@ -80,6 +85,7 @@ contract P2PPairStaking is apeCoin = _apeCoin; cApe = _cApe; apeCoinStaking = ApeCoinStaking(_apeCoinStaking); + aclManager = IACLManager(_aclManager); } function initialize() public initializer { @@ -681,7 +687,7 @@ contract P2PPairStaking is return this.onERC721Received.selector; } - function setCompoundFee(uint256 _compoundFee) external onlyOwner { + function setCompoundFee(uint256 _compoundFee) external onlyPoolAdmin { require( _compoundFee < PercentageMath.HALF_PERCENTAGE_FACTOR, "Fee Too High" @@ -699,14 +705,73 @@ contract P2PPairStaking is compoundBot = _compoundBot; emit CompoundBotUpdated(oldValue, _compoundBot); } + function claimCompoundFee(address receiver) external onlyPoolAdmin { + this.claimCApeReward(receiver); + } + + /** + * @notice Pauses the contract. Only pool admin or emergency admin can call this function + **/ + function pause() external onlyEmergencyOrPoolAdmin { + paused = true; + emit Paused(_msgSender()); + } + + /** + * @notice Unpause the contract. Only pool admin can call this function + **/ + function unpause() external onlyPoolAdmin { + paused = false; + emit Unpaused(_msgSender()); } function rescueERC20( address token, address to, uint256 amount - ) external onlyOwner { + ) external onlyPoolAdmin { IERC20(token).safeTransfer(to, amount); emit RescueERC20(token, to, amount); } + + modifier whenNotPaused() { + require(!paused, "paused"); + _; + } + + modifier whenPaused() { + require(paused, "not paused"); + _; + } + + /** + * @dev Only pool admin can call functions marked by this modifier. + **/ + modifier onlyPoolAdmin() { + _onlyPoolAdmin(); + _; + } + + /** + * @dev Only emergency or pool admin can call functions marked by this modifier. + **/ + modifier onlyEmergencyOrPoolAdmin() { + _onlyPoolOrEmergencyAdmin(); + _; + } + + function _onlyPoolAdmin() internal view { + require( + aclManager.isPoolAdmin(msg.sender), + Errors.CALLER_NOT_POOL_ADMIN + ); + } + + function _onlyPoolOrEmergencyAdmin() internal view { + require( + aclManager.isPoolAdmin(msg.sender) || + aclManager.isEmergencyAdmin(msg.sender), + Errors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN + ); + } } diff --git a/contracts/interfaces/IP2PPairStaking.sol b/contracts/interfaces/IP2PPairStaking.sol index 483951905..c7fa6c664 100644 --- a/contracts/interfaces/IP2PPairStaking.sol +++ b/contracts/interfaces/IP2PPairStaking.sol @@ -4,6 +4,16 @@ pragma solidity 0.8.10; import "../dependencies/openzeppelin/contracts/IERC20.sol"; interface IP2PPairStaking { + /** + * @dev Emitted when the pause is triggered by `account`. + */ + event Paused(address account); + + /** + * @dev Emitted when the pause is lifted by `account`. + */ + event Unpaused(address account); + enum StakingType { BAYCStaking, MAYCStaking, diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 52ec06ab4..efb8cbbd0 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -2653,6 +2653,7 @@ export const deployP2PPairStakingImpl = async (verify?: boolean) => { const apeCoinStaking = (await getContractAddressInDb(eContractid.ApeCoinStaking)) || (await deployApeCoinStaking(verify)).address; + const aclManager = await getACLManager(); const args = [ allTokens.BAYC.address, allTokens.MAYC.address, @@ -2663,6 +2664,7 @@ export const deployP2PPairStakingImpl = async (verify?: boolean) => { allTokens.APE.address, allTokens.cAPE.address, apeCoinStaking, + aclManager.address, ]; return withSaveAndVerify( diff --git a/scripts/deployments/steps/23_renounceOwnership.ts b/scripts/deployments/steps/23_renounceOwnership.ts index b7c23d182..96c9e0293 100644 --- a/scripts/deployments/steps/23_renounceOwnership.ts +++ b/scripts/deployments/steps/23_renounceOwnership.ts @@ -381,31 +381,16 @@ export const step_23 = async ( if (DRY_RUN) { const encodedData1 = p2pPairStakingProxy.interface.encodeFunctionData( "changeAdmin", - [paraSpaceAdminAddress] + [emergencyAdminAddresses[0]] ); await dryRunEncodedData(p2pPairStakingProxy.address, encodedData1); - if (gatewayAdminAddress !== paraSpaceAdminAddress) { - const encodedData2 = p2pPairStaking.interface.encodeFunctionData( - "transferOwnership", - [gatewayAdminAddress] - ); - await dryRunEncodedData(p2pPairStaking.address, encodedData2); - } } else { await waitForTx( await p2pPairStakingProxy.changeAdmin( - paraSpaceAdminAddress, + emergencyAdminAddresses[0], GLOBAL_OVERRIDES ) ); - if (gatewayAdminAddress !== paraSpaceAdminAddress) { - await waitForTx( - await p2pPairStaking.transferOwnership( - gatewayAdminAddress, - GLOBAL_OVERRIDES - ) - ); - } } console.timeEnd("transferring P2PPairStaking ownership..."); console.log(); diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index 575a30336..27a8ada71 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -956,13 +956,13 @@ describe("P2P Pair Staking Test", () => { it("compound fee work as expected", async () => { const { users: [user1, user2, user3], - gatewayAdmin, + poolAdmin, bayc, ape, } = await loadFixture(fixture); await waitForTx( - await p2pPairStaking.connect(gatewayAdmin.signer).setCompoundFee(50) + await p2pPairStaking.connect(poolAdmin.signer).setCompoundFee(50) ); await supplyAndValidate(bayc, "1", user3, true); From 4c51fac2442c728aaf2a3d0d94a0c9e39facf7fd Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 15 Jun 2023 15:11:36 +0800 Subject: [PATCH 27/99] chore: fix FUN-POOL-05: User can withdraw $APE without timeLock --- contracts/interfaces/INTokenApeStaking.sol | 6 +- contracts/interfaces/ITimeLock.sol | 2 + contracts/misc/TimeLock.sol | 13 +- .../protocol/libraries/helpers/Errors.sol | 1 + contracts/protocol/pool/PoolApeStaking.sol | 34 +++- contracts/protocol/tokenization/NToken.sol | 2 + .../protocol/tokenization/NTokenBAYC.sol | 21 ++- .../protocol/tokenization/NTokenMAYC.sol | 21 ++- .../protocol/tokenization/NTokenMoonBirds.sol | 1 + contracts/protocol/tokenization/PToken.sol | 2 + .../protocol/tokenization/PYieldToken.sol | 1 + .../libraries/ApeStakingLogic.sol | 120 +++++++++++-- .../deployments/steps/23_renounceOwnership.ts | 10 +- test/_pool_ape_staking.spec.ts | 167 +++++++++++++++++- test/p2p_pair_staking.spec.ts | 10 -- 15 files changed, 368 insertions(+), 43 deletions(-) diff --git a/contracts/interfaces/INTokenApeStaking.sol b/contracts/interfaces/INTokenApeStaking.sol index bfb1a64fa..ce03375fa 100644 --- a/contracts/interfaces/INTokenApeStaking.sol +++ b/contracts/interfaces/INTokenApeStaking.sol @@ -16,7 +16,8 @@ interface INTokenApeStaking { function withdrawApeCoin( ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient + address _recipient, + DataTypes.TimeLockParams memory _timeLockParams ) external; function depositBAKC( @@ -30,7 +31,8 @@ interface INTokenApeStaking { function withdrawBAKC( ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs, - address _apeRecipient + address _apeRecipient, + DataTypes.TimeLockParams memory _timeLockParams ) external; function unstakePositionAndRepay(uint256 tokenId, address unstaker) diff --git a/contracts/interfaces/ITimeLock.sol b/contracts/interfaces/ITimeLock.sol index 602cfb1d2..35829b7ba 100644 --- a/contracts/interfaces/ITimeLock.sol +++ b/contracts/interfaces/ITimeLock.sol @@ -74,6 +74,7 @@ interface ITimeLock { /** @dev Function to create a new time-lock agreement * @param assetType Type of the asset involved * @param actionType Type of action for the time-lock + * @param callerUnderlyingAsset Underlying asset of the caller if caller is xToken * @param asset Address of the asset * @param tokenIdsOrAmounts Array of token IDs or amounts * @param beneficiary Address of the beneficiary @@ -83,6 +84,7 @@ interface ITimeLock { function createAgreement( DataTypes.AssetType assetType, DataTypes.TimeLockActionType actionType, + address callerUnderlyingAsset, address asset, uint256[] memory tokenIdsOrAmounts, address beneficiary, diff --git a/contracts/misc/TimeLock.sol b/contracts/misc/TimeLock.sol index a0b42bde5..8aefbd52d 100644 --- a/contracts/misc/TimeLock.sol +++ b/contracts/misc/TimeLock.sol @@ -35,10 +35,14 @@ contract TimeLock is ITimeLock, ReentrancyGuardUpgradeable, IERC721Receiver { address private immutable wpunk; address private immutable Punk; - modifier onlyXToken(address asset) { + /** + * @dev Only POOL or callerTag asset's xToken can call functions marked by this modifier. + **/ + modifier onlyValidCaller(address callerUnderlyingAsset) { require( - msg.sender == POOL.getReserveXToken(asset), - Errors.CALLER_NOT_XTOKEN + msg.sender == address(POOL) || + msg.sender == POOL.getReserveXToken(callerUnderlyingAsset), + Errors.CALLER_NOT_ALLOWED ); _; } @@ -77,11 +81,12 @@ contract TimeLock is ITimeLock, ReentrancyGuardUpgradeable, IERC721Receiver { function createAgreement( DataTypes.AssetType assetType, DataTypes.TimeLockActionType actionType, + address callerUnderlyingAsset, address asset, uint256[] calldata tokenIdsOrAmounts, address beneficiary, uint48 releaseTime - ) external onlyXToken(asset) returns (uint256) { + ) external onlyValidCaller(callerUnderlyingAsset) returns (uint256) { require(beneficiary != address(0), "Beneficiary cant be zero address"); require(releaseTime > block.timestamp, "Release time not valid"); diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index a4b65827d..fa57c774b 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -133,4 +133,5 @@ library Errors { string public constant CALLER_NOT_OPERATOR = "138"; // The caller of the function is not operator string public constant INVALID_FEE_VALUE = "139"; // invalid fee rate value string public constant TOKEN_NOT_ALLOW_RESCUE = "140"; // token is not allow rescue + string public constant CALLER_NOT_ALLOWED = "141"; //The caller of the function is not allowed } diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index ab4982085..c3a524cfc 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -118,13 +118,29 @@ contract PoolApeStaking is DataTypes.ReserveData storage nftReserve = ps._reserves[nftAsset]; address xTokenAddress = nftReserve.xTokenAddress; INToken nToken = INToken(xTokenAddress); + uint256 totalWithdrawAmount = 0; for (uint256 index = 0; index < _nfts.length; index++) { require( nToken.ownerOf(_nfts[index].tokenId) == msg.sender, Errors.NOT_THE_OWNER ); + totalWithdrawAmount += _nfts[index].amount; } - INTokenApeStaking(xTokenAddress).withdrawApeCoin(_nfts, msg.sender); + + DataTypes.TimeLockParams memory timeLockParams = GenericLogic + .calculateTimeLockParams( + ps._reserves[address(APE_COIN)], + DataTypes.TimeLockFactorParams({ + assetType: DataTypes.AssetType.ERC20, + asset: address(APE_COIN), + amount: totalWithdrawAmount + }) + ); + INTokenApeStaking(xTokenAddress).withdrawApeCoin( + _nfts, + msg.sender, + timeLockParams + ); _checkUserHf(ps, msg.sender, true); } @@ -162,6 +178,7 @@ contract PoolApeStaking is uint256[] memory transferredTokenIds = new uint256[](_nftPairs.length); uint256 actualTransferAmount = 0; + uint256 totalWithdrawAmount = 0; for (uint256 index = 0; index < _nftPairs.length; index++) { require( @@ -187,11 +204,24 @@ contract PoolApeStaking is .bakcTokenId; actualTransferAmount++; } + + totalWithdrawAmount += _nftPairs[index].amount; } + DataTypes.TimeLockParams memory timeLockParams = GenericLogic + .calculateTimeLockParams( + ps._reserves[address(APE_COIN)], + DataTypes.TimeLockFactorParams({ + assetType: DataTypes.AssetType.ERC20, + asset: address(APE_COIN), + amount: totalWithdrawAmount + }) + ); + INTokenApeStaking(localVar.xTokenAddress).withdrawBAKC( _nftPairs, - msg.sender + msg.sender, + timeLockParams ); ////transfer BAKC back for user diff --git a/contracts/protocol/tokenization/NToken.sol b/contracts/protocol/tokenization/NToken.sol index 6385d3551..1332e8ec8 100644 --- a/contracts/protocol/tokenization/NToken.sol +++ b/contracts/protocol/tokenization/NToken.sol @@ -124,6 +124,7 @@ contract NToken is VersionedInitializable, MintableIncentivizedERC721, INToken { DataTypes.AssetType.ERC721, timeLockParams.actionType, underlyingAsset, + underlyingAsset, tokenIds, receiverOfUnderlying, timeLockParams.releaseTime @@ -175,6 +176,7 @@ contract NToken is VersionedInitializable, MintableIncentivizedERC721, INToken { DataTypes.AssetType.ERC721, timeLockParams.actionType, underlyingAsset, + underlyingAsset, tokenIds, target, timeLockParams.releaseTime diff --git a/contracts/protocol/tokenization/NTokenBAYC.sol b/contracts/protocol/tokenization/NTokenBAYC.sol index ada1f7324..1c67db808 100644 --- a/contracts/protocol/tokenization/NTokenBAYC.sol +++ b/contracts/protocol/tokenization/NTokenBAYC.sol @@ -6,6 +6,7 @@ import {NTokenApeStaking} from "./NTokenApeStaking.sol"; import {IPool} from "../../interfaces/IPool.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; +import {DataTypes} from "../libraries/types/DataTypes.sol"; /** * @title BAYC NToken @@ -53,9 +54,17 @@ contract NTokenBAYC is NTokenApeStaking { */ function withdrawApeCoin( ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient + address _recipient, + DataTypes.TimeLockParams memory _timeLockParams ) external onlyPool nonReentrant { - _apeCoinStaking.withdrawBAYC(_nfts, _recipient); + ApeStakingLogic.executeWithdrawBAYC( + POOL, + _ERC721Data.underlyingAsset, + _apeCoinStaking, + _nfts, + _recipient, + _timeLockParams + ); } /** @@ -97,13 +106,17 @@ contract NTokenBAYC is NTokenApeStaking { */ function withdrawBAKC( ApeCoinStaking.PairNftWithdrawWithAmount[] calldata _nftPairs, - address _apeRecipient + address _apeRecipient, + DataTypes.TimeLockParams memory _timeLockParams ) external onlyPool nonReentrant { ApeStakingLogic.withdrawBAKC( + POOL, + _ERC721Data.underlyingAsset, _apeCoinStaking, POOL_ID(), _nftPairs, - _apeRecipient + _apeRecipient, + _timeLockParams ); } diff --git a/contracts/protocol/tokenization/NTokenMAYC.sol b/contracts/protocol/tokenization/NTokenMAYC.sol index 47cbbabdf..e702430a0 100644 --- a/contracts/protocol/tokenization/NTokenMAYC.sol +++ b/contracts/protocol/tokenization/NTokenMAYC.sol @@ -6,6 +6,7 @@ import {NTokenApeStaking} from "./NTokenApeStaking.sol"; import {IPool} from "../../interfaces/IPool.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; +import {DataTypes} from "../libraries/types/DataTypes.sol"; /** * @title MAYC NToken @@ -53,9 +54,17 @@ contract NTokenMAYC is NTokenApeStaking { */ function withdrawApeCoin( ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient + address _recipient, + DataTypes.TimeLockParams memory _timeLockParams ) external onlyPool nonReentrant { - _apeCoinStaking.withdrawMAYC(_nfts, _recipient); + ApeStakingLogic.executeWithdrawMAYC( + POOL, + _ERC721Data.underlyingAsset, + _apeCoinStaking, + _nfts, + _recipient, + _timeLockParams + ); } /** @@ -97,13 +106,17 @@ contract NTokenMAYC is NTokenApeStaking { */ function withdrawBAKC( ApeCoinStaking.PairNftWithdrawWithAmount[] calldata _nftPairs, - address _apeRecipient + address _apeRecipient, + DataTypes.TimeLockParams memory _timeLockParams ) external onlyPool nonReentrant { ApeStakingLogic.withdrawBAKC( + POOL, + _ERC721Data.underlyingAsset, _apeCoinStaking, POOL_ID(), _nftPairs, - _apeRecipient + _apeRecipient, + _timeLockParams ); } diff --git a/contracts/protocol/tokenization/NTokenMoonBirds.sol b/contracts/protocol/tokenization/NTokenMoonBirds.sol index 3daa55f54..98fa21c6b 100644 --- a/contracts/protocol/tokenization/NTokenMoonBirds.sol +++ b/contracts/protocol/tokenization/NTokenMoonBirds.sol @@ -69,6 +69,7 @@ contract NTokenMoonBirds is NToken, IMoonBirdBase { DataTypes.AssetType.ERC721, timeLockParams.actionType, underlyingAsset, + underlyingAsset, tokenIds, receiverOfUnderlying, timeLockParams.releaseTime diff --git a/contracts/protocol/tokenization/PToken.sol b/contracts/protocol/tokenization/PToken.sol index 98923d54e..8bc1a7cbd 100644 --- a/contracts/protocol/tokenization/PToken.sol +++ b/contracts/protocol/tokenization/PToken.sol @@ -124,6 +124,7 @@ contract PToken is DataTypes.AssetType.ERC20, timeLockParams.actionType, _underlyingAsset, + _underlyingAsset, amounts, receiverOfUnderlying, timeLockParams.releaseTime @@ -225,6 +226,7 @@ contract PToken is DataTypes.AssetType.ERC20, timeLockParams.actionType, _underlyingAsset, + _underlyingAsset, amounts, target, timeLockParams.releaseTime diff --git a/contracts/protocol/tokenization/PYieldToken.sol b/contracts/protocol/tokenization/PYieldToken.sol index 0b68d6a63..bb031baad 100644 --- a/contracts/protocol/tokenization/PYieldToken.sol +++ b/contracts/protocol/tokenization/PYieldToken.sol @@ -68,6 +68,7 @@ contract PYieldToken is PToken { DataTypes.AssetType.ERC20, timeLockParams.actionType, _underlyingAsset, + _underlyingAsset, amounts, receiverOfUnderlying, timeLockParams.releaseTime diff --git a/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol b/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol index d0a7cd868..556a0c7a4 100644 --- a/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol +++ b/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol @@ -4,6 +4,7 @@ import {ApeCoinStaking} from "../../../dependencies/yoga-labs/ApeCoinStaking.sol import {IERC721} from "../../../dependencies/openzeppelin/contracts/IERC721.sol"; import {SafeERC20} from "../../../dependencies/openzeppelin/contracts/SafeERC20.sol"; import {IERC20} from "../../../dependencies/openzeppelin/contracts/IERC20.sol"; +import {ITimeLock} from "../../../interfaces/ITimeLock.sol"; import "../../../interfaces/IPool.sol"; import {DataTypes} from "../../libraries/types/DataTypes.sol"; import {PercentageMath} from "../../libraries/math/PercentageMath.sol"; @@ -30,6 +31,72 @@ library ApeStakingLogic { } event UnstakeApeIncentiveUpdated(uint256 oldValue, uint256 newValue); + /** + * @notice Withdraw staked ApeCoin from the BAYC pool. If withdraw is total staked amount, performs an automatic claim. + * @param _nfts Array of BAYC NFT's with staked amounts + * @param _recipient Address to send withdraw amount and claim to + */ + function executeWithdrawBAYC( + IPool POOL, + address underlyingAsset, + ApeCoinStaking _apeCoinStaking, + ApeCoinStaking.SingleNft[] calldata _nfts, + address _recipient, + DataTypes.TimeLockParams memory _timeLockParams + ) external { + if (_timeLockParams.releaseTime != 0) { + IERC20 ApeCoin = _apeCoinStaking.apeCoin(); + uint256 beforeBalance = ApeCoin.balanceOf(address(this)); + _apeCoinStaking.withdrawBAYC(_nfts, address(this)); + uint256 afterBalance = ApeCoin.balanceOf(address(this)); + + uint256 totalAmount = afterBalance - beforeBalance; + createApeCoinAgreement( + POOL, + underlyingAsset, + ApeCoin, + totalAmount, + _recipient, + _timeLockParams + ); + } else { + _apeCoinStaking.withdrawBAYC(_nfts, _recipient); + } + } + + /** + * @notice Withdraw staked ApeCoin from the MAYC pool. If withdraw is total staked amount, performs an automatic claim. + * @param _nfts Array of MAYC NFT's with staked amounts + * @param _recipient Address to send withdraw amount and claim to + */ + function executeWithdrawMAYC( + IPool POOL, + address underlyingAsset, + ApeCoinStaking _apeCoinStaking, + ApeCoinStaking.SingleNft[] calldata _nfts, + address _recipient, + DataTypes.TimeLockParams memory _timeLockParams + ) external { + if (_timeLockParams.releaseTime != 0) { + IERC20 ApeCoin = _apeCoinStaking.apeCoin(); + uint256 beforeBalance = ApeCoin.balanceOf(address(this)); + _apeCoinStaking.withdrawMAYC(_nfts, address(this)); + uint256 afterBalance = ApeCoin.balanceOf(address(this)); + + uint256 totalAmount = afterBalance - beforeBalance; + createApeCoinAgreement( + POOL, + underlyingAsset, + ApeCoin, + totalAmount, + _recipient, + _timeLockParams + ); + } else { + _apeCoinStaking.withdrawMAYC(_nfts, _recipient); + } + } + /** * @notice withdraw Ape coin staking position from ApeCoinStaking * @param _apeCoinStaking ApeCoinStaking contract address @@ -38,32 +105,65 @@ library ApeStakingLogic { * @param _apeRecipient the receiver of ape coin */ function withdrawBAKC( + IPool POOL, + address underlyingAsset, ApeCoinStaking _apeCoinStaking, uint256 poolId, ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs, - address _apeRecipient + address _apeRecipient, + DataTypes.TimeLockParams memory _timeLockParams ) external { ApeCoinStaking.PairNftWithdrawWithAmount[] memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( 0 ); - uint256 beforeBalance = _apeCoinStaking.apeCoin().balanceOf( - address(this) - ); + IERC20 ApeCoin = _apeCoinStaking.apeCoin(); + uint256 beforeBalance = ApeCoin.balanceOf(address(this)); if (poolId == BAYC_POOL_ID) { _apeCoinStaking.withdrawBAKC(_nftPairs, _otherPairs); } else { _apeCoinStaking.withdrawBAKC(_otherPairs, _nftPairs); } - uint256 afterBalance = _apeCoinStaking.apeCoin().balanceOf( - address(this) - ); + uint256 afterBalance = ApeCoin.balanceOf(address(this)); + + uint256 totalAmount = afterBalance - beforeBalance; + if (_timeLockParams.releaseTime != 0) { + createApeCoinAgreement( + POOL, + underlyingAsset, + ApeCoin, + totalAmount, + _apeRecipient, + _timeLockParams + ); + } else { + ApeCoin.safeTransfer(_apeRecipient, totalAmount); + } + } + + function createApeCoinAgreement( + IPool POOL, + address underlyingAsset, + IERC20 ApeCoin, + uint256 amount, + address recipient, + DataTypes.TimeLockParams memory timeLockParams + ) internal { + ITimeLock timeLock = POOL.TIME_LOCK(); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amount; - _apeCoinStaking.apeCoin().safeTransfer( - _apeRecipient, - afterBalance - beforeBalance + timeLock.createAgreement( + DataTypes.AssetType.ERC20, + timeLockParams.actionType, + underlyingAsset, + address(ApeCoin), + amounts, + recipient, + timeLockParams.releaseTime ); + ApeCoin.safeTransfer(address(timeLock), amount); } /** diff --git a/scripts/deployments/steps/23_renounceOwnership.ts b/scripts/deployments/steps/23_renounceOwnership.ts index 96c9e0293..f5232caa5 100644 --- a/scripts/deployments/steps/23_renounceOwnership.ts +++ b/scripts/deployments/steps/23_renounceOwnership.ts @@ -20,6 +20,7 @@ import { getContractAddressInDb, getParaSpaceAdmins, dryRunEncodedData, + getEthersSigners, } from "../../../helpers/contracts-helpers"; import {DRY_RUN, GLOBAL_OVERRIDES} from "../../../helpers/hardhat-constants"; import {waitForTx} from "../../../helpers/misc-utils"; @@ -378,18 +379,17 @@ export const step_23 = async ( const p2pPairStaking = await getP2PPairStaking(); const p2pPairStakingProxy = await getInitializableAdminUpgradeabilityProxy(p2pPairStaking.address); + const signers = await getEthersSigners(); + const adminAddress = signers[5].getAddress(); if (DRY_RUN) { const encodedData1 = p2pPairStakingProxy.interface.encodeFunctionData( "changeAdmin", - [emergencyAdminAddresses[0]] + adminAddress ); await dryRunEncodedData(p2pPairStakingProxy.address, encodedData1); } else { await waitForTx( - await p2pPairStakingProxy.changeAdmin( - emergencyAdminAddresses[0], - GLOBAL_OVERRIDES - ) + await p2pPairStakingProxy.changeAdmin(adminAddress, GLOBAL_OVERRIDES) ); } console.timeEnd("transferring P2PPairStaking ownership..."); diff --git a/test/_pool_ape_staking.spec.ts b/test/_pool_ape_staking.spec.ts index c88d9df4b..4e9584420 100644 --- a/test/_pool_ape_staking.spec.ts +++ b/test/_pool_ape_staking.spec.ts @@ -3,8 +3,10 @@ import {expect} from "chai"; import {MAX_UINT_AMOUNT, ZERO_ADDRESS, ONE_ADDRESS} from "../helpers/constants"; import { getAutoCompoundApe, + getPoolConfiguratorProxy, getPToken, getPTokenSApe, + getTimeLockProxy, getVariableDebtToken, } from "../helpers/contracts-getters"; import { @@ -24,13 +26,14 @@ import { supplyAndValidate, } from "./helpers/validated-steps"; import {almostEqual} from "./helpers/uniswapv3-helper"; -import {ProtocolErrors} from "../helpers/types"; +import {eContractid, ProtocolErrors} from "../helpers/types"; import {parseEther} from "ethers/lib/utils"; import { executeAcceptBidWithCredit, executeSeaportBuyWithCredit, } from "./helpers/marketplace-helper"; import {BigNumber} from "ethers"; +import {deployReserveTimeLockStrategy} from "../helpers/contracts-deployments"; describe("APE Coin Staking Test", () => { let testEnv: TestEnv; @@ -135,7 +138,7 @@ describe("APE Coin Staking Test", () => { return testEnv; }; - + /* it("TC-pool-ape-staking-01 test borrowApeAndStake: failed when borrow + cash < staking amount (revert expected)", async () => { const { users: [user1], @@ -2982,4 +2985,164 @@ describe("APE Coin Staking Test", () => { .data; expect(isUsingAsCollateral(configDataAfter, sApeReserveData.id)).true; }); +*/ + it("TC-pool-ape-staking-47 test mayc withdrawApeCoin should transfer ape coin to timelock contract", async () => { + const { + users: [user1], + ape, + mayc, + pool, + poolAdmin, + } = await loadFixture(fixture); + + //setup timelock strategy + const minThreshold = await convertToCurrencyDecimals(ape.address, "100000"); + const midThreshold = await convertToCurrencyDecimals(ape.address, "200000"); + const minTime = 5; + const midTime = 300; + const maxTime = 3600; + + const timeLockProxy = await getTimeLockProxy(); + const defaultStrategy = await deployReserveTimeLockStrategy( + eContractid.DefaultTimeLockStrategy + "ERC20", + pool.address, + minThreshold.toString(), + midThreshold.toString(), + minTime.toString(), + midTime.toString(), + maxTime.toString(), + midThreshold.mul(10).toString(), + (12 * 3600).toString(), + (24 * 3600).toString() + ); + const poolConfigurator = await getPoolConfiguratorProxy(); + await waitForTx( + await poolConfigurator + .connect(poolAdmin.signer) + .setReserveTimeLockStrategyAddress(ape.address, defaultStrategy.address) + ); + + await supplyAndValidate(mayc, "1", user1, true); + const amount = parseEther("10000"); + const totalAmount = parseEther("20000"); + await mintAndValidate(ape, "20000", user1); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: totalAmount, + }, + [{tokenId: 0, amount: amount}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + + await waitForTx( + await pool + .connect(user1.signer) + .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount}]) + ); + expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(amount); + + await waitForTx( + await pool + .connect(user1.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount, isUncommit: true}, + ]) + ); + expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(totalAmount); + + await advanceTimeAndBlock(10); + + await waitForTx(await timeLockProxy.connect(user1.signer).claim(["0"])); + expect(await ape.balanceOf(user1.address)).to.be.eq(amount); + + await waitForTx(await timeLockProxy.connect(user1.signer).claim(["1"])); + expect(await ape.balanceOf(user1.address)).to.be.eq(totalAmount); + expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(0); + }); + + it("TC-pool-ape-staking-48 test bayc withdrawApeCoin should transfer ape coin to timelock contract", async () => { + const { + users: [user1], + ape, + bayc, + pool, + poolAdmin, + } = await loadFixture(fixture); + + //setup timelock strategy + const minThreshold = await convertToCurrencyDecimals(ape.address, "100000"); + const midThreshold = await convertToCurrencyDecimals(ape.address, "200000"); + const minTime = 5; + const midTime = 300; + const maxTime = 3600; + + const timeLockProxy = await getTimeLockProxy(); + const defaultStrategy = await deployReserveTimeLockStrategy( + eContractid.DefaultTimeLockStrategy + "ERC20", + pool.address, + minThreshold.toString(), + midThreshold.toString(), + minTime.toString(), + midTime.toString(), + maxTime.toString(), + midThreshold.mul(10).toString(), + (12 * 3600).toString(), + (24 * 3600).toString() + ); + const poolConfigurator = await getPoolConfiguratorProxy(); + await waitForTx( + await poolConfigurator + .connect(poolAdmin.signer) + .setReserveTimeLockStrategyAddress(ape.address, defaultStrategy.address) + ); + + await supplyAndValidate(bayc, "1", user1, true); + const amount = parseEther("10000"); + const totalAmount = parseEther("20000"); + await mintAndValidate(ape, "20000", user1); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: totalAmount, + }, + [{tokenId: 0, amount: amount}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + + await waitForTx( + await pool + .connect(user1.signer) + .withdrawApeCoin(bayc.address, [{tokenId: 0, amount: amount}]) + ); + expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(amount); + + await waitForTx( + await pool + .connect(user1.signer) + .withdrawBAKC(bayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount, isUncommit: true}, + ]) + ); + expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(totalAmount); + + await advanceTimeAndBlock(10); + + await waitForTx(await timeLockProxy.connect(user1.signer).claim(["0"])); + expect(await ape.balanceOf(user1.address)).to.be.eq(amount); + + await waitForTx(await timeLockProxy.connect(user1.signer).claim(["1"])); + expect(await ape.balanceOf(user1.address)).to.be.eq(totalAmount); + expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(0); + }); }); diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index 27a8ada71..7ff1b0201 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -400,16 +400,6 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bakc, "1", user3, true); await mintAndValidate(ape, "1000000", user2); - await waitForTx( - await mayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) From 400fae00401c36b22cc208dfa85e37b3f2566d04 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 15 Jun 2023 16:17:16 +0800 Subject: [PATCH 28/99] chore: fix bakc owner check --- contracts/protocol/pool/PoolApeStaking.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index c3a524cfc..f0673f78c 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -902,7 +902,9 @@ contract PoolApeStaking is bakcOwner = localVar.bakcContract.ownerOf(tokenId); require( (userAddress == bakcOwner) || - (userAddress == INToken(localVar.bakcNToken).ownerOf(tokenId)), + (bakcOwner == localVar.bakcNToken && + userAddress == + INToken(localVar.bakcNToken).ownerOf(tokenId)), Errors.NOT_THE_BAKC_OWNER ); localVar.bakcContract.safeTransferFrom( From ad5856431b7c6ded43f9e95684f19321319c8708 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 15 Jun 2023 16:34:38 +0800 Subject: [PATCH 29/99] chore: cache storage variable to save gas --- contracts/protocol/tokenization/PYieldToken.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/contracts/protocol/tokenization/PYieldToken.sol b/contracts/protocol/tokenization/PYieldToken.sol index bb031baad..61259c0cd 100644 --- a/contracts/protocol/tokenization/PYieldToken.sol +++ b/contracts/protocol/tokenization/PYieldToken.sol @@ -59,6 +59,7 @@ contract PYieldToken is PToken { _burnScaled(from, receiverOfUnderlying, amount, index); if (receiverOfUnderlying != address(this)) { + address underlyingAsset = _underlyingAsset; if (timeLockParams.releaseTime != 0) { ITimeLock timeLock = POOL.TIME_LOCK(); uint256[] memory amounts = new uint256[](1); @@ -67,15 +68,15 @@ contract PYieldToken is PToken { timeLock.createAgreement( DataTypes.AssetType.ERC20, timeLockParams.actionType, - _underlyingAsset, - _underlyingAsset, + underlyingAsset, + underlyingAsset, amounts, receiverOfUnderlying, timeLockParams.releaseTime ); receiverOfUnderlying = address(timeLock); } - IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount); + IERC20(underlyingAsset).safeTransfer(receiverOfUnderlying, amount); } } @@ -130,17 +131,18 @@ contract PYieldToken is PToken { _updateUserIndex(account, 0); (uint256 freeYield, uint256 lockedYield) = _yieldAmount(account); if (freeYield > 0) { + address underlyingAsset = _underlyingAsset; _userPendingYield[account] = lockedYield; (address yieldUnderlying, address yieldToken) = IYieldInfo( - _underlyingAsset + underlyingAsset ).yieldToken(); uint256 liquidityIndex = POOL.getReserveNormalizedIncome( yieldUnderlying ); freeYield = freeYield.rayMul(liquidityIndex); if (freeYield > IERC20(yieldToken).balanceOf(address(this))) { - IAutoYieldApe(_underlyingAsset).claimFor(address(this)); + IAutoYieldApe(underlyingAsset).claimFor(address(this)); } IERC20(yieldToken).safeTransfer(account, freeYield); } From a21896ec70ddb3753eb222452c2da99e9b31aa47 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 15 Jun 2023 16:36:27 +0800 Subject: [PATCH 30/99] chore: small gas optimization --- contracts/apestaking/P2PPairStaking.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index 3a9aa9d19..1496d2595 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -550,14 +550,16 @@ contract P2PPairStaking is ), rewardShare.percentMul(order.apeShare) ); - _depositCApeShareForUser( - IERC721(nBakc).ownerOf(order.bakcTokenId), - rewardShare.percentMul(order.bakcShare) - ); _depositCApeShareForUser( order.apeCoinOfferer, rewardShare.percentMul(order.apeCoinShare) ); + if (order.stakingType == StakingType.BAKCPairStaking) { + _depositCApeShareForUser( + IERC721(nBakc).ownerOf(order.bakcTokenId), + rewardShare.percentMul(order.bakcShare) + ); + } emit OrderClaimedAndCompounded(orderHash, rewardAmount + feeAmount); From 8fbb200af4c7e9968e9b84a52eca643968754b0c Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 15 Jun 2023 16:44:06 +0800 Subject: [PATCH 31/99] chore: fix some code style --- contracts/apestaking/P2PPairStaking.sol | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index 1496d2595..67f7f3d05 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -106,22 +106,9 @@ contract P2PPairStaking is updateApeCoinStakingCap(); //approve ApeCoin for apeCoinStaking - uint256 allowance = IERC20(apeCoin).allowance( - address(this), - address(apeCoinStaking) - ); - if (allowance == 0) { - IERC20(apeCoin).safeApprove( - address(apeCoinStaking), - type(uint256).max - ); - } - + IERC20(apeCoin).safeApprove(address(apeCoinStaking), type(uint256).max); //approve ApeCoin for cApe - allowance = IERC20(apeCoin).allowance(address(this), cApe); - if (allowance == 0) { - IERC20(apeCoin).safeApprove(cApe, type(uint256).max); - } + IERC20(apeCoin).safeApprove(cApe, type(uint256).max); } function cancelListing(ListingOrder calldata listingOrder) @@ -149,7 +136,8 @@ contract P2PPairStaking is //2 check if orders can match require( - apeOrder.stakingType <= StakingType.MAYCStaking, + apeOrder.stakingType == StakingType.MAYCStaking || + apeOrder.stakingType == StakingType.BAYCStaking, "invalid stake type" ); require( @@ -513,7 +501,10 @@ contract P2PPairStaking is ) internal returns (uint256, uint256) { MatchedOrder memory order = matchedOrders[orderHash]; uint256 balanceBefore = IERC20(apeCoin).balanceOf(address(this)); - if (order.stakingType < StakingType.BAKCPairStaking) { + if ( + order.stakingType == StakingType.BAYCStaking || + order.stakingType == StakingType.MAYCStaking + ) { uint256[] memory _nfts = new uint256[](1); _nfts[0] = order.apeTokenId; if (order.stakingType == StakingType.BAYCStaking) { From 7ecd60ceb4490a7d23cae56d9e3f69d6d3f4cf3e Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 15 Jun 2023 17:38:14 +0800 Subject: [PATCH 32/99] chore: update some storage variable to immutable to save gas. --- contracts/apestaking/P2PPairStaking.sol | 55 +++++++++++-------------- helpers/contracts-deployments.ts | 8 +++- scripts/upgrade/P2PPairStaking.ts | 10 ++++- test/p2p_pair_staking.spec.ts | 18 +++++++- 4 files changed, 54 insertions(+), 37 deletions(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index 67f7f3d05..b0b5bb90a 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -49,6 +49,10 @@ contract P2PPairStaking is address internal immutable apeCoin; address internal immutable cApe; ApeCoinStaking internal immutable apeCoinStaking; + uint256 public immutable compoundFee; + uint256 private immutable baycMatchedCap; + uint256 private immutable maycMatchedCap; + uint256 private immutable bakcMatchedCap; bytes32 internal DOMAIN_SEPARATOR; mapping(bytes32 => ListingOrderStatus) public listingOrderStatus; @@ -56,10 +60,10 @@ contract P2PPairStaking is mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; mapping(address => uint256) private cApeShareBalance; address public __matchingOperator; - uint256 public compoundFee; - uint256 private baycMatchedCap; - uint256 private maycMatchedCap; - uint256 private bakcMatchedCap; + uint256 public __compoundFee; + uint256 private __baycMatchedCap; + uint256 private __maycMatchedCap; + uint256 private __bakcMatchedCap; address public compoundBot; bool private paused; IACLManager private immutable aclManager; @@ -74,7 +78,8 @@ contract P2PPairStaking is address _apeCoin, address _cApe, address _apeCoinStaking, - address _aclManager + address _aclManager, + uint256 _compoundFee ) { bayc = _bayc; mayc = _mayc; @@ -86,6 +91,18 @@ contract P2PPairStaking is cApe = _cApe; apeCoinStaking = ApeCoinStaking(_apeCoinStaking); aclManager = IACLManager(_aclManager); + compoundFee = _compoundFee; + + ( + , + ApeCoinStaking.PoolUI memory baycPool, + ApeCoinStaking.PoolUI memory maycPool, + ApeCoinStaking.PoolUI memory bakcPool + ) = apeCoinStaking.getPoolsUI(); + + baycMatchedCap = baycPool.currentTimeRange.capPerPosition; + maycMatchedCap = maycPool.currentTimeRange.capPerPosition; + bakcMatchedCap = bakcPool.currentTimeRange.capPerPosition; } function initialize() public initializer { @@ -103,7 +120,6 @@ contract P2PPairStaking is address(this) ) ); - updateApeCoinStakingCap(); //approve ApeCoin for apeCoinStaking IERC20(apeCoin).safeApprove(address(apeCoinStaking), type(uint256).max); @@ -402,19 +418,6 @@ contract P2PPairStaking is } } - function updateApeCoinStakingCap() public { - ( - , - ApeCoinStaking.PoolUI memory baycPool, - ApeCoinStaking.PoolUI memory maycPool, - ApeCoinStaking.PoolUI memory bakcPool - ) = apeCoinStaking.getPoolsUI(); - - baycMatchedCap = baycPool.currentTimeRange.capPerPosition; - maycMatchedCap = maycPool.currentTimeRange.capPerPosition; - bakcMatchedCap = bakcPool.currentTimeRange.capPerPosition; - } - function pendingCApeReward(address user) public view returns (uint256) { uint256 amount = 0; uint256 shareBalance = cApeShareBalance[user]; @@ -680,24 +683,14 @@ contract P2PPairStaking is return this.onERC721Received.selector; } - function setCompoundFee(uint256 _compoundFee) external onlyPoolAdmin { - require( - _compoundFee < PercentageMath.HALF_PERCENTAGE_FACTOR, - "Fee Too High" - ); - uint256 oldValue = compoundFee; - if (oldValue != _compoundFee) { - compoundFee = _compoundFee; - emit CompoundFeeUpdated(oldValue, _compoundFee); - } - } - function setCompoundBot(address _compoundBot) external onlyOwner { address oldValue = compoundBot; if (oldValue != _compoundBot) { compoundBot = _compoundBot; emit CompoundBotUpdated(oldValue, _compoundBot); } + } + function claimCompoundFee(address receiver) external onlyPoolAdmin { this.claimCApeReward(receiver); } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index efb8cbbd0..871052af3 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -2638,7 +2638,10 @@ export const deployAutoCompoundApeImplAndAssignItToProxy = async ( ); }; -export const deployP2PPairStakingImpl = async (verify?: boolean) => { +export const deployP2PPairStakingImpl = async ( + compoundFee: number, + verify?: boolean +) => { const allTokens = await getAllTokens(); const protocolDataProvider = await getProtocolDataProvider(); const nBAYC = ( @@ -2665,6 +2668,7 @@ export const deployP2PPairStakingImpl = async (verify?: boolean) => { allTokens.cAPE.address, apeCoinStaking, aclManager.address, + compoundFee, ]; return withSaveAndVerify( @@ -2676,7 +2680,7 @@ export const deployP2PPairStakingImpl = async (verify?: boolean) => { }; export const deployP2PPairStaking = async (verify?: boolean) => { - const p2pImplementation = await deployP2PPairStakingImpl(verify); + const p2pImplementation = await deployP2PPairStakingImpl(0, verify); const deployer = await getFirstSigner(); const deployerAddress = await deployer.getAddress(); diff --git a/scripts/upgrade/P2PPairStaking.ts b/scripts/upgrade/P2PPairStaking.ts index 042a37ff9..365705a43 100644 --- a/scripts/upgrade/P2PPairStaking.ts +++ b/scripts/upgrade/P2PPairStaking.ts @@ -7,9 +7,15 @@ import {dryRunEncodedData} from "../../helpers/contracts-helpers"; import {DRY_RUN, GLOBAL_OVERRIDES} from "../../helpers/hardhat-constants"; import {waitForTx} from "../../helpers/misc-utils"; -export const upgradeP2PPairStaking = async (verify = false) => { +export const upgradeP2PPairStaking = async ( + compoundFee: number, + verify = false +) => { console.time("deploy P2PPairStaking"); - const p2pPairStakingImpl = await deployP2PPairStakingImpl(verify); + const p2pPairStakingImpl = await deployP2PPairStakingImpl( + compoundFee, + verify + ); const p2pPairStaking = await getP2PPairStaking(); const p2pPairStakingProxy = await getInitializableAdminUpgradeabilityProxy( p2pPairStaking.address diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index 7ff1b0201..fe988b958 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -6,6 +6,7 @@ import {testEnvFixture} from "./helpers/setup-env"; import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; import { getAutoCompoundApe, + getInitializableAdminUpgradeabilityProxy, getP2PPairStaking, } from "../helpers/contracts-getters"; import {MAX_UINT_AMOUNT} from "../helpers/constants"; @@ -13,6 +14,12 @@ import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; import {parseEther} from "ethers/lib/utils"; import {almostEqual} from "./helpers/uniswapv3-helper"; +import {deployP2PPairStakingImpl} from "../helpers/contracts-deployments"; +import {DRY_RUN, GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; +import { + dryRunEncodedData, + getEthersSigners, +} from "../helpers/contracts-helpers"; describe("P2P Pair Staking Test", () => { let testEnv: TestEnv; @@ -945,14 +952,21 @@ describe("P2P Pair Staking Test", () => { it("compound fee work as expected", async () => { const { - users: [user1, user2, user3], + users: [user1, user2, user3, , user5], poolAdmin, bayc, ape, } = await loadFixture(fixture); + const p2pPairStakingImpl = await deployP2PPairStakingImpl(50); + const p2pPairStaking = await getP2PPairStaking(); + const p2pPairStakingProxy = await getInitializableAdminUpgradeabilityProxy( + p2pPairStaking.address + ); await waitForTx( - await p2pPairStaking.connect(poolAdmin.signer).setCompoundFee(50) + await p2pPairStakingProxy + .connect(user5.signer) + .upgradeTo(p2pPairStakingImpl.address, GLOBAL_OVERRIDES) ); await supplyAndValidate(bayc, "1", user3, true); From 16848c32c34916a9943814253604a3db4aefaae1 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 19 Jun 2023 10:09:33 +0800 Subject: [PATCH 33/99] chore: cache reward amount to avoid fetch from ApeCoinStaking twice. --- contracts/apestaking/AutoCompoundApe.sol | 36 ++++++++++---------- contracts/apestaking/base/CApe.sol | 43 +++++++++++++++++++++--- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/contracts/apestaking/AutoCompoundApe.sol b/contracts/apestaking/AutoCompoundApe.sol index b3cf96823..54401938c 100644 --- a/contracts/apestaking/AutoCompoundApe.sol +++ b/contracts/apestaking/AutoCompoundApe.sol @@ -54,7 +54,9 @@ contract AutoCompoundApe is /// @inheritdoc IAutoCompoundApe function deposit(address onBehalf, uint256 amount) external override { require(amount > 0, "zero amount"); - uint256 amountShare = getShareByPooledApe(amount); + + uint256 rewardAmount = _getRewardApeBalance(); + uint256 amountShare = _getShareByPooledApe(amount, rewardAmount); if (amountShare == 0) { amountShare = amount; // permanently lock the first MINIMUM_LIQUIDITY tokens to prevent getPooledApeByShares return 0 @@ -64,7 +66,7 @@ contract AutoCompoundApe is _mint(onBehalf, amountShare); _transferTokenIn(msg.sender, amount); - _harvest(); + _harvest(rewardAmount); _compound(); emit Transfer(address(0), onBehalf, amount); @@ -75,10 +77,11 @@ contract AutoCompoundApe is function withdraw(uint256 amount) external override { require(amount > 0, "zero amount"); - uint256 amountShare = getShareByPooledApe(amount); + uint256 rewardAmount = _getRewardApeBalance(); + uint256 amountShare = _getShareByPooledApe(amount, rewardAmount); _burn(msg.sender, amountShare); - _harvest(); + _harvest(rewardAmount); uint256 _bufferBalance = bufferBalance; if (amount > _bufferBalance) { _withdrawFromApeCoinStaking(amount - _bufferBalance); @@ -93,21 +96,25 @@ contract AutoCompoundApe is /// @inheritdoc IAutoCompoundApe function harvestAndCompound() external { - _harvest(); + _harvest(_getRewardApeBalance()); _compound(); } - function _getTotalPooledApeBalance() - internal - view - override - returns (uint256) - { + function _getRewardApeBalance() internal view override returns (uint256) { uint256 rewardAmount = apeStaking.pendingRewards( APE_COIN_POOL_ID, address(this), 0 ); + return rewardAmount; + } + + function _getTotalPooledApeBalance(uint256 rewardAmount) + internal + view + override + returns (uint256) + { return stakingBalance + rewardAmount + bufferBalance; } @@ -139,12 +146,7 @@ contract AutoCompoundApe is } } - function _harvest() internal { - uint256 rewardAmount = apeStaking.pendingRewards( - APE_COIN_POOL_ID, - address(this), - 0 - ); + function _harvest(uint256 rewardAmount) internal { if (rewardAmount > 0) { uint256 balanceBefore = apeCoin.balanceOf(address(this)); apeStaking.claimSelfApeCoin(); diff --git a/contracts/apestaking/base/CApe.sol b/contracts/apestaking/base/CApe.sol index 30f578079..e94139b07 100644 --- a/contracts/apestaking/base/CApe.sol +++ b/contracts/apestaking/base/CApe.sol @@ -51,7 +51,7 @@ abstract contract CApe is ContextUpgradeable, ICApe, PausableUpgradeable { * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { - return _getTotalPooledApeBalance(); + return _getTotalPooledApeBalance(_getRewardApeBalance()); } /** @@ -60,7 +60,7 @@ abstract contract CApe is ContextUpgradeable, ICApe, PausableUpgradeable { * @dev The sum of all APE balances in the protocol, equals to the total supply of PsAPE. */ function getTotalPooledApeBalance() public view returns (uint256) { - return _getTotalPooledApeBalance(); + return _getTotalPooledApeBalance(_getRewardApeBalance()); } /** @@ -231,7 +231,32 @@ abstract contract CApe is ContextUpgradeable, ICApe, PausableUpgradeable { * @return the amount of shares that corresponds to `amount` protocol-controlled Ape. */ function getShareByPooledApe(uint256 amount) public view returns (uint256) { - uint256 totalPooledApe = _getTotalPooledApeBalance(); + uint256 totalPooledApe = _getTotalPooledApeBalance( + _getRewardApeBalance() + ); + return _calculateShareByPooledApe(amount, totalPooledApe); + } + + /** + * @return the amount of shares that corresponds to `amount` protocol-controlled Ape. + */ + function _getShareByPooledApe(uint256 amount, uint256 rewardAmount) + public + view + returns (uint256) + { + uint256 totalPooledApe = _getTotalPooledApeBalance(rewardAmount); + return _calculateShareByPooledApe(amount, totalPooledApe); + } + + /** + * @return the amount of shares that corresponds to `amount` protocol-controlled Ape. + */ + function _calculateShareByPooledApe(uint256 amount, uint256 totalPooledApe) + internal + view + returns (uint256) + { if (totalPooledApe == 0) { return 0; } else { @@ -252,16 +277,24 @@ abstract contract CApe is ContextUpgradeable, ICApe, PausableUpgradeable { return 0; } else { return - sharesAmount.mul(_getTotalPooledApeBalance()).div(totalShares); + sharesAmount + .mul(_getTotalPooledApeBalance(_getRewardApeBalance())) + .div(totalShares); } } + /** + * @return the amount of reward ApeCoin + * @dev This function is required to be implemented in a derived contract. + */ + function _getRewardApeBalance() internal view virtual returns (uint256); + /** * @return the total amount (in wei) of APE controlled by the protocol. * @dev This is used for calculating tokens from shares and vice versa. * @dev This function is required to be implemented in a derived contract. */ - function _getTotalPooledApeBalance() + function _getTotalPooledApeBalance(uint256 rewardAmount) internal view virtual From f29f94676d00e055653286b9589a7bb3a2c0697b Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 20 Jun 2023 10:42:19 +0800 Subject: [PATCH 34/99] chore: allow user set sape as collateral --- contracts/interfaces/IPoolApeStaking.sol | 4 +- .../protocol/libraries/logic/SupplyLogic.sol | 11 - .../libraries/logic/ValidationLogic.sol | 6 - contracts/protocol/pool/PoolApeStaking.sol | 35 +-- .../deployments/steps/23_renounceOwnership.ts | 2 +- tasks/upgrade/index.ts | 10 +- test/_pool_ape_staking.spec.ts | 216 +++++++++++++----- test/_sape_pool_operation.spec.ts | 73 +++--- test/_uniswapv3_pool_operation.spec.ts | 10 +- test/_xtoken_ptoken.spec.ts | 2 +- test/auto_compound_ape.spec.ts | 27 ++- test/xtoken_ntoken_bakc.spec.ts | 18 +- 12 files changed, 263 insertions(+), 151 deletions(-) diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 2d7be7d76..c9dd993ff 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -25,12 +25,14 @@ interface IPoolApeStaking { * @param stakingInfo Detail info of the staking * @param _nfts Array of BAYC/MAYC NFT's with staked amounts * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts + * @param _openSApeCollateralFlag if true and when user sApe collateral flag is false, we will open it. Should call setUserUseERC20AsCollateral to turn off the flag. * @dev Need check User health factor > 1. */ function borrowApeAndStake( StakingInfo calldata stakingInfo, ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs + ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs, + bool _openSApeCollateralFlag ) external; /** diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index 63ba6ee75..64a44220b 100644 --- a/contracts/protocol/libraries/logic/SupplyLogic.sol +++ b/contracts/protocol/libraries/logic/SupplyLogic.sol @@ -220,17 +220,6 @@ library SupplyLogic { ); } } - if ( - tokenType == XTokenType.NTokenBAYC || - tokenType == XTokenType.NTokenMAYC - ) { - Helpers.setAssetUsedAsCollateral( - userConfig, - reservesData, - DataTypes.SApeAddress, - params.onBehalfOf - ); - } for (uint256 index = 0; index < params.tokenData.length; index++) { IERC721(params.asset).safeTransferFrom( params.payer, diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 8db48710d..6e0d3e511 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -417,12 +417,6 @@ library ValidationLogic { ) internal pure { require(userBalance != 0, Errors.UNDERLYING_BALANCE_ZERO); - IXTokenType xToken = IXTokenType(reserveCache.xTokenAddress); - require( - xToken.getXTokenType() != XTokenType.PTokenSApe, - Errors.SAPE_NOT_ALLOWED - ); - ( bool isActive, , diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index f0673f78c..353ac416c 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -283,7 +283,8 @@ contract PoolApeStaking is function borrowApeAndStake( StakingInfo calldata stakingInfo, ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs + ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs, + bool _openSApeCollateralFlag ) external nonReentrant { DataTypes.PoolStorage storage ps = poolStorage(); _checkSApeIsNotPaused(ps); @@ -304,10 +305,10 @@ contract PoolApeStaking is DataTypes.ReserveData storage borrowAssetReserve = ps._reserves[ stakingInfo.borrowAsset ]; - // no time lock needed here - DataTypes.TimeLockParams memory timeLockParams; // 1, handle borrow part if (stakingInfo.borrowAmount > 0) { + // no time lock needed here + DataTypes.TimeLockParams memory timeLockParams; if (stakingInfo.borrowAsset == address(APE_COIN)) { IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( localVar.xTokenAddress, @@ -389,7 +390,20 @@ contract PoolApeStaking is } } - // 5 mint debt token + //5 check if need to collateralize sAPE + if (_openSApeCollateralFlag) { + DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ + msg.sender + ]; + Helpers.setAssetUsedAsCollateral( + userConfig, + ps._reserves, + DataTypes.SApeAddress, + msg.sender + ); + } + + // 6 mint debt token if (stakingInfo.borrowAmount > 0) { BorrowLogic.executeBorrow( ps._reserves, @@ -410,23 +424,12 @@ contract PoolApeStaking is ); } - //6 checkout ape balance + //7 checkout ape balance require( APE_COIN.balanceOf(localVar.xTokenAddress) == localVar.balanceBefore, Errors.TOTAL_STAKING_AMOUNT_WRONG ); - - //7 collateralize sAPE - DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ - msg.sender - ]; - Helpers.setAssetUsedAsCollateral( - userConfig, - ps._reserves, - DataTypes.SApeAddress, - msg.sender - ); } /// @inheritdoc IPoolApeStaking diff --git a/scripts/deployments/steps/23_renounceOwnership.ts b/scripts/deployments/steps/23_renounceOwnership.ts index f5232caa5..5c5d7e937 100644 --- a/scripts/deployments/steps/23_renounceOwnership.ts +++ b/scripts/deployments/steps/23_renounceOwnership.ts @@ -384,7 +384,7 @@ export const step_23 = async ( if (DRY_RUN) { const encodedData1 = p2pPairStakingProxy.interface.encodeFunctionData( "changeAdmin", - adminAddress + [adminAddress] ); await dryRunEncodedData(p2pPairStakingProxy.address, encodedData1); } else { diff --git a/tasks/upgrade/index.ts b/tasks/upgrade/index.ts index be0d7e61b..025c336aa 100644 --- a/tasks/upgrade/index.ts +++ b/tasks/upgrade/index.ts @@ -119,17 +119,17 @@ task("upgrade:timelock", "upgrade timelock").setAction(async (_, DRE) => { console.timeEnd("upgrade timelock"); }); -task("upgrade:p2p-pair-staking", "upgrade p2p pair staking").setAction( - async (_, DRE) => { +task("upgrade:p2p-pair-staking", "upgrade p2p pair staking") + .addPositionalParam("compoundFee", "new compound fee") + .setAction(async (compoundFee, DRE) => { const {upgradeP2PPairStaking} = await import( "../../scripts/upgrade/P2PPairStaking" ); await DRE.run("set-DRE"); console.time("upgrade p2p pair staking"); - await upgradeP2PPairStaking(ETHERSCAN_VERIFICATION); + await upgradeP2PPairStaking(compoundFee, ETHERSCAN_VERIFICATION); console.timeEnd("upgrade p2p pair staking"); - } -); + }); task("upgrade:ptoken", "upgrade ptoken").setAction(async (_, DRE) => { const {upgradePToken} = await import("../../scripts/upgrade/ptoken"); diff --git a/test/_pool_ape_staking.spec.ts b/test/_pool_ape_staking.spec.ts index 4e9584420..83ca6cd60 100644 --- a/test/_pool_ape_staking.spec.ts +++ b/test/_pool_ape_staking.spec.ts @@ -138,7 +138,7 @@ describe("APE Coin Staking Test", () => { return testEnv; }; - /* + it("TC-pool-ape-staking-01 test borrowApeAndStake: failed when borrow + cash < staking amount (revert expected)", async () => { const { users: [user1], @@ -162,7 +162,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ).to.be.revertedWith(ProtocolErrors.TOTAL_STAKING_AMOUNT_WRONG); }); @@ -190,7 +191,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ).to.be.revertedWith(ProtocolErrors.TOTAL_STAKING_AMOUNT_WRONG); }); @@ -221,7 +223,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -278,7 +281,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -335,7 +339,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -391,7 +396,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -463,7 +469,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -545,7 +552,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -580,7 +588,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -616,7 +625,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}], + true ) ); @@ -697,7 +707,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}], + true ) ); @@ -777,7 +788,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -841,7 +853,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -912,7 +925,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -975,7 +989,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [] + [], + true ) ); expect( @@ -987,7 +1002,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1039,7 +1055,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1092,7 +1109,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1142,7 +1160,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1218,7 +1237,8 @@ describe("APE Coin Staking Test", () => { {tokenId: 0, amount: amount}, {tokenId: 1, amount: amount}, ], - [{mainTokenId: 1, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 1, bakcTokenId: 0, amount: amount}], + true ) ); @@ -1234,7 +1254,8 @@ describe("APE Coin Staking Test", () => { {tokenId: 0, amount: amount}, {tokenId: 1, amount: amount}, ], - [{mainTokenId: 1, bakcTokenId: 1, amount: amount}] + [{mainTokenId: 1, bakcTokenId: 1, amount: amount}], + true ) ); @@ -1343,7 +1364,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1421,7 +1443,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ).to.be.revertedWith(ProtocolErrors.COLLATERAL_CANNOT_COVER_NEW_BORROW); }); @@ -1451,7 +1474,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1518,7 +1542,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1587,7 +1612,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1652,7 +1678,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1691,7 +1718,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); }); @@ -1719,7 +1747,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ).to.be.revertedWith("DepositMoreThanOneAPE()"); }); @@ -1751,7 +1780,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); @@ -1818,7 +1848,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [] + [], + true ) ); @@ -1831,7 +1862,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -1897,7 +1929,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [] + [], + true ) ); @@ -1910,7 +1943,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); // User 1 - totalStake should increased in Stake amount @@ -1973,7 +2007,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount2, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ).to.be.revertedWith("ERC20: transfer amount exceeds balance"); }); @@ -2017,7 +2052,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); const healthFactorAfter = (await pool.getUserAccountData(user1.address)) @@ -2081,7 +2117,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2154,7 +2191,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); await waitForTx( @@ -2206,7 +2244,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); await waitForTx( @@ -2258,7 +2297,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2316,7 +2356,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2363,7 +2404,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2440,7 +2482,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2506,7 +2549,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2566,7 +2610,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2641,7 +2686,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2737,7 +2783,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2820,7 +2867,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount}], - [] + [], + true ) ); @@ -2888,7 +2936,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2977,7 +3026,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + true ) ); @@ -2985,7 +3035,7 @@ describe("APE Coin Staking Test", () => { .data; expect(isUsingAsCollateral(configDataAfter, sApeReserveData.id)).true; }); -*/ + it("TC-pool-ape-staking-47 test mayc withdrawApeCoin should transfer ape coin to timelock contract", async () => { const { users: [user1], @@ -3036,7 +3086,8 @@ describe("APE Coin Staking Test", () => { cashAmount: totalAmount, }, [{tokenId: 0, amount: amount}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); @@ -3116,7 +3167,8 @@ describe("APE Coin Staking Test", () => { cashAmount: totalAmount, }, [{tokenId: 0, amount: amount}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); @@ -3145,4 +3197,60 @@ describe("APE Coin Staking Test", () => { expect(await ape.balanceOf(user1.address)).to.be.eq(totalAmount); expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(0); }); + + it("TC-pool-ape-staking-49 test borrowApeAndStake with 100% debt failed if don't use sape as collateral", async () => { + const { + users: [user1], + ape, + mayc, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + + await changePriceAndValidate(ape, "0.003"); + + await expect( + pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + false + ) + ).to.be.revertedWith(ProtocolErrors.COLLATERAL_CANNOT_COVER_NEW_BORROW); + + await changePriceAndValidate(ape, "0.0001"); + + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + false + ) + ); + + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + const userConfig = BigNumber.from( + (await pool.getUserConfiguration(user1.address)).data + ); + const apeData = await pool.getReserveData(ape.address); + expect(isUsingAsCollateral(userConfig, apeData.id)).to.be.false; + }); }); diff --git a/test/_sape_pool_operation.spec.ts b/test/_sape_pool_operation.spec.ts index 8e8a89ec6..e7e91fdb5 100644 --- a/test/_sape_pool_operation.spec.ts +++ b/test/_sape_pool_operation.spec.ts @@ -77,7 +77,8 @@ describe("SApe Pool Operation Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount}], - [] + [], + true ) ); @@ -127,7 +128,8 @@ describe("SApe Pool Operation Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: amount}], - [] + [], + true ) ); @@ -158,37 +160,38 @@ describe("SApe Pool Operation Test", () => { ).to.be.revertedWith(ProtocolErrors.SAPE_NOT_ALLOWED); }); - it("set sApe not as collateral is not allowed", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "5000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount}], - [] - ) - ); - - await expect( - pool - .connect(user1.signer) - .setUserUseERC20AsCollateral(sApeAddress, false, { - gasLimit: 12_450_000, - }) - ).to.be.revertedWith(ProtocolErrors.SAPE_NOT_ALLOWED); - }); + // it("set sApe not as collateral is not allowed", async () => { + // const { + // users: [user1], + // ape, + // mayc, + // pool, + // } = await loadFixture(fixture); + // + // await supplyAndValidate(mayc, "1", user1, true); + // await mintAndValidate(ape, "10000", user1); + // + // const amount = await convertToCurrencyDecimals(ape.address, "5000"); + // expect( + // await pool.connect(user1.signer).borrowApeAndStake( + // { + // nftAsset: mayc.address, + // borrowAsset: ape.address, + // borrowAmount: amount, + // cashAmount: 0, + // }, + // [{tokenId: 0, amount: amount}], + // [], + // true + // ) + // ); + // + // await expect( + // pool + // .connect(user1.signer) + // .setUserUseERC20AsCollateral(sApeAddress, false, { + // gasLimit: 12_450_000, + // }) + // ).to.be.revertedWith(ProtocolErrors.SAPE_NOT_ALLOWED); + // }); }); diff --git a/test/_uniswapv3_pool_operation.spec.ts b/test/_uniswapv3_pool_operation.spec.ts index 3b289bc35..6b81a4d54 100644 --- a/test/_uniswapv3_pool_operation.spec.ts +++ b/test/_uniswapv3_pool_operation.spec.ts @@ -720,7 +720,7 @@ describe("Uniswap V3 NFT supply, withdraw, setCollateral, liquidation and transf ); }); - it("UniswapV3 asset can be auctioned [ @skip-on-coverage ]", async () => { + it("UniswapV3 asset can not be auctioned [ @skip-on-coverage ]", async () => { const { users: [borrower, liquidator], pool, @@ -746,13 +746,11 @@ describe("Uniswap V3 NFT supply, withdraw, setCollateral, liquidation and transf expect(liquidatorBalance).to.eq(0); // try to start auction - await waitForTx( - await pool + await expect( + pool .connect(liquidator.signer) .startAuction(borrower.address, nftPositionManager.address, 1) - ); - - expect(await nUniswapV3.isAuctioned(1)).to.be.true; + ).to.be.revertedWith(ProtocolErrors.AUCTION_NOT_ENABLED); }); it("liquidation failed if underlying erc20 was not active [ @skip-on-coverage ]", async () => { diff --git a/test/_xtoken_ptoken.spec.ts b/test/_xtoken_ptoken.spec.ts index 41d46b1eb..12fa9aef6 100644 --- a/test/_xtoken_ptoken.spec.ts +++ b/test/_xtoken_ptoken.spec.ts @@ -197,7 +197,7 @@ describe("Functionalities of ptoken permit", () => { }); describe("Allowance could be override", () => { - let preset: Awaited>; + let preset; before(async () => { preset = await loadFixture(fixture); }); diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index 28c73beef..fb1838d07 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -479,7 +479,8 @@ describe("Auto Compound Ape Test", () => { cashAmount: user1Amount, }, [{tokenId: 0, amount: user1Amount}], - [] + [], + true ) ); @@ -492,7 +493,8 @@ describe("Auto Compound Ape Test", () => { cashAmount: user2Amount, }, [{tokenId: 1, amount: user2Amount}], - [] + [], + true ) ); @@ -505,7 +507,8 @@ describe("Auto Compound Ape Test", () => { cashAmount: user3Amount, }, [{tokenId: 2, amount: user3Amount}], - [] + [], + true ) ); @@ -639,7 +642,8 @@ describe("Auto Compound Ape Test", () => { {tokenId: 1, amount: userAmount}, {tokenId: 2, amount: userAmount}, ], - [] + [], + true ) ); @@ -757,7 +761,8 @@ describe("Auto Compound Ape Test", () => { cashAmount: user1Amount, }, [{tokenId: 0, amount: user1Amount}], - [] + [], + true ) ); @@ -770,7 +775,8 @@ describe("Auto Compound Ape Test", () => { cashAmount: user2Amount, }, [{tokenId: 1, amount: user2Amount}], - [] + [], + true ) ); @@ -783,7 +789,8 @@ describe("Auto Compound Ape Test", () => { cashAmount: user3Amount, }, [{tokenId: 2, amount: user3Amount}], - [] + [], + true ) ); @@ -942,7 +949,8 @@ describe("Auto Compound Ape Test", () => { {mainTokenId: 0, bakcTokenId: 0, amount: userAmount}, {mainTokenId: 1, bakcTokenId: 1, amount: userAmount}, {mainTokenId: 2, bakcTokenId: 2, amount: userAmount}, - ] + ], + true ) ); @@ -1120,7 +1128,8 @@ describe("Auto Compound Ape Test", () => { cashAmount: 0, }, [{tokenId: 0, amount: user1Amount}], - [] + [], + true ) ); diff --git a/test/xtoken_ntoken_bakc.spec.ts b/test/xtoken_ntoken_bakc.spec.ts index c38c4cf51..bfe5ab253 100644 --- a/test/xtoken_ntoken_bakc.spec.ts +++ b/test/xtoken_ntoken_bakc.spec.ts @@ -95,7 +95,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); @@ -181,7 +182,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); @@ -221,7 +223,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); @@ -267,7 +270,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); @@ -334,7 +338,8 @@ describe("APE Coin Staking Test", () => { cashAmount: amount, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); @@ -384,7 +389,8 @@ describe("APE Coin Staking Test", () => { cashAmount: 0, }, [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], + true ) ); let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); From 54288cb02b1c62fe8582cf944853bbc9006c2c45 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 20 Jun 2023 10:49:53 +0800 Subject: [PATCH 35/99] chore: fix lint and rename variable --- contracts/interfaces/ITimeLock.sol | 4 ++-- contracts/misc/TimeLock.sol | 8 ++++---- test/p2p_pair_staking.spec.ts | 6 +----- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/contracts/interfaces/ITimeLock.sol b/contracts/interfaces/ITimeLock.sol index 35829b7ba..ea10eb7a1 100644 --- a/contracts/interfaces/ITimeLock.sol +++ b/contracts/interfaces/ITimeLock.sol @@ -74,7 +74,7 @@ interface ITimeLock { /** @dev Function to create a new time-lock agreement * @param assetType Type of the asset involved * @param actionType Type of action for the time-lock - * @param callerUnderlyingAsset Underlying asset of the caller if caller is xToken + * @param sourceAsset Underlying asset of the caller if caller is xToken * @param asset Address of the asset * @param tokenIdsOrAmounts Array of token IDs or amounts * @param beneficiary Address of the beneficiary @@ -84,7 +84,7 @@ interface ITimeLock { function createAgreement( DataTypes.AssetType assetType, DataTypes.TimeLockActionType actionType, - address callerUnderlyingAsset, + address sourceAsset, address asset, uint256[] memory tokenIdsOrAmounts, address beneficiary, diff --git a/contracts/misc/TimeLock.sol b/contracts/misc/TimeLock.sol index 8aefbd52d..229b535d4 100644 --- a/contracts/misc/TimeLock.sol +++ b/contracts/misc/TimeLock.sol @@ -38,10 +38,10 @@ contract TimeLock is ITimeLock, ReentrancyGuardUpgradeable, IERC721Receiver { /** * @dev Only POOL or callerTag asset's xToken can call functions marked by this modifier. **/ - modifier onlyValidCaller(address callerUnderlyingAsset) { + modifier onlyValidCaller(address sourceAsset) { require( msg.sender == address(POOL) || - msg.sender == POOL.getReserveXToken(callerUnderlyingAsset), + msg.sender == POOL.getReserveXToken(sourceAsset), Errors.CALLER_NOT_ALLOWED ); _; @@ -81,12 +81,12 @@ contract TimeLock is ITimeLock, ReentrancyGuardUpgradeable, IERC721Receiver { function createAgreement( DataTypes.AssetType assetType, DataTypes.TimeLockActionType actionType, - address callerUnderlyingAsset, + address sourceAsset, address asset, uint256[] calldata tokenIdsOrAmounts, address beneficiary, uint48 releaseTime - ) external onlyValidCaller(callerUnderlyingAsset) returns (uint256) { + ) external onlyValidCaller(sourceAsset) returns (uint256) { require(beneficiary != address(0), "Beneficiary cant be zero address"); require(releaseTime > block.timestamp, "Release time not valid"); diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index fe988b958..e71c565a7 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -15,11 +15,7 @@ import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; import {parseEther} from "ethers/lib/utils"; import {almostEqual} from "./helpers/uniswapv3-helper"; import {deployP2PPairStakingImpl} from "../helpers/contracts-deployments"; -import {DRY_RUN, GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; -import { - dryRunEncodedData, - getEthersSigners, -} from "../helpers/contracts-helpers"; +import {GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; describe("P2P Pair Staking Test", () => { let testEnv: TestEnv; From 0b0cc6aeb4c3ad32cfebea5cd70f09c174116e21 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 20 Jun 2023 14:37:24 +0800 Subject: [PATCH 36/99] chore: add p2p pause and test case --- contracts/apestaking/P2PPairStaking.sol | 21 ++++-- test/p2p_pair_staking.spec.ts | 87 +++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index b0b5bb90a..c44143143 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -145,7 +145,7 @@ contract P2PPairStaking is function matchPairStakingList( ListingOrder calldata apeOrder, ListingOrder calldata apeCoinOrder - ) external nonReentrant returns (bytes32 orderHash) { + ) external nonReentrant whenNotPaused returns (bytes32 orderHash) { //1 validate all order _validateApeOrder(apeOrder); bytes32 apeCoinListingOrderHash = _validateApeCoinOrder(apeCoinOrder); @@ -212,7 +212,7 @@ contract P2PPairStaking is ListingOrder calldata apeOrder, ListingOrder calldata bakcOrder, ListingOrder calldata apeCoinOrder - ) external nonReentrant returns (bytes32 orderHash) { + ) external nonReentrant whenNotPaused returns (bytes32 orderHash) { //1 validate all order _validateApeOrder(apeOrder); _validateBakcOrder(bakcOrder); @@ -284,7 +284,11 @@ contract P2PPairStaking is return orderHash; } - function breakUpMatchedOrder(bytes32 orderHash) external nonReentrant { + function breakUpMatchedOrder(bytes32 orderHash) + external + nonReentrant + whenNotPaused + { MatchedOrder memory order = matchedOrders[orderHash]; //1 check if have permission to break up @@ -379,6 +383,7 @@ contract P2PPairStaking is function claimForMatchedOrderAndCompound(bytes32[] calldata orderHashes) external nonReentrant + whenNotPaused { require(msg.sender == compoundBot, "no permission to compound"); _claimForMatchedOrdersAndCompound(orderHashes); @@ -409,7 +414,11 @@ contract P2PPairStaking is } } - function claimCApeReward(address receiver) external nonReentrant { + function claimCApeReward(address receiver) + external + nonReentrant + whenNotPaused + { uint256 cApeAmount = pendingCApeReward(msg.sender); if (cApeAmount > 0) { IERC20(cApe).safeTransfer(receiver, cApeAmount); @@ -698,7 +707,7 @@ contract P2PPairStaking is /** * @notice Pauses the contract. Only pool admin or emergency admin can call this function **/ - function pause() external onlyEmergencyOrPoolAdmin { + function pause() external onlyEmergencyOrPoolAdmin whenNotPaused { paused = true; emit Paused(_msgSender()); } @@ -706,7 +715,7 @@ contract P2PPairStaking is /** * @notice Unpause the contract. Only pool admin can call this function **/ - function unpause() external onlyPoolAdmin { + function unpause() external onlyPoolAdmin whenPaused { paused = false; emit Unpaused(_msgSender()); } diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index e71c565a7..c201023fd 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -16,6 +16,7 @@ import {parseEther} from "ethers/lib/utils"; import {almostEqual} from "./helpers/uniswapv3-helper"; import {deployP2PPairStakingImpl} from "../helpers/contracts-deployments"; import {GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; +import {ProtocolErrors} from "../helpers/types"; describe("P2P Pair Staking Test", () => { let testEnv: TestEnv; @@ -1205,4 +1206,90 @@ describe("P2P Pair Staking Test", () => { .matchPairStakingList(user1SignedOrder1, user2SignedOrder) ); }); + + it("pause work as expected", async () => { + const { + users: [user1, user2], + ape, + mayc, + poolAdmin, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "1000000", user2); + + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(1); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 1, + mayc, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 1, + cApe, + 0, + 8000, + user2 + ); + + await expect( + p2pPairStaking.connect(user1.signer).pause() + ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN); + + await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).pause()); + + await expect( + p2pPairStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ).to.be.revertedWith("paused"); + + await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).unpause()); + + const txReceipt = await waitForTx( + await p2pPairStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).pause()); + + await expect( + p2pPairStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([orderHash]) + ).to.be.revertedWith("paused"); + + await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).unpause()); + + await waitForTx( + await p2pPairStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([orderHash]) + ); + + await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).pause()); + + await expect( + p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) + ).to.be.revertedWith("paused"); + + await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).unpause()); + + await waitForTx( + await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) + ); + }); }); From e0a113f2973f5ca90852c37882045c8a4e07cf98 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 20 Jun 2023 15:32:23 +0800 Subject: [PATCH 37/99] chore: don't need to check hf if sApe is not set as collateral --- contracts/protocol/pool/PoolApeStaking.sol | 33 ++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 353ac416c..376a46486 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -119,7 +119,8 @@ contract PoolApeStaking is address xTokenAddress = nftReserve.xTokenAddress; INToken nToken = INToken(xTokenAddress); uint256 totalWithdrawAmount = 0; - for (uint256 index = 0; index < _nfts.length; index++) { + uint256 arrayLength = _nfts.length; + for (uint256 index = 0; index < arrayLength; index++) { require( nToken.ownerOf(_nfts[index].tokenId) == msg.sender, Errors.NOT_THE_OWNER @@ -142,7 +143,15 @@ contract PoolApeStaking is timeLockParams ); - _checkUserHf(ps, msg.sender, true); + DataTypes.UserConfigurationMap memory userConfig = ps._usersConfig[ + msg.sender + ]; + DataTypes.ReserveData storage reserve = ps._reserves[ + DataTypes.SApeAddress + ]; + if (userConfig.isUsingAsCollateral(reserve.id)) { + _checkUserHf(ps, userConfig, msg.sender, true); + } } /// @inheritdoc IPoolApeStaking @@ -233,7 +242,15 @@ contract PoolApeStaking is ); } - _checkUserHf(ps, msg.sender, true); + DataTypes.UserConfigurationMap memory userConfig = ps._usersConfig[ + msg.sender + ]; + DataTypes.ReserveData storage reserve = ps._reserves[ + DataTypes.SApeAddress + ]; + if (userConfig.isUsingAsCollateral(reserve.id)) { + _checkUserHf(ps, userConfig, msg.sender, true); + } } /// @inheritdoc IPoolApeStaking @@ -443,7 +460,10 @@ contract PoolApeStaking is address incentiveReceiver = address(0); address positionOwner = INToken(xTokenAddress).ownerOf(tokenId); if (msg.sender != positionOwner) { - _checkUserHf(ps, positionOwner, false); + DataTypes.UserConfigurationMap memory userConfig = ps._usersConfig[ + positionOwner + ]; + _checkUserHf(ps, userConfig, positionOwner, false); incentiveReceiver = msg.sender; } @@ -659,13 +679,10 @@ contract PoolApeStaking is function _checkUserHf( DataTypes.PoolStorage storage ps, + DataTypes.UserConfigurationMap memory userConfig, address user, bool checkAbove ) private view { - DataTypes.UserConfigurationMap memory userConfig = ps._usersConfig[ - user - ]; - uint256 healthFactor; if (!userConfig.isBorrowingAny()) { healthFactor = type(uint256).max; From 3cf5ed92248eed1f7c55cfc473d38377be41ac0a Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 21 Jun 2023 09:49:31 +0800 Subject: [PATCH 38/99] chore: small fix --- contracts/apestaking/P2PPairStaking.sol | 2 +- contracts/protocol/pool/PoolApeStaking.sol | 17 ++++----- test/_pool_ape_staking.spec.ts | 43 +++++++++++----------- test/p2p_pair_staking.spec.ts | 4 +- 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol index c44143143..8c0ffb0d8 100644 --- a/contracts/apestaking/P2PPairStaking.sol +++ b/contracts/apestaking/P2PPairStaking.sol @@ -692,7 +692,7 @@ contract P2PPairStaking is return this.onERC721Received.selector; } - function setCompoundBot(address _compoundBot) external onlyOwner { + function setCompoundBot(address _compoundBot) external onlyPoolAdmin { address oldValue = compoundBot; if (oldValue != _compoundBot) { compoundBot = _compoundBot; diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 376a46486..8e9289849 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -183,13 +183,12 @@ contract PoolApeStaking is _checkSApeIsNotPaused(ps); ApeStakingLocalVars memory localVar = _generalCache(ps, nftAsset); - localVar.transferredTokenOwners = new address[](_nftPairs.length); - - uint256[] memory transferredTokenIds = new uint256[](_nftPairs.length); + uint256 arrayLength = _nftPairs.length; + localVar.transferredTokenOwners = new address[](arrayLength); + uint256[] memory transferredTokenIds = new uint256[](arrayLength); uint256 actualTransferAmount = 0; uint256 totalWithdrawAmount = 0; - - for (uint256 index = 0; index < _nftPairs.length; index++) { + for (uint256 index = 0; index < arrayLength; index++) { require( INToken(localVar.xTokenAddress).ownerOf( _nftPairs[index].mainTokenId @@ -226,7 +225,6 @@ contract PoolApeStaking is amount: totalWithdrawAmount }) ); - INTokenApeStaking(localVar.xTokenAddress).withdrawBAKC( _nftPairs, msg.sender, @@ -262,9 +260,10 @@ contract PoolApeStaking is _checkSApeIsNotPaused(ps); ApeStakingLocalVars memory localVar = _generalCache(ps, nftAsset); - localVar.transferredTokenOwners = new address[](_nftPairs.length); + uint256 arrayLength = _nftPairs.length; + localVar.transferredTokenOwners = new address[](arrayLength); - for (uint256 index = 0; index < _nftPairs.length; index++) { + for (uint256 index = 0; index < arrayLength; index++) { require( INToken(localVar.xTokenAddress).ownerOf( _nftPairs[index].mainTokenId @@ -287,7 +286,7 @@ contract PoolApeStaking is ); //transfer BAKC back for user - for (uint256 index = 0; index < _nftPairs.length; index++) { + for (uint256 index = 0; index < arrayLength; index++) { localVar.bakcContract.safeTransferFrom( localVar.xTokenAddress, localVar.transferredTokenOwners[index], diff --git a/test/_pool_ape_staking.spec.ts b/test/_pool_ape_staking.spec.ts index 83ca6cd60..cb8077e5f 100644 --- a/test/_pool_ape_staking.spec.ts +++ b/test/_pool_ape_staking.spec.ts @@ -672,7 +672,8 @@ describe("APE Coin Staking Test", () => { ); const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount.add(pendingRewardsPool2)); + //reward is not as collateral + expect(totalStake).equal(amount); expect(await ape.balanceOf(user1.address)).to.be.eq( userBalance.add(pendingRewardsPool3) @@ -756,7 +757,8 @@ describe("APE Coin Staking Test", () => { ); const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount.add(pendingRewardsPool2)); + //reward is not as collateral + expect(totalStake).equal(amount); expect(await ape.balanceOf(user1.address)).to.be.eq( userBalance.add(pendingRewardsPool3) @@ -830,7 +832,7 @@ describe("APE Coin Staking Test", () => { ); }); - it("TC-pool-ape-staking-13 test claimApeCoin fails when hf < 1 (revert expected)", async () => { + it("TC-pool-ape-staking-13 test claimApeCoin should success when hf < 1", async () => { const { users: [user1], ape, @@ -877,11 +879,8 @@ describe("APE Coin Staking Test", () => { // drop HF to liquidation levels await changePriceAndValidate(mayc, "3"); - await expect( - pool.connect(user1.signer).claimApeCoin(mayc.address, [0]) - ).to.be.revertedWith( - ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); + //ape coin reward is not used as collateral + expect(await pool.connect(user1.signer).claimApeCoin(mayc.address, [0])); }); it("TC-pool-ape-staking-14 test unstakeApePositionAndRepay repays cape debt - no excess", async () => { @@ -2717,11 +2716,11 @@ describe("APE Coin Staking Test", () => { await advanceTimeAndBlock(parseInt("86400")); // bayc rewards - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); + // const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( + // 2, + // nMAYC.address, + // "0" + // ); // bakc rewards const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( 3, @@ -2754,8 +2753,8 @@ describe("APE Coin Staking Test", () => { // User 3 - ape balance should increased pendingRewardsPool3 expect(await ape.balanceOf(user3.address)).eq(pendingRewardsPool3); totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User1 - total stake should increased amount1 + pendingRewardsPool2 - expect(totalStake).equal(amount1.add(pendingRewardsPool2)); + // User1 - total stake should increased amount1 + expect(totalStake).equal(amount1); }); it("TC-pool-ape-staking-43 test withdrawBAKC success when withdraw amount == bakc staking amount, and the sender is not the BAKC owner, it will automatically claim and transfer the reward to the BACK owner", async () => { @@ -2814,11 +2813,11 @@ describe("APE Coin Staking Test", () => { await advanceTimeAndBlock(parseInt("86400")); // bayc rewards - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); + // const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( + // 2, + // nMAYC.address, + // "0" + // ); // bakc rewards const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( 3, @@ -2839,8 +2838,8 @@ describe("APE Coin Staking Test", () => { amount2.add(pendingRewardsPool3) ); totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User1 - total stake should increased amount1 + pendingRewardsPool2 - expect(totalStake).equal(amount1.add(pendingRewardsPool2)); + // User1 - total stake should increased amount1 + expect(totalStake).equal(amount1); }); it("TC-pool-ape-staking-44 test withdrawApeCoin fails when the sender is not the NFT owner(revert expected)", async () => { diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index c201023fd..846727781 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -26,7 +26,7 @@ describe("P2P Pair Staking Test", () => { const fixture = async () => { testEnv = await loadFixture(testEnvFixture); - const {ape, users, apeCoinStaking, gatewayAdmin} = testEnv; + const {ape, users, apeCoinStaking, poolAdmin} = testEnv; const user1 = users[0]; const user2 = users[1]; @@ -66,7 +66,7 @@ describe("P2P Pair Staking Test", () => { ); await waitForTx( await p2pPairStaking - .connect(gatewayAdmin.signer) + .connect(poolAdmin.signer) .setCompoundBot(user1.address) ); From 317a9b9f34ed9e3ab6c19f00baeb8a85d5f3d269 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 21 Jun 2023 10:43:01 +0800 Subject: [PATCH 39/99] chore: add test case and gas optimization --- contracts/interfaces/IPoolApeStaking.sol | 2 +- contracts/protocol/tokenization/PToken.sol | 14 +++--- test/_pool_ape_staking.spec.ts | 52 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index c9dd993ff..60bc62a6a 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -25,7 +25,7 @@ interface IPoolApeStaking { * @param stakingInfo Detail info of the staking * @param _nfts Array of BAYC/MAYC NFT's with staked amounts * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts - * @param _openSApeCollateralFlag if true and when user sApe collateral flag is false, we will open it. Should call setUserUseERC20AsCollateral to turn off the flag. + * @param _openSApeCollateralFlag if true and when user current sApe collateral flag is false, we will open it. We don't close the flag here, user should call setUserUseERC20AsCollateral to turn off the flag. * @dev Need check User health factor > 1. */ function borrowApeAndStake( diff --git a/contracts/protocol/tokenization/PToken.sol b/contracts/protocol/tokenization/PToken.sol index 8bc1a7cbd..2915bb9b7 100644 --- a/contracts/protocol/tokenization/PToken.sol +++ b/contracts/protocol/tokenization/PToken.sol @@ -115,6 +115,7 @@ contract PToken is ) external virtual override onlyPool { _burnScaled(from, receiverOfUnderlying, amount, index); if (receiverOfUnderlying != address(this)) { + address underlyingAsset = _underlyingAsset; if (timeLockParams.releaseTime != 0) { ITimeLock timeLock = POOL.TIME_LOCK(); uint256[] memory amounts = new uint256[](1); @@ -123,15 +124,15 @@ contract PToken is timeLock.createAgreement( DataTypes.AssetType.ERC20, timeLockParams.actionType, - _underlyingAsset, - _underlyingAsset, + underlyingAsset, + underlyingAsset, amounts, receiverOfUnderlying, timeLockParams.releaseTime ); receiverOfUnderlying = address(timeLock); } - IERC20(_underlyingAsset).safeTransfer(receiverOfUnderlying, amount); + IERC20(underlyingAsset).safeTransfer(receiverOfUnderlying, amount); } } @@ -217,6 +218,7 @@ contract PToken is uint256 amount, DataTypes.TimeLockParams calldata timeLockParams ) external virtual override onlyPool { + address underlyingAsset = _underlyingAsset; if (timeLockParams.releaseTime != 0) { ITimeLock timeLock = POOL.TIME_LOCK(); uint256[] memory amounts = new uint256[](1); @@ -225,15 +227,15 @@ contract PToken is timeLock.createAgreement( DataTypes.AssetType.ERC20, timeLockParams.actionType, - _underlyingAsset, - _underlyingAsset, + underlyingAsset, + underlyingAsset, amounts, target, timeLockParams.releaseTime ); target = address(timeLock); } - IERC20(_underlyingAsset).safeTransfer(target, amount); + IERC20(underlyingAsset).safeTransfer(target, amount); } /// @inheritdoc IPToken diff --git a/test/_pool_ape_staking.spec.ts b/test/_pool_ape_staking.spec.ts index cb8077e5f..0df9070b4 100644 --- a/test/_pool_ape_staking.spec.ts +++ b/test/_pool_ape_staking.spec.ts @@ -3252,4 +3252,56 @@ describe("APE Coin Staking Test", () => { const apeData = await pool.getReserveData(ape.address); expect(isUsingAsCollateral(userConfig, apeData.id)).to.be.false; }); + + it("TC-pool-ape-staking-50 test withdrawApeCoin should success when hf < 1 and sApe is not used as collateral", async () => { + const { + users: [user1], + ape, + mayc, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + await changePriceAndValidate(mayc, "100"); + await changePriceAndValidate(ape, "0.001"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], + false + ) + ); + + await changePriceAndValidate(mayc, "40"); + await changePriceAndValidate(ape, "0.002"); + await changeSApePriceAndValidate(sApeAddress, "0.002"); + + const healthFactor = (await pool.getUserAccountData(user1.address)) + .healthFactor; + expect(healthFactor.lt(parseEther("1"))).to.be.true; + + expect( + await pool + .connect(user1.signer) + .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) + ); + + expect( + await pool + .connect(user1.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, + ]) + ); + }); }); From cd61586db4271cc043cfbe73c2854bec2d757fc0 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 3 Jul 2023 22:43:36 +0800 Subject: [PATCH 40/99] chore: basic logic for pair staking --- contracts/apestaking/ParaApeStaking.sol | 350 ++++++++++ .../logic/ApeStakingCommonLogic.sol | 388 +++++++++++ .../apestaking/logic/ApeStakingP2PLogic.sol | 635 ++++++++++++++++++ .../apestaking/logic/ApeStakingVaultLogic.sol | 501 ++++++++++++++ contracts/interfaces/IApeStakingP2P.sol | 164 +++++ contracts/interfaces/IApeStakingVault.sol | 20 + contracts/interfaces/IParaApeStaking.sol | 38 ++ contracts/interfaces/IPoolApeStaking.sol | 2 + .../protocol/libraries/logic/BorrowLogic.sol | 51 ++ .../libraries/logic/ValidationLogic.sol | 76 ++- contracts/protocol/pool/PoolApeStaking.sol | 23 +- 11 files changed, 2213 insertions(+), 35 deletions(-) create mode 100644 contracts/apestaking/ParaApeStaking.sol create mode 100644 contracts/apestaking/logic/ApeStakingCommonLogic.sol create mode 100644 contracts/apestaking/logic/ApeStakingP2PLogic.sol create mode 100644 contracts/apestaking/logic/ApeStakingVaultLogic.sol create mode 100644 contracts/interfaces/IApeStakingP2P.sol create mode 100644 contracts/interfaces/IApeStakingVault.sol create mode 100644 contracts/interfaces/IParaApeStaking.sol diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol new file mode 100644 index 000000000..b28d84e50 --- /dev/null +++ b/contracts/apestaking/ParaApeStaking.sol @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.10; + +import "../interfaces/IParaApeStaking.sol"; +import "../dependencies/openzeppelin/upgradeability/Initializable.sol"; +import "../dependencies/openzeppelin/upgradeability/ReentrancyGuardUpgradeable.sol"; +import "../dependencies/openzeppelin/upgradeability/PausableUpgradeable.sol"; +import {IERC20, SafeERC20} from "../dependencies/openzeppelin/contracts/SafeERC20.sol"; +import "../dependencies/openzeppelin/contracts/SafeCast.sol"; +import "../dependencies/yoga-labs/ApeCoinStaking.sol"; +import "../interfaces/IACLManager.sol"; +import "../interfaces/ICApe.sol"; +import {PercentageMath} from "../protocol/libraries/math/PercentageMath.sol"; +import {WadRayMath} from "../protocol/libraries/math/WadRayMath.sol"; +import "./logic/ApeStakingP2PLogic.sol"; +import "./logic/ApeStakingVaultLogic.sol"; +import "./logic/ApeStakingCommonLogic.sol"; + +contract ParaApeStaking is + Initializable, + ReentrancyGuardUpgradeable, + PausableUpgradeable, + IParaApeStaking +{ + using SafeERC20 for IERC20; + using SafeCast for uint256; + using PercentageMath for uint256; + using WadRayMath for uint256; + + //keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + bytes32 internal constant EIP712_DOMAIN = + 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; + + address internal immutable pool; + address internal immutable bayc; + address internal immutable mayc; + address internal immutable bakc; + address internal immutable nBayc; + address internal immutable nMayc; + address internal immutable nBakc; + address internal immutable apeCoin; + address internal immutable cApe; + ApeCoinStaking internal immutable apeCoinStaking; + uint256 public immutable compoundFee; + uint256 private immutable baycMatchedCap; + uint256 private immutable maycMatchedCap; + uint256 private immutable bakcMatchedCap; + IACLManager private immutable aclManager; + + bytes32 internal DOMAIN_SEPARATOR; + mapping(bytes32 => ListingOrderStatus) public listingOrderStatus; + mapping(bytes32 => MatchedOrder) public matchedOrders; + mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; + mapping(StakingType => mapping(uint32 => uint256)) + private positionCApeShareDebt; + mapping(address => uint256) private cApeShareBalance; + + constructor( + address _pool, + address _bayc, + address _mayc, + address _bakc, + address _nBayc, + address _nMayc, + address _nBakc, + address _apeCoin, + address _cApe, + address _apeCoinStaking, + address _aclManager, + uint256 _compoundFee + ) { + pool = _pool; + bayc = _bayc; + mayc = _mayc; + bakc = _bakc; + nBayc = _nBayc; + nMayc = _nMayc; + nBakc = _nBakc; + apeCoin = _apeCoin; + cApe = _cApe; + apeCoinStaking = ApeCoinStaking(_apeCoinStaking); + aclManager = IACLManager(_aclManager); + compoundFee = _compoundFee; + + ( + , + ApeCoinStaking.PoolUI memory baycPool, + ApeCoinStaking.PoolUI memory maycPool, + ApeCoinStaking.PoolUI memory bakcPool + ) = apeCoinStaking.getPoolsUI(); + + baycMatchedCap = baycPool.currentTimeRange.capPerPosition; + maycMatchedCap = maycPool.currentTimeRange.capPerPosition; + bakcMatchedCap = bakcPool.currentTimeRange.capPerPosition; + } + + function initialize() public initializer { + __ReentrancyGuard_init(); + __Pausable_init(); + + DOMAIN_SEPARATOR = keccak256( + abi.encode( + EIP712_DOMAIN, + //keccak256("ParaSpace"), + 0x88d989289235fb06c18e3c2f7ea914f41f773e86fb0073d632539f566f4df353, + //keccak256(bytes("1")), + 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, + block.chainid, + address(this) + ) + ); + + //approve ApeCoin for apeCoinStaking + IERC20(apeCoin).safeApprove(address(apeCoinStaking), type(uint256).max); + //approve ApeCoin for cApe + IERC20(apeCoin).safeApprove(cApe, type(uint256).max); + } + + function cancelListing(ListingOrder calldata listingOrder) + external + nonReentrant + { + require(msg.sender == listingOrder.offerer, "not order offerer"); + bytes32 orderHash = ApeStakingP2PLogic.getListingOrderHash( + listingOrder + ); + require( + listingOrderStatus[orderHash] != ListingOrderStatus.Cancelled, + "order already cancelled" + ); + listingOrderStatus[orderHash] = ListingOrderStatus.Cancelled; + + emit OrderCancelled(orderHash, listingOrder.offerer); + } + + function matchPairStakingList( + ListingOrder calldata apeOrder, + ListingOrder calldata apeCoinOrder + ) external nonReentrant whenNotPaused returns (bytes32 orderHash) { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.DOMAIN_SEPARATOR = DOMAIN_SEPARATOR; + orderHash = ApeStakingP2PLogic.matchPairStakingList( + apeOrder, + apeCoinOrder, + listingOrderStatus, + matchedOrders, + apeMatchedCount, + vars + ); + + emit PairStakingMatched(orderHash); + + return orderHash; + } + + function matchBAKCPairStakingList( + ListingOrder calldata apeOrder, + ListingOrder calldata bakcOrder, + ListingOrder calldata apeCoinOrder + ) external nonReentrant whenNotPaused returns (bytes32 orderHash) { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.DOMAIN_SEPARATOR = DOMAIN_SEPARATOR; + orderHash = ApeStakingP2PLogic.matchPairStakingList( + apeOrder, + bakcOrder, + apeCoinOrder, + listingOrderStatus, + matchedOrders, + apeMatchedCount, + vars + ); + + emit PairStakingMatched(orderHash); + + return orderHash; + } + + function breakUpMatchedOrder(bytes32 orderHash) + external + nonReentrant + whenNotPaused + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingP2PLogic.breakUpMatchedOrder( + listingOrderStatus, + matchedOrders, + cApeShareBalance, + apeMatchedCount, + vars, + orderHash + ); + + //7 emit event + emit PairStakingBreakUp(orderHash); + } + + function claimForMatchedOrderAndCompound(bytes32[] calldata orderHashes) + external + nonReentrant + whenNotPaused + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingP2PLogic.claimForMatchedOrdersAndCompound( + matchedOrders, + cApeShareBalance, + vars, + orderHashes + ); + } + + function claimCApeReward(address receiver) + external + nonReentrant + whenNotPaused + { + uint256 cApeAmount = pendingCApeReward(msg.sender); + if (cApeAmount > 0) { + IERC20(cApe).safeTransfer(receiver, cApeAmount); + delete cApeShareBalance[msg.sender]; + emit CApeClaimed(msg.sender, receiver, cApeAmount); + } + } + + function pendingCApeReward(address user) public view returns (uint256) { + uint256 amount = 0; + uint256 shareBalance = cApeShareBalance[user]; + if (shareBalance > 0) { + amount = ICApe(cApe).getPooledApeByShares(shareBalance); + } + return amount; + } + + function getApeCoinStakingCap(StakingType stakingType) + public + view + returns (uint256) + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + return ApeStakingP2PLogic.getApeCoinStakingCap(stakingType, vars); + } + + mapping(uint256 => PoolState) public poolStates; + + //address public vaultBot; + + function depositPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingVaultLogic.depositPairNFT( + poolStates, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + } + + function stakingPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingVaultLogic.stakingPairNFT( + poolStates, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + } + + function withdrawPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingVaultLogic.withdrawPairNFT( + poolStates, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + } + + // to save gas we don't claim pending reward in ApeCoinStaking. + function claimPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingVaultLogic.claimPairNFT( + poolStates, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + } + + function compoundPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingVaultLogic.compoundPairNFT( + poolStates, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + } + + function depositBAYC(uint256[] calldata apeTokenIds) external {} + + function depositMAYC(uint256[] calldata apeTokenIds) external {} + + function depositBAKC(uint256[] calldata bakcTokenIds) external {} + + function _createCacheVars() + internal + view + returns (ApeStakingVaultCacheVars memory) + { + ApeStakingVaultCacheVars memory vars; + vars.pool = pool; + vars.bayc = bayc; + vars.mayc = mayc; + vars.bakc = bakc; + vars.nBayc = nBayc; + vars.nMayc = nMayc; + vars.nBakc = nBakc; + vars.apeCoin = apeCoin; + vars.cApe = cApe; + vars.apeCoinStaking = apeCoinStaking; + vars.compoundFee = compoundFee; + vars.baycMatchedCap = baycMatchedCap; + vars.maycMatchedCap = maycMatchedCap; + vars.bakcMatchedCap = bakcMatchedCap; + return vars; + } +} diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol new file mode 100644 index 000000000..524f1a2ca --- /dev/null +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.10; + +import "../../interfaces/IParaApeStaking.sol"; +import {IERC20, SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; +import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; +import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; +import "../../interfaces/IAutoCompoundApe.sol"; +import "../../interfaces/ICApe.sol"; +import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; +import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; + +/** + * @title ApeStakingVaultLogic library + * + * @notice Implements the base logic for ape staking vault + */ +library ApeStakingCommonLogic { + using PercentageMath for uint256; + using SafeCast for uint256; + using SafeERC20 for IERC20; + + function depositCApeShareForUser( + mapping(address => uint256) storage cApeShareBalance, + address user, + uint256 amount + ) internal { + if (amount > 0) { + cApeShareBalance[user] += amount; + } + } + + /* + function stake( + StakingType stakingType, + uint32 mainTokenId, + uint32 bakcTokenId + ) external nonReentrant { + (address ape, address apeNToken) = _getApeAndNTokenAddress(stakingType); + + //validate owner + address apeNTokenOwner = IERC721(apeNToken).ownerOf(mainTokenId); + require(msg.sender == apeNTokenOwner, "not ntoken owner"); + if ( + stakingType == StakingType.BAYCPairStaking || + stakingType == StakingType.MAYCPairStaking + ) { + address bakcNTokenOwner = IERC721(nBakc).ownerOf(bakcTokenId); + require(msg.sender == bakcNTokenOwner, "not bakc ntoken owner"); + } + + //get all token + _handleApeTransfer(ape, apeNToken, mainTokenId); + uint256 apePositionCap = getApeCoinStakingCap(stakingType); + uint256 latestBorrowIndex = IPool(pool).borrowPoolCApe(apePositionCap); + IAutoCompoundApe(cApe).withdraw(apePositionCap); + if ( + stakingType == StakingType.BAYCPairStaking || + stakingType == StakingType.MAYCPairStaking + ) { + IERC721(bakc).safeTransferFrom(nBakc, address(this), bakcTokenId); + } + + //update status + apeMatchedCount[ape][mainTokenId] += 1; + { + uint256 cApeExchangeRate = ICApe(cApe).getPooledApeByShares( + WadRayMath.RAY + ); + positionCApeShareDebt[stakingType][mainTokenId] = apePositionCap + .rayDiv(latestBorrowIndex) + .rayDiv(cApeExchangeRate); + } + + //stake for ApeCoinStaking + if ( + stakingType == StakingType.BAYCStaking || + stakingType == StakingType.BAYCPairStaking + ) { + ApeCoinStaking.SingleNft[] + memory singleNft = new ApeCoinStaking.SingleNft[](1); + singleNft[0].tokenId = mainTokenId; + singleNft[0].amount = apePositionCap.toUint224(); + if (stakingType == StakingType.BAYCStaking) { + apeCoinStaking.depositBAYC(singleNft); + } else { + apeCoinStaking.depositMAYC(singleNft); + } + } else { + ApeCoinStaking.PairNftDepositWithAmount[] + memory _stakingPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 1 + ); + _stakingPairs[0].mainTokenId = mainTokenId; + _stakingPairs[0].bakcTokenId = bakcTokenId; + _stakingPairs[0].amount = apePositionCap.toUint184(); + ApeCoinStaking.PairNftDepositWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 0 + ); + if (stakingType == StakingType.BAYCPairStaking) { + apeCoinStaking.depositBAKC(_stakingPairs, _otherPairs); + } else { + apeCoinStaking.depositBAKC(_otherPairs, _stakingPairs); + } + } + } + + function unstake(VaultPosition calldata position) external nonReentrant { + (address ape, address apeNToken) = _getApeAndNTokenAddress( + position.stakingType + ); + + // check owner + address apeNTokenOwner = IERC721(apeNToken).ownerOf( + position.mainTokenId + ); + if ( + position.stakingType == StakingType.BAYCPairStaking || + position.stakingType == StakingType.MAYCPairStaking + ) { + require(msg.sender == apeNTokenOwner, "no permission to break up"); + } else { + address nBakcOwner = IERC721(nBakc).ownerOf(position.bakcTokenId); + require( + msg.sender == apeNTokenOwner || msg.sender == nBakcOwner, + "no permission to break up" + ); + } + + //exit from ApeCoinStaking + uint256 apePositionCap = getApeCoinStakingCap(position.stakingType); + uint256 beforeBalance = IERC20(apeCoin).balanceOf(address(this)); + if ( + position.stakingType == StakingType.BAYCPairStaking || + position.stakingType == StakingType.MAYCPairStaking + ) { + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](1); + _nfts[0].tokenId = position.mainTokenId; + _nfts[0].amount = apePositionCap.toUint224(); + if (position.stakingType == StakingType.BAYCStaking) { + apeCoinStaking.withdrawSelfBAYC(_nfts); + } else { + apeCoinStaking.withdrawSelfMAYC(_nfts); + } + } else { + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _nfts = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 1 + ); + _nfts[0].mainTokenId = position.mainTokenId; + _nfts[0].bakcTokenId = position.bakcTokenId; + _nfts[0].amount = apePositionCap.toUint184(); + _nfts[0].isUncommit = true; + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); + if (position.stakingType == StakingType.BAYCPairStaking) { + apeCoinStaking.withdrawBAKC(_nfts, _otherPairs); + } else { + apeCoinStaking.withdrawBAKC(_otherPairs, _nfts); + } + } + uint256 afterBalance = IERC20(apeCoin).balanceOf(address(this)); + uint256 unstakeAmount = afterBalance - beforeBalance; + IAutoCompoundApe(cApe).deposit(address(this), unstakeAmount); + + //repay ape position debt and interest debt + { + uint256 cApeExchangeRate = ICApe(cApe).getPooledApeByShares( + WadRayMath.RAY + ); + uint256 latestBorrowIndex = IPool(pool) + .getReserveNormalizedVariableDebt(cApe); + uint256 debtInterest = _calculateCurrentPositionDebtInterest( + position, + apePositionCap, + cApeExchangeRate, + latestBorrowIndex + ); + uint256 totalDebt = debtInterest + apePositionCap; + require(totalDebt <= unstakeAmount, "can't repay debt"); + IPool(pool).repay(cApe, totalDebt, address(this)); + delete positionCApeShareDebt[position.stakingType][ + position.mainTokenId + ]; + + // distribute left ape coin reward to nBAYC/nMAYC owner as cApe + if (unstakeAmount > totalDebt) { + uint256 leftAmount = unstakeAmount - totalDebt; + uint256 feeAmount = leftAmount.percentMul(compoundFee); + leftAmount -= feeAmount; + uint256 leftShare = leftAmount.rayDiv(cApeExchangeRate); + _depositCApeShareForUser(apeNTokenOwner, leftShare); + } + } + + // transfer Ape or BAKC to nToken + uint256 matchedCount = apeMatchedCount[ape][position.mainTokenId]; + if (matchedCount == 1) { + IERC721(ape).safeTransferFrom( + address(this), + apeNToken, + position.mainTokenId + ); + } + apeMatchedCount[ape][position.mainTokenId] = matchedCount - 1; + if ( + position.stakingType == StakingType.BAYCPairStaking || + position.stakingType == StakingType.MAYCPairStaking + ) { + IERC721(bakc).safeTransferFrom( + address(this), + nBakc, + position.bakcTokenId + ); + } + } + + function claimAndCompound(VaultPosition[] calldata positions) + external + nonReentrant + { + //ignore getShareByPooledApe return 0 case. + uint256 cApeExchangeRate = ICApe(cApe).getPooledApeByShares( + WadRayMath.RAY + ); + uint256 latestBorrowIndex = IPool(pool) + .getReserveNormalizedVariableDebt(cApe); + uint256 _compoundFee = compoundFee; + uint256 totalReward; + uint256 totalDebtInterest; + uint256 totalFee; + uint256 positionLength = positions.length; + for (uint256 index = 0; index < positionLength; index++) { + VaultPosition calldata position = positions[index]; + ( + uint256 reward, + uint256 debtInterest, + uint256 fee + ) = _claimAndCompound( + position, + cApeExchangeRate, + latestBorrowIndex, + _compoundFee + ); + totalReward += reward; + totalDebtInterest += debtInterest; + totalFee += fee; + } + if (totalReward > 0) { + IAutoCompoundApe(cApe).deposit(address(this), totalReward); + IPool(pool).repay(cApe, totalDebtInterest, address(this)); + IERC20(apeCoin).safeTransfer(compoundBot, totalFee); + } + } + + function _claimAndCompound( + VaultPosition calldata position, + uint256 cApeExchangeRate, + uint256 latestBorrowIndex, + uint256 _compoundFee + ) + internal + returns ( + uint256, + uint256, + uint256 + ) + { + (, address apeNToken) = _getApeAndNTokenAddress(position.stakingType); + + //get reward amount + uint256 rewardAmount; + { + uint256 balanceBefore = IERC20(apeCoin).balanceOf(address(this)); + if ( + position.stakingType == StakingType.BAYCStaking || + position.stakingType == StakingType.MAYCStaking + ) { + uint256[] memory _nfts = new uint256[](1); + _nfts[0] = position.mainTokenId; + if (position.stakingType == StakingType.BAYCStaking) { + apeCoinStaking.claimSelfBAYC(_nfts); + } else { + apeCoinStaking.claimSelfMAYC(_nfts); + } + } else { + ApeCoinStaking.PairNft[] + memory _nfts = new ApeCoinStaking.PairNft[](1); + _nfts[0].mainTokenId = position.mainTokenId; + _nfts[0].bakcTokenId = position.bakcTokenId; + ApeCoinStaking.PairNft[] + memory _otherPairs = new ApeCoinStaking.PairNft[](0); + if (position.stakingType == StakingType.BAYCPairStaking) { + apeCoinStaking.claimSelfBAKC(_nfts, _otherPairs); + } else { + apeCoinStaking.claimSelfBAKC(_otherPairs, _nfts); + } + } + uint256 balanceAfter = IERC20(apeCoin).balanceOf(address(this)); + rewardAmount = balanceAfter - balanceBefore; + } + + // calculate debt + uint256 debtInterest; + { + uint256 apePositionCap = getApeCoinStakingCap(position.stakingType); + debtInterest = _calculateCurrentPositionDebtInterest( + position, + apePositionCap, + cApeExchangeRate, + latestBorrowIndex + ); + + //simply revert if rewardAmount < debtInterest, or it's hard to update positionCApeVariableDebtIndex + require(rewardAmount >= debtInterest, ""); + positionCApeShareDebt[position.stakingType][ + position.mainTokenId + ] = apePositionCap.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + } + + uint256 remainingReward = rewardAmount - debtInterest; + uint256 feeAmount = remainingReward.percentMul(_compoundFee); + remainingReward -= feeAmount; + uint256 rewardShare = remainingReward.rayDiv(cApeExchangeRate); + _depositCApeShareForUser( + IERC721(apeNToken).ownerOf(position.mainTokenId), + rewardShare + ); + + return (rewardAmount, debtInterest, feeAmount); + } + + function _depositCApeShareForUser(address user, uint256 amount) internal { + if (amount > 0) { + cApeShareBalance[user] += amount; + } + } + + function getApeCoinStakingCap(StakingType stakingType) + public + view + returns (uint256) + { + if (stakingType == StakingType.BAYCStaking) { + return baycMatchedCap; + } else if (stakingType == StakingType.MAYCStaking) { + return maycMatchedCap; + } else { + return bakcMatchedCap; + } + } + + function _calculateCurrentPositionDebtInterest( + VaultPosition calldata position, + uint256 apePositionCap, + uint256 cApeExchangeRate, + uint256 latestBorrowIndex + ) internal view returns (uint256) { + uint256 shareDebt = positionCApeShareDebt[position.stakingType][ + position.mainTokenId + ]; + uint256 currentDebt = shareDebt.rayMul(cApeExchangeRate).rayMul( + latestBorrowIndex + ); + return (currentDebt - apePositionCap); + } + + function _handleApeTransfer( + address apeToken, + address apeNToken, + uint256 tokenId + ) internal { + address currentOwner = IERC721(apeToken).ownerOf(tokenId); + if (currentOwner != address(this)) { + IERC721(apeToken).safeTransferFrom( + apeNToken, + address(this), + tokenId + ); + } + }*/ +} diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol new file mode 100644 index 000000000..0f5ea0724 --- /dev/null +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.10; + +import "../../interfaces/IApeStakingP2P.sol"; +import "../../interfaces/IApeStakingP2P.sol"; +import {IERC20, SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; +import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; +import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; +import "../../interfaces/IAutoCompoundApe.sol"; +import "../../interfaces/ICApe.sol"; +import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; +import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import "./ApeStakingCommonLogic.sol"; +import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; + +/** + * @title ApeStakingVaultLogic library + * + * @notice Implements the base logic for ape staking vault + */ +library ApeStakingP2PLogic { + using PercentageMath for uint256; + using SafeCast for uint256; + using SafeERC20 for IERC20; + using WadRayMath for uint256; + + event OrderClaimedAndCompounded(bytes32 orderHash, uint256 totalReward); + + uint256 internal constant WAD = 1e18; + + //keccak256("ListingOrder(uint8 stakingType,address offerer,address token,uint256 tokenId,uint256 share,uint256 startTime,uint256 endTime)"); + bytes32 internal constant LISTING_ORDER_HASH = + 0x227f9dd14259caacdbcf45411b33cf1c018f31bd3da27e613a66edf8ae45814f; + + //keccak256("MatchedOrder(uint8 stakingType,address apeToken,uint32 apeTokenId,uint32 apeShare,uint32 bakcTokenId,uint32 bakcShare,address apeCoinOfferer,uint32 apeCoinShare,uint256 apePrincipleAmount)"); + bytes32 internal constant MATCHED_ORDER_HASH = + 0x7db3dae7d89c86e6881a66a131841305c008b207e41ff86a804b4bb056652808; + + function matchPairStakingList( + IApeStakingP2P.ListingOrder calldata apeOrder, + IApeStakingP2P.ListingOrder calldata apeCoinOrder, + mapping(bytes32 => IApeStakingP2P.ListingOrderStatus) + storage listingOrderStatus, + mapping(bytes32 => IApeStakingP2P.MatchedOrder) storage matchedOrders, + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + IParaApeStaking.ApeStakingVaultCacheVars memory vars + ) external returns (bytes32 orderHash) { + //1 validate all order + _validateApeOrder(listingOrderStatus, apeOrder, vars); + bytes32 apeCoinListingOrderHash = _validateApeCoinOrder( + listingOrderStatus, + apeCoinOrder, + vars + ); + + //2 check if orders can match + require( + apeOrder.stakingType == IApeStakingP2P.StakingType.MAYCStaking || + apeOrder.stakingType == IApeStakingP2P.StakingType.BAYCStaking, + "invalid stake type" + ); + require( + apeOrder.stakingType == apeCoinOrder.stakingType, + "orders type match failed" + ); + require( + apeOrder.share + apeCoinOrder.share == + PercentageMath.PERCENTAGE_FACTOR, + "orders share match failed" + ); + + //3 transfer token + _handleApeTransfer(apeOrder, vars); + uint256 apeAmount = _handleCApeTransferAndConvert(apeCoinOrder, vars); + + //4 create match order + IApeStakingP2P.MatchedOrder memory matchedOrder = IApeStakingP2P + .MatchedOrder({ + stakingType: apeOrder.stakingType, + apeToken: apeOrder.token, + apeTokenId: apeOrder.tokenId, + apeShare: apeOrder.share, + bakcTokenId: 0, + bakcShare: 0, + apeCoinOfferer: apeCoinOrder.offerer, + apeCoinShare: apeCoinOrder.share, + apePrincipleAmount: apeAmount, + apeCoinListingOrderHash: apeCoinListingOrderHash + }); + orderHash = getMatchedOrderHash(matchedOrder); + matchedOrders[orderHash] = matchedOrder; + apeMatchedCount[apeOrder.token][apeOrder.tokenId] += 1; + + //5 stake for ApeCoinStaking + ApeCoinStaking.SingleNft[] + memory singleNft = new ApeCoinStaking.SingleNft[](1); + singleNft[0].tokenId = apeOrder.tokenId; + singleNft[0].amount = apeAmount.toUint224(); + if (apeOrder.stakingType == IApeStakingP2P.StakingType.BAYCStaking) { + vars.apeCoinStaking.depositBAYC(singleNft); + } else { + vars.apeCoinStaking.depositMAYC(singleNft); + } + + //6 update ape coin listing order status + listingOrderStatus[apeCoinListingOrderHash] = IApeStakingP2P + .ListingOrderStatus + .Matched; + + return orderHash; + } + + function matchPairStakingList( + IApeStakingP2P.ListingOrder calldata apeOrder, + IApeStakingP2P.ListingOrder calldata bakcOrder, + IApeStakingP2P.ListingOrder calldata apeCoinOrder, + mapping(bytes32 => IApeStakingP2P.ListingOrderStatus) + storage listingOrderStatus, + mapping(bytes32 => IApeStakingP2P.MatchedOrder) storage matchedOrders, + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + IParaApeStaking.ApeStakingVaultCacheVars memory vars + ) external returns (bytes32 orderHash) { + //1 validate all order + _validateApeOrder(listingOrderStatus, apeOrder, vars); + _validateBakcOrder(listingOrderStatus, bakcOrder, vars); + bytes32 apeCoinListingOrderHash = _validateApeCoinOrder( + listingOrderStatus, + apeCoinOrder, + vars + ); + + //2 check if orders can match + require( + apeOrder.stakingType == + IApeStakingP2P.StakingType.BAYCPairStaking || + apeOrder.stakingType == + IApeStakingP2P.StakingType.MAYCPairStaking, + "invalid stake type" + ); + require( + apeOrder.stakingType == bakcOrder.stakingType && + apeOrder.stakingType == apeCoinOrder.stakingType, + "orders type match failed" + ); + require( + apeOrder.share + bakcOrder.share + apeCoinOrder.share == + PercentageMath.PERCENTAGE_FACTOR, + "share match failed" + ); + + //3 transfer token + _handleApeTransfer(apeOrder, vars); + IERC721(vars.bakc).safeTransferFrom( + vars.nBakc, + address(this), + bakcOrder.tokenId + ); + uint256 apeAmount = _handleCApeTransferAndConvert(apeCoinOrder, vars); + + //4 create match order + IApeStakingP2P.MatchedOrder memory matchedOrder = IApeStakingP2P + .MatchedOrder({ + stakingType: apeOrder.stakingType, + apeToken: apeOrder.token, + apeTokenId: apeOrder.tokenId, + apeShare: apeOrder.share, + bakcTokenId: bakcOrder.tokenId, + bakcShare: bakcOrder.share, + apeCoinOfferer: apeCoinOrder.offerer, + apeCoinShare: apeCoinOrder.share, + apePrincipleAmount: apeAmount, + apeCoinListingOrderHash: apeCoinListingOrderHash + }); + orderHash = getMatchedOrderHash(matchedOrder); + matchedOrders[orderHash] = matchedOrder; + apeMatchedCount[apeOrder.token][apeOrder.tokenId] += 1; + + //5 stake for ApeCoinStaking + ApeCoinStaking.PairNftDepositWithAmount[] + memory _stakingPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 1 + ); + _stakingPairs[0].mainTokenId = apeOrder.tokenId; + _stakingPairs[0].bakcTokenId = bakcOrder.tokenId; + _stakingPairs[0].amount = apeAmount.toUint184(); + ApeCoinStaking.PairNftDepositWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 0 + ); + if (apeOrder.token == vars.bayc) { + vars.apeCoinStaking.depositBAKC(_stakingPairs, _otherPairs); + } else { + vars.apeCoinStaking.depositBAKC(_otherPairs, _stakingPairs); + } + + //6 update ape coin listing order status + listingOrderStatus[apeCoinListingOrderHash] = IApeStakingP2P + .ListingOrderStatus + .Matched; + + return orderHash; + } + + function breakUpMatchedOrder( + mapping(bytes32 => IApeStakingP2P.ListingOrderStatus) + storage listingOrderStatus, + mapping(bytes32 => IApeStakingP2P.MatchedOrder) storage matchedOrders, + mapping(address => uint256) storage cApeShareBalance, + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bytes32 orderHash + ) external { + IApeStakingP2P.MatchedOrder memory order = matchedOrders[orderHash]; + + //1 check if have permission to break up + address apeNToken = _getApeNTokenAddress(vars, order.apeToken); + address apeNTokenOwner = IERC721(apeNToken).ownerOf(order.apeTokenId); + address nBakcOwner = IERC721(vars.nBakc).ownerOf(order.bakcTokenId); + require( + msg.sender == apeNTokenOwner || + msg.sender == order.apeCoinOfferer || + (msg.sender == nBakcOwner && + (order.stakingType == + IApeStakingP2P.StakingType.BAYCPairStaking || + order.stakingType == + IApeStakingP2P.StakingType.MAYCPairStaking)), + "no permission to break up" + ); + + //2 claim pending reward and compound + bytes32[] memory orderHashes = new bytes32[](1); + orderHashes[0] = orderHash; + claimForMatchedOrdersAndCompound( + matchedOrders, + cApeShareBalance, + vars, + orderHashes + ); + + //3 delete matched order + delete matchedOrders[orderHash]; + + //4 exit from ApeCoinStaking + if ( + order.stakingType == IApeStakingP2P.StakingType.BAYCStaking || + order.stakingType == IApeStakingP2P.StakingType.MAYCStaking + ) { + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](1); + _nfts[0].tokenId = order.apeTokenId; + _nfts[0].amount = order.apePrincipleAmount.toUint224(); + if (order.stakingType == IApeStakingP2P.StakingType.BAYCStaking) { + vars.apeCoinStaking.withdrawSelfBAYC(_nfts); + } else { + vars.apeCoinStaking.withdrawSelfMAYC(_nfts); + } + } else { + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _nfts = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 1 + ); + _nfts[0].mainTokenId = order.apeTokenId; + _nfts[0].bakcTokenId = order.bakcTokenId; + _nfts[0].amount = order.apePrincipleAmount.toUint184(); + _nfts[0].isUncommit = true; + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); + if (order.apeToken == vars.bayc) { + vars.apeCoinStaking.withdrawBAKC(_nfts, _otherPairs); + } else { + vars.apeCoinStaking.withdrawBAKC(_otherPairs, _nfts); + } + } + //5 transfer token + uint256 matchedCount = apeMatchedCount[order.apeToken][ + order.apeTokenId + ]; + if (matchedCount == 1) { + IERC721(order.apeToken).safeTransferFrom( + address(this), + apeNToken, + order.apeTokenId + ); + } + apeMatchedCount[order.apeToken][order.apeTokenId] = matchedCount - 1; + + IAutoCompoundApe(vars.cApe).deposit( + order.apeCoinOfferer, + order.apePrincipleAmount + ); + if ( + order.stakingType == IApeStakingP2P.StakingType.BAYCPairStaking || + order.stakingType == IApeStakingP2P.StakingType.MAYCPairStaking + ) { + IERC721(vars.bakc).safeTransferFrom( + address(this), + vars.nBakc, + order.bakcTokenId + ); + } + + //6 reset ape coin listing order status + if ( + listingOrderStatus[order.apeCoinListingOrderHash] != + IApeStakingP2P.ListingOrderStatus.Cancelled + ) { + listingOrderStatus[order.apeCoinListingOrderHash] = IApeStakingP2P + .ListingOrderStatus + .Pending; + } + } + + function claimForMatchedOrdersAndCompound( + mapping(bytes32 => IApeStakingP2P.MatchedOrder) storage matchedOrders, + mapping(address => uint256) storage cApeShareBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bytes32[] memory orderHashes + ) public { + //ignore getShareByPooledApe return 0 case. + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WAD); + uint256 totalReward; + uint256 totalFeeShare; + uint256 orderCounts = orderHashes.length; + for (uint256 index = 0; index < orderCounts; index++) { + bytes32 orderHash = orderHashes[index]; + ( + uint256 reward, + uint256 feeShare + ) = _claimForMatchedOrderAndCompound( + matchedOrders, + cApeShareBalance, + vars, + orderHash, + cApeExchangeRate + ); + totalReward += reward; + totalFeeShare += feeShare; + } + if (totalReward > 0) { + IAutoCompoundApe(vars.cApe).deposit(address(this), totalReward); + ApeStakingCommonLogic.depositCApeShareForUser( + cApeShareBalance, + address(this), + totalFeeShare + ); + } + } + + function getApeCoinStakingCap( + IApeStakingP2P.StakingType stakingType, + IParaApeStaking.ApeStakingVaultCacheVars memory vars + ) internal pure returns (uint256) { + if (stakingType == IApeStakingP2P.StakingType.BAYCStaking) { + return vars.baycMatchedCap; + } else if (stakingType == IApeStakingP2P.StakingType.MAYCStaking) { + return vars.maycMatchedCap; + } else { + return vars.bakcMatchedCap; + } + } + + function _claimForMatchedOrderAndCompound( + mapping(bytes32 => IApeStakingP2P.MatchedOrder) storage matchedOrders, + mapping(address => uint256) storage cApeShareBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bytes32 orderHash, + uint256 cApeExchangeRate + ) internal returns (uint256, uint256) { + IApeStakingP2P.MatchedOrder memory order = matchedOrders[orderHash]; + uint256 balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + if ( + order.stakingType == IApeStakingP2P.StakingType.BAYCStaking || + order.stakingType == IApeStakingP2P.StakingType.MAYCStaking + ) { + uint256[] memory _nfts = new uint256[](1); + _nfts[0] = order.apeTokenId; + if (order.stakingType == IApeStakingP2P.StakingType.BAYCStaking) { + vars.apeCoinStaking.claimSelfBAYC(_nfts); + } else { + vars.apeCoinStaking.claimSelfMAYC(_nfts); + } + } else { + ApeCoinStaking.PairNft[] + memory _nfts = new ApeCoinStaking.PairNft[](1); + _nfts[0].mainTokenId = order.apeTokenId; + _nfts[0].bakcTokenId = order.bakcTokenId; + ApeCoinStaking.PairNft[] + memory _otherPairs = new ApeCoinStaking.PairNft[](0); + if (order.apeToken == vars.bayc) { + vars.apeCoinStaking.claimSelfBAKC(_nfts, _otherPairs); + } else { + vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nfts); + } + } + uint256 balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 rewardAmount = balanceAfter - balanceBefore; + if (rewardAmount == 0) { + return (0, 0); + } + + uint256 rewardShare = rewardAmount.wadDiv(cApeExchangeRate); + //compound fee + uint256 _compoundFeeShare = rewardShare.percentMul(vars.compoundFee); + rewardShare -= _compoundFeeShare; + + ApeStakingCommonLogic.depositCApeShareForUser( + cApeShareBalance, + IERC721(_getApeNTokenAddress(vars, order.apeToken)).ownerOf( + order.apeTokenId + ), + rewardShare.percentMul(order.apeShare) + ); + ApeStakingCommonLogic.depositCApeShareForUser( + cApeShareBalance, + order.apeCoinOfferer, + rewardShare.percentMul(order.apeCoinShare) + ); + if ( + order.stakingType == IApeStakingP2P.StakingType.BAYCPairStaking || + order.stakingType == IApeStakingP2P.StakingType.MAYCPairStaking + ) { + ApeStakingCommonLogic.depositCApeShareForUser( + cApeShareBalance, + IERC721(vars.nBakc).ownerOf(order.bakcTokenId), + rewardShare.percentMul(order.bakcShare) + ); + } + + emit OrderClaimedAndCompounded(orderHash, rewardAmount); + + return (rewardAmount, _compoundFeeShare); + } + + function _validateOrderBasicInfo( + bytes32 DOMAIN_SEPARATOR, + mapping(bytes32 => IApeStakingP2P.ListingOrderStatus) + storage listingOrderStatus, + IApeStakingP2P.ListingOrder calldata listingOrder + ) internal view returns (bytes32 orderHash) { + require( + listingOrder.startTime <= block.timestamp, + "ape order not start" + ); + require(listingOrder.endTime >= block.timestamp, "ape offer expired"); + + orderHash = getListingOrderHash(listingOrder); + require( + listingOrderStatus[orderHash] != + IApeStakingP2P.ListingOrderStatus.Cancelled, + "order already cancelled" + ); + + if (msg.sender != listingOrder.offerer) { + require( + validateOrderSignature( + DOMAIN_SEPARATOR, + listingOrder.offerer, + orderHash, + listingOrder.v, + listingOrder.r, + listingOrder.s + ), + "invalid signature" + ); + } + } + + function _validateApeOrder( + mapping(bytes32 => IApeStakingP2P.ListingOrderStatus) + storage listingOrderStatus, + IApeStakingP2P.ListingOrder calldata apeOrder, + IParaApeStaking.ApeStakingVaultCacheVars memory vars + ) internal view { + _validateOrderBasicInfo( + vars.DOMAIN_SEPARATOR, + listingOrderStatus, + apeOrder + ); + + address nToken = _getApeNTokenAddress(vars, apeOrder.token); + require( + IERC721(nToken).ownerOf(apeOrder.tokenId) == apeOrder.offerer, + "ape order invalid NToken owner" + ); + } + + function _validateApeCoinOrder( + mapping(bytes32 => IApeStakingP2P.ListingOrderStatus) + storage listingOrderStatus, + IApeStakingP2P.ListingOrder calldata apeCoinOrder, + IParaApeStaking.ApeStakingVaultCacheVars memory vars + ) internal view returns (bytes32 orderHash) { + orderHash = _validateOrderBasicInfo( + vars.DOMAIN_SEPARATOR, + listingOrderStatus, + apeCoinOrder + ); + require( + apeCoinOrder.token == vars.cApe, + "ape coin order invalid token" + ); + require( + listingOrderStatus[orderHash] != + IApeStakingP2P.ListingOrderStatus.Matched, + "ape coin order already matched" + ); + } + + function _validateBakcOrder( + mapping(bytes32 => IApeStakingP2P.ListingOrderStatus) + storage listingOrderStatus, + IApeStakingP2P.ListingOrder calldata bakcOrder, + IParaApeStaking.ApeStakingVaultCacheVars memory vars + ) internal view { + _validateOrderBasicInfo( + vars.DOMAIN_SEPARATOR, + listingOrderStatus, + bakcOrder + ); + + require(bakcOrder.token == vars.bakc, "bakc order invalid token"); + require( + IERC721(vars.nBakc).ownerOf(bakcOrder.tokenId) == bakcOrder.offerer, + "bakc order invalid NToken owner" + ); + } + + function _handleApeTransfer( + IApeStakingP2P.ListingOrder calldata order, + IParaApeStaking.ApeStakingVaultCacheVars memory vars + ) internal { + address currentOwner = IERC721(order.token).ownerOf(order.tokenId); + if (currentOwner != address(this)) { + address nTokenAddress = _getApeNTokenAddress(vars, order.token); + IERC721(order.token).safeTransferFrom( + nTokenAddress, + address(this), + order.tokenId + ); + } + } + + function _handleCApeTransferAndConvert( + IApeStakingP2P.ListingOrder calldata apeCoinOrder, + IParaApeStaking.ApeStakingVaultCacheVars memory vars + ) internal returns (uint256) { + uint256 apeAmount = getApeCoinStakingCap( + apeCoinOrder.stakingType, + vars + ); + IERC20(vars.cApe).safeTransferFrom( + apeCoinOrder.offerer, + address(this), + apeAmount + ); + IAutoCompoundApe(vars.cApe).withdraw(apeAmount); + return apeAmount; + } + + function _getApeNTokenAddress( + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address apeToken + ) internal pure returns (address) { + if (apeToken == vars.bayc) { + return vars.nBayc; + } else if (apeToken == vars.mayc) { + return vars.nMayc; + } else { + revert("unsupported ape token"); + } + } + + function getMatchedOrderHash(IApeStakingP2P.MatchedOrder memory order) + public + pure + returns (bytes32) + { + return + keccak256( + abi.encode( + MATCHED_ORDER_HASH, + order.stakingType, + order.apeToken, + order.apeTokenId, + order.apeShare, + order.bakcTokenId, + order.bakcShare, + order.apeCoinOfferer, + order.apeCoinShare, + order.apePrincipleAmount + ) + ); + } + + function getListingOrderHash(IApeStakingP2P.ListingOrder calldata order) + public + pure + returns (bytes32) + { + return + keccak256( + abi.encode( + LISTING_ORDER_HASH, + order.stakingType, + order.offerer, + order.token, + order.tokenId, + order.share, + order.startTime, + order.endTime + ) + ); + } + + function validateOrderSignature( + bytes32 DOMAIN_SEPARATOR, + address signer, + bytes32 orderHash, + uint8 v, + bytes32 r, + bytes32 s + ) public view returns (bool) { + return + SignatureChecker.verify( + orderHash, + signer, + v, + r, + s, + DOMAIN_SEPARATOR + ); + } +} diff --git a/contracts/apestaking/logic/ApeStakingVaultLogic.sol b/contracts/apestaking/logic/ApeStakingVaultLogic.sol new file mode 100644 index 000000000..4220e2775 --- /dev/null +++ b/contracts/apestaking/logic/ApeStakingVaultLogic.sol @@ -0,0 +1,501 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.10; + +import {IPool} from "../../interfaces/IPool.sol"; +import "../../interfaces/IParaApeStaking.sol"; +import {IERC20, SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; +import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; +import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; +import "../../interfaces/IAutoCompoundApe.sol"; +import "../../interfaces/ICApe.sol"; +import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; +import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; + +/** + * @title ApeStakingVaultLogic library + * + * @notice Implements the base logic for ape staking vault + */ +library ApeStakingVaultLogic { + using PercentageMath for uint256; + using SafeCast for uint256; + using SafeERC20 for IERC20; + using WadRayMath for uint256; + + uint256 constant BAYC_POOL_ID = 1; + uint256 constant MAYC_POOL_ID = 2; + uint256 constant BAKC_POOL_ID = 3; + uint256 constant BAYC_BAKC_PAIR_POOL_ID = 1; + uint256 constant MAYC_BAKC_PAIR_POOL_ID = 2; + uint256 constant BAYC_SINGLE_POOL_ID = 3; + uint256 constant MAYC_SINGLE_POOL_ID = 4; + uint256 constant BAKC_SINGLE_POOL_ID = 5; + uint256 internal constant WAD = 1e18; + event PairNFTDeposited( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + event PairNFTStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event PairNFTWithdrew(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event PairNFTClaimed(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event PairNFTCompounded( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + + function depositPairNFT( + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + IParaApeStaking.PoolState storage poolState; + { + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; + poolState = poolStates[poolId]; + } + vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; + vars.apeToken = isBAYC ? vars.bayc : vars.mayc; + vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; + address msgSender = msg.sender; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + //check ntoken owner + { + address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); + address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); + require( + msgSender == nApeOwner && msgSender == nBakcOwner, + "not owner" + ); + } + + // check both ape and bakc are not staking + { + (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( + vars.apeStakingPoolId, + apeTokenId + ); + require(stakedAmount == 0, "ape already staked"); + (stakedAmount, ) = vars.apeCoinStaking.nftPosition( + BAKC_POOL_ID, + bakcTokenId + ); + require(stakedAmount == 0, "bakc already staked"); + (, bool isPaired) = vars.apeCoinStaking.mainToBakc( + vars.apeStakingPoolId, + apeTokenId + ); + require(!isPaired, "ape already pair staked"); + } + + //update pair status + poolState.pairStatus[apeTokenId] = IApeStakingVault.PairingStatus({ + tokenId: bakcTokenId, + isPaired: true + }); + + //transfer ape and BAKC + IERC721(vars.apeToken).safeTransferFrom( + vars.nApe, + address(this), + apeTokenId + ); + IERC721(vars.bakc).safeTransferFrom( + vars.nBakc, + address(this), + bakcTokenId + ); + + //emit event + emit PairNFTDeposited(isBAYC, apeTokenId, bakcTokenId); + } + } + + function stakingPairNFT( + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + IParaApeStaking.PoolState storage poolState; + { + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; + poolState = poolStates[poolId]; + } + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + ApeCoinStaking.PairNftDepositWithAmount[] + memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + arrayLength + ); + vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; + vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + // check pair status + { + IApeStakingVault.PairingStatus + memory localPairStatus = poolState.pairStatus[apeTokenId]; + require( + localPairStatus.tokenId == bakcTokenId && + localPairStatus.isPaired, + "wrong pair status" + ); + } + + //update state + poolState.rewardsDebt[apeTokenId] = vars.accumulatedRewardsPerNft; + + // construct staking data + _nfts[index] = ApeCoinStaking.SingleNft({ + tokenId: apeTokenId, + amount: vars.positionCap.toUint224() + }); + _nftPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184() + }); + + //emit event + emit PairNFTStaked(isBAYC, apeTokenId, bakcTokenId); + } + + // prepare Ape coin + uint256 totalNeed = (vars.positionCap + vars.bakcMatchedCap) * + arrayLength; + IPool(vars.pool).borrowPoolCApe(totalNeed); + IAutoCompoundApe(vars.cApe).withdraw(totalNeed); + + //stake in ApeCoinStaking + ApeCoinStaking.PairNftDepositWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 0 + ); + if (isBAYC) { + vars.apeCoinStaking.depositBAYC(_nfts); + vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.depositMAYC(_nfts); + vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); + } + + //update state + poolState.totalPosition += arrayLength; + } + + function withdrawPairNFT( + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + _claimPairNFT(poolStates, vars, isBAYC, apeTokenIds, bakcTokenIds); + + IParaApeStaking.PoolState storage poolState; + { + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; + poolState = poolStates[poolId]; + } + vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; + vars.apeToken = isBAYC ? vars.bayc : vars.mayc; + vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; + vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; + vars._nfts = new ApeCoinStaking.SingleNft[](arrayLength); + vars._nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + //check pair status + require( + poolState.pairStatus[apeTokenId].tokenId == bakcTokenId, + "wrong ape and bakc pair" + ); + + //check ntoken owner + { + address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); + address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); + address msgSender = msg.sender; + require( + msgSender == nApeOwner || msgSender == nBakcOwner, + "not owner" + ); + } + + // update pair status + delete poolState.pairStatus[apeTokenId]; + + // we only need to check pair staking position + (, bool isPaired) = vars.apeCoinStaking.mainToBakc( + vars.apeStakingPoolId, + apeTokenId + ); + if (isPaired) { + vars._nfts[vars.stakingPair] = ApeCoinStaking.SingleNft({ + tokenId: apeTokenId, + amount: vars.positionCap.toUint224() + }); + + vars._nftPairs[vars.stakingPair] = ApeCoinStaking + .PairNftWithdrawWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); + vars.stakingPair++; + } + } + + //withdraw from ApeCoinStaking and compound + if (vars.stakingPair > 0) { + //update state + uint256 totalPosition = poolState.totalPosition - vars.stakingPair; + poolState.totalPosition = totalPosition; + + { + ApeCoinStaking.SingleNft[] memory _nfts = vars._nfts; + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _nftPairs = vars._nftPairs; + uint256 stakingPair = vars.stakingPair; + assembly { + mstore(_nfts, stakingPair) + } + assembly { + mstore(_nftPairs, stakingPair) + } + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); + if (isBAYC) { + vars.apeCoinStaking.withdrawBAYC(vars._nfts, address(this)); + vars.apeCoinStaking.withdrawBAKC(vars._nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.withdrawMAYC(vars._nfts, address(this)); + vars.apeCoinStaking.withdrawBAKC(_otherPairs, vars._nftPairs); + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; + + //update reward index + IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WAD + ); + uint256 shareAmount = balanceDiff.wadDiv(cApeExchangeRate); + poolState.accumulatedRewardsPerNft += shareAmount / totalPosition; + } + + //transfer ape and BAKC bakc to nToken + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + IERC721(vars.apeToken).safeTransferFrom( + address(this), + vars.nApe, + apeTokenId + ); + IERC721(vars.bakc).safeTransferFrom( + address(this), + vars.nBakc, + bakcTokenId + ); + + //emit event + emit PairNFTWithdrew(isBAYC, apeTokenId, bakcTokenId); + } + } + + function claimPairNFT( + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + _claimPairNFT(poolStates, vars, isBAYC, apeTokenIds, bakcTokenIds); + } + + function compoundPairNFT( + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + IParaApeStaking.PoolState storage poolState; + { + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; + poolState = poolStates[poolId]; + } + uint256[] memory _nfts = new uint256[](arrayLength); + ApeCoinStaking.PairNft[] + memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + // check pair status + IApeStakingVault.PairingStatus memory localPairStatus = poolState + .pairStatus[apeTokenId]; + require( + localPairStatus.tokenId == bakcTokenId && + localPairStatus.isPaired, + "wrong pair status" + ); + + // construct staking data + _nfts[index] = apeTokenId; + _nftPairs[index] = ApeCoinStaking.PairNft({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId + }); + + //emit event + emit PairNFTCompounded(isBAYC, apeTokenId, bakcTokenId); + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + + //claim from ApeCoinStaking + ApeCoinStaking.PairNft[] + memory _otherPairs = new ApeCoinStaking.PairNft[](0); + if (isBAYC) { + vars.apeCoinStaking.claimSelfBAYC(_nfts); + vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.claimSelfMAYC(_nfts); + vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; + + //update reward index + IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WAD); + uint256 shareAmount = balanceDiff.wadDiv(cApeExchangeRate); + poolState.accumulatedRewardsPerNft += + shareAmount / + poolState.totalPosition; + } + + function _claimPairNFT( + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) internal { + IParaApeStaking.PoolState storage poolState; + { + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; + poolState = poolStates[poolId]; + } + vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; + vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + uint256 rewardShares; + address claimFor; + uint256 arrayLength = apeTokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + //just need to check ape ntoken owner + { + address nApe = isBAYC ? vars.nBayc : vars.nMayc; + address nApeOwner = IERC721(nApe).ownerOf(apeTokenId); + if (claimFor == address(0)) { + claimFor = nApeOwner; + } else { + require(nApeOwner == claimFor, "claim not for same owner"); + } + } + + //check pair status + require( + poolState.pairStatus[apeTokenId].tokenId == bakcTokenId, + "wrong ape and bakc pair" + ); + (, bool isPaired) = vars.apeCoinStaking.mainToBakc( + vars.apeStakingPoolId, + apeTokenId + ); + //if it's not staking in ApeCoinStaking, we skip calculating reward + if (!isPaired) { + continue; + } + + //update reward, to save gas we don't claim pending reward in ApeCoinStaking. + rewardShares += (vars.accumulatedRewardsPerNft - + poolState.rewardsDebt[apeTokenId]); + poolState.rewardsDebt[apeTokenId] = vars.accumulatedRewardsPerNft; + + //emit event + emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); + } + + if (rewardShares > 0) { + IERC20(vars.cApe).safeTransfer(claimFor, rewardShares); + } + } +} diff --git a/contracts/interfaces/IApeStakingP2P.sol b/contracts/interfaces/IApeStakingP2P.sol new file mode 100644 index 000000000..a7cf8cfb0 --- /dev/null +++ b/contracts/interfaces/IApeStakingP2P.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.8.10; + +import "../dependencies/openzeppelin/contracts/IERC20.sol"; +import "../dependencies/yoga-labs/ApeCoinStaking.sol"; + +interface IApeStakingP2P { + enum StakingType { + BAYCStaking, + MAYCStaking, + BAYCPairStaking, + MAYCPairStaking + } + + enum ListingOrderStatus { + Pending, + Matched, + Cancelled + } + + struct ListingOrder { + StakingType stakingType; + address offerer; + address token; + uint32 tokenId; + uint32 share; + uint256 startTime; + uint256 endTime; + uint8 v; + bytes32 r; + bytes32 s; + } + + struct MatchedOrder { + StakingType stakingType; + address apeToken; + uint32 apeTokenId; + uint32 apeShare; + uint32 bakcTokenId; + uint32 bakcShare; + address apeCoinOfferer; + uint32 apeCoinShare; + uint256 apePrincipleAmount; + bytes32 apeCoinListingOrderHash; + } + + struct VaultPosition { + StakingType stakingType; + uint32 mainTokenId; + uint32 bakcTokenId; + } + + /** + * @dev Emit an event whenever an listing order is successfully cancelled. + * @param orderHash The hash of the cancelled order. + * @param offerer The offerer of the cancelled order. + */ + event OrderCancelled(bytes32 orderHash, address indexed offerer); + + /** + * @dev Emitted when a order matched. + * @param orderHash The hash of the matched order + **/ + event PairStakingMatched(bytes32 orderHash); + + /** + * @dev Emitted when a matched order break up. + * @param orderHash The hash of the break up order + **/ + event PairStakingBreakUp(bytes32 orderHash); + + /** + * @dev Emitted when user claimed pending cApe reward. + * @param user The address of the user + * @param receiver The address of the cApe receiver + * @param amount The amount of the cApe been claimed + **/ + event CApeClaimed(address user, address receiver, uint256 amount); + + /** + * @dev Emitted when we claimed pending reward for matched order and compound. + * @param orderHash The hash of the break up order + **/ + event OrderClaimedAndCompounded(bytes32 orderHash, uint256 totalReward); + + /** + * @dev Emitted during rescueERC20() + * @param token The address of the token + * @param to The address of the recipient + * @param amount The amount being rescued + **/ + event RescueERC20( + address indexed token, + address indexed to, + uint256 amount + ); + + /** + * @notice Cancel a listing order, order canceled cannot be matched. + * @param listingOrder the detail info of the order to be canceled + */ + function cancelListing(ListingOrder calldata listingOrder) external; + + /** + * @notice match an apeOrder with an apeCoinOrder to pair staking + * @param apeOrder the ape owner's listing order + * @param apeCoinOrder the Ape Coin owner's listing order + * @return orderHash matched order hash + */ + function matchPairStakingList( + ListingOrder calldata apeOrder, + ListingOrder calldata apeCoinOrder + ) external returns (bytes32 orderHash); + + /** + * @notice match an apeOrder, an bakcOrder with an apeCoinOrder to pair staking + * @param apeOrder the ape owner's listing order + * @param bakcOrder the bakc owner's listing order + * @param apeCoinOrder the Ape Coin owner's listing order + * @return orderHash matched order hash + */ + function matchBAKCPairStakingList( + ListingOrder calldata apeOrder, + ListingOrder calldata bakcOrder, + ListingOrder calldata apeCoinOrder + ) external returns (bytes32 orderHash); + + /** + * @notice break up an matched pair staking order, only participant of the matched order can call. + * @param orderHash the hash of the matched order to be break up + */ + function breakUpMatchedOrder(bytes32 orderHash) external; + + /** + * @notice claim pending reward for matched pair staking orders and deposit as cApe for user to compound. + * @param orderHashes the hash of the matched orders to be break up + */ + function claimForMatchedOrderAndCompound(bytes32[] calldata orderHashes) + external; + + /** + * @param user The address of the user + * @return amount Returns the amount of cApe owned by user + */ + function pendingCApeReward(address user) + external + view + returns (uint256 amount); + + /** + * @notice claim user compounded cApe + * @param receiver The address of the cApe receiver + */ + function claimCApeReward(address receiver) external; + + /** + * @notice get Ape Coin Staking cap for every position. + * @param stakingType the pair staking type + * @return Ape Coin Staking cap + */ + function getApeCoinStakingCap(StakingType stakingType) + external + returns (uint256); +} diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol new file mode 100644 index 000000000..43c130393 --- /dev/null +++ b/contracts/interfaces/IApeStakingVault.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.8.10; + +import "../dependencies/openzeppelin/contracts/IERC20.sol"; +import "../dependencies/yoga-labs/ApeCoinStaking.sol"; + +interface IApeStakingVault { + struct PairingStatus { + uint248 tokenId; + bool isPaired; + } + struct PoolState { + uint256 accumulatedRewardsPerNft; + uint256 totalPosition; + //tokenId => reward debt position + mapping(uint256 => uint256) rewardsDebt; + //apeTokenId => PairingStatus + mapping(uint256 => PairingStatus) pairStatus; + } +} diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol new file mode 100644 index 000000000..240764617 --- /dev/null +++ b/contracts/interfaces/IParaApeStaking.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.8.10; + +import "../dependencies/openzeppelin/contracts/IERC20.sol"; +import "../dependencies/yoga-labs/ApeCoinStaking.sol"; +import "./IApeStakingVault.sol"; +import "./IApeStakingP2P.sol"; + +interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { + struct ApeStakingVaultCacheVars { + address pool; + address bayc; + address mayc; + address bakc; + address nBayc; + address nMayc; + address nBakc; + address apeCoin; + address cApe; + ApeCoinStaking apeCoinStaking; + uint256 compoundFee; + uint256 baycMatchedCap; + uint256 maycMatchedCap; + uint256 bakcMatchedCap; + //optional + bytes32 DOMAIN_SEPARATOR; + address apeToken; + address nApe; + uint256 apeStakingPoolId; + uint256 positionCap; + uint256 accumulatedRewardsPerNft; + uint256 balanceBefore; + uint256 balanceAfter; + ApeCoinStaking.SingleNft[] _nfts; + ApeCoinStaking.PairNftWithdrawWithAmount[] _nftPairs; + uint256 stakingPair; + } +} diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 60bc62a6a..9b558b272 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -20,6 +20,8 @@ interface IPoolApeStaking { uint256 cashAmount; } + function borrowPoolCApe(uint256 amount) external returns (uint256); + /** * @notice Deposit ape coin to BAYC/MAYC pool or BAKC pool * @param stakingInfo Detail info of the staking diff --git a/contracts/protocol/libraries/logic/BorrowLogic.sol b/contracts/protocol/libraries/logic/BorrowLogic.sol index 5ca455ecc..cad627ac1 100644 --- a/contracts/protocol/libraries/logic/BorrowLogic.sol +++ b/contracts/protocol/libraries/logic/BorrowLogic.sol @@ -129,6 +129,57 @@ library BorrowLogic { ); } + /** + * @notice Implements the borrow without collateral feature. + * @dev Emits the `Borrow()` event + * @param reservesData The state of all the reserves + * @param borrowFor The address borrow the asset + * @param asset The address of the borrow asset + * @param amount The borrow amount + */ + function executeBorrowWithoutCollateral( + mapping(address => DataTypes.ReserveData) storage reservesData, + address borrowFor, + address asset, + uint256 amount + ) external returns (uint256) { + DataTypes.ReserveData storage reserve = reservesData[asset]; + DataTypes.ReserveCache memory reserveCache = reserve.cache(); + + reserve.updateState(reserveCache); + + ValidationLogic.validateBorrowWithoutCollateral(reserveCache, amount); + + (, reserveCache.nextScaledVariableDebt) = IVariableDebtToken( + reserveCache.variableDebtTokenAddress + ).mint( + borrowFor, + borrowFor, + amount, + reserveCache.nextVariableBorrowIndex + ); + + reserve.updateInterestRates(reserveCache, asset, 0, amount); + + DataTypes.TimeLockParams memory timeLockParams; + IPToken(reserveCache.xTokenAddress).transferUnderlyingTo( + borrowFor, + amount, + timeLockParams + ); + + emit Borrow( + asset, + borrowFor, + borrowFor, + amount, + reserve.currentVariableBorrowRate, + 0 + ); + + return reserveCache.nextVariableBorrowIndex; + } + /** * @notice Implements the repay feature. Repaying transfers the underlying back to the xToken and clears the * equivalent amount of debt for the user by burning the corresponding debt token. diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 6e0d3e511..462cb448e 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -244,19 +244,12 @@ library ValidationLogic { DataTypes.AssetType assetType; } - /** - * @notice Validates a borrow action. - * @param reservesData The state of all the reserves - * @param reservesList The addresses of all the active reserves - * @param params Additional params needed for the validation - */ - function validateBorrow( - mapping(address => DataTypes.ReserveData) storage reservesData, - mapping(uint256 => address) storage reservesList, - DataTypes.ValidateBorrowParams memory params - ) internal view { - require(params.amount != 0, Errors.INVALID_AMOUNT); - ValidateBorrowLocalVars memory vars; + function validateBorrowBaseInfo( + DataTypes.ReserveCache memory reserveCache, + uint256 amount, + ValidateBorrowLocalVars memory vars + ) internal pure { + require(amount != 0, Errors.INVALID_AMOUNT); ( vars.isActive, @@ -264,7 +257,7 @@ library ValidationLogic { vars.borrowingEnabled, vars.isPaused, vars.assetType - ) = params.reserveCache.reserveConfiguration.getFlags(); + ) = reserveCache.reserveConfiguration.getFlags(); require( vars.assetType == DataTypes.AssetType.ERC20, @@ -275,33 +268,18 @@ library ValidationLogic { require(!vars.isFrozen, Errors.RESERVE_FROZEN); require(vars.borrowingEnabled, Errors.BORROWING_NOT_ENABLED); - require( - params.priceOracleSentinel == address(0) || - IPriceOracleSentinel(params.priceOracleSentinel) - .isBorrowAllowed(), - Errors.PRICE_ORACLE_SENTINEL_CHECK_FAILED - ); - - vars.reserveDecimals = params - .reserveCache - .reserveConfiguration - .getDecimals(); - vars.borrowCap = params - .reserveCache - .reserveConfiguration - .getBorrowCap(); + vars.reserveDecimals = reserveCache.reserveConfiguration.getDecimals(); + vars.borrowCap = reserveCache.reserveConfiguration.getBorrowCap(); unchecked { vars.assetUnit = 10**vars.reserveDecimals; } if (vars.borrowCap != 0) { - vars.totalSupplyVariableDebt = params - .reserveCache + vars.totalSupplyVariableDebt = reserveCache .currScaledVariableDebt - .rayMul(params.reserveCache.nextVariableBorrowIndex); - - vars.totalDebt = vars.totalSupplyVariableDebt + params.amount; + .rayMul(reserveCache.nextVariableBorrowIndex); + vars.totalDebt = vars.totalSupplyVariableDebt + amount; unchecked { require( vars.totalDebt <= vars.borrowCap * vars.assetUnit, @@ -309,6 +287,36 @@ library ValidationLogic { ); } } + } + + function validateBorrowWithoutCollateral( + DataTypes.ReserveCache memory reserveCache, + uint256 amount + ) internal pure { + ValidateBorrowLocalVars memory vars; + validateBorrowBaseInfo(reserveCache, amount, vars); + } + + /** + * @notice Validates a borrow action. + * @param reservesData The state of all the reserves + * @param reservesList The addresses of all the active reserves + * @param params Additional params needed for the validation + */ + function validateBorrow( + mapping(address => DataTypes.ReserveData) storage reservesData, + mapping(uint256 => address) storage reservesList, + DataTypes.ValidateBorrowParams memory params + ) internal view { + ValidateBorrowLocalVars memory vars; + validateBorrowBaseInfo(params.reserveCache, params.amount, vars); + + require( + params.priceOracleSentinel == address(0) || + IPriceOracleSentinel(params.priceOracleSentinel) + .isBorrowAllowed(), + Errors.PRICE_ORACLE_SENTINEL_CHECK_FAILED + ); ( vars.userCollateralInBaseCurrency, diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 8e9289849..f2ecf67ed 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -52,6 +52,7 @@ contract PoolApeStaking is uint24 internal immutable APE_WETH_FEE; uint24 internal immutable WETH_USDC_FEE; address internal immutable WETH; + address internal immutable APE_STAKING_VAULT; event ReserveUsedAsCollateralEnabled( address indexed reserve, @@ -91,7 +92,8 @@ contract PoolApeStaking is ISwapRouter uniswapV3SwapRouter, address weth, uint24 apeWethFee, - uint24 wethUsdcFee + uint24 wethUsdcFee, + address apeStakingVault ) { ADDRESSES_PROVIDER = provider; APE_COMPOUND = apeCompound; @@ -101,12 +103,31 @@ contract PoolApeStaking is WETH = weth; APE_WETH_FEE = apeWethFee; WETH_USDC_FEE = wethUsdcFee; + APE_STAKING_VAULT = apeStakingVault; } function getRevision() internal pure virtual override returns (uint256) { return POOL_REVISION; } + function borrowPoolCApe(uint256 amount) + external + nonReentrant + returns (uint256) + { + require(msg.sender == APE_STAKING_VAULT); + DataTypes.PoolStorage storage ps = poolStorage(); + + uint256 latestBorrowIndex = BorrowLogic.executeBorrowWithoutCollateral( + ps._reserves, + APE_STAKING_VAULT, + address(APE_COMPOUND), + amount + ); + + return latestBorrowIndex; + } + /// @inheritdoc IPoolApeStaking function withdrawApeCoin( address nftAsset, From 797849db20ff70bf6689d0b54054e7e8447a8c95 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 5 Jul 2023 08:37:11 +0800 Subject: [PATCH 41/99] chore: pair staking test case --- contracts/apestaking/ParaApeStaking.sol | 43 ++- .../apestaking/logic/ApeStakingVaultLogic.sol | 180 ++++++----- contracts/interfaces/IApeStakingVault.sol | 1 + contracts/interfaces/IPoolApeStaking.sol | 2 + .../protocol/libraries/logic/BorrowLogic.sol | 5 + contracts/protocol/pool/PoolApeStaking.sol | 14 +- .../tokenization/NTokenApeStaking.sol | 3 + .../protocol/tokenization/NTokenBAKC.sol | 3 + helpers/contracts-deployments.ts | 153 ++++++++- helpers/contracts-getters.ts | 12 + helpers/types.ts | 4 + package.json | 2 +- scripts/deployments/steps/06_pool.ts | 3 + .../deployments/steps/23_renounceOwnership.ts | 26 ++ test/para_ape_staking.spec.ts | 299 ++++++++++++++++++ 15 files changed, 667 insertions(+), 83 deletions(-) create mode 100644 test/para_ape_staking.spec.ts diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index b28d84e50..897285481 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -15,6 +15,7 @@ import {WadRayMath} from "../protocol/libraries/math/WadRayMath.sol"; import "./logic/ApeStakingP2PLogic.sol"; import "./logic/ApeStakingVaultLogic.sol"; import "./logic/ApeStakingCommonLogic.sol"; +import "hardhat/console.sol"; contract ParaApeStaking is Initializable, @@ -239,6 +240,12 @@ contract ParaApeStaking is return ApeStakingP2PLogic.getApeCoinStakingCap(stakingType, vars); } + uint256 constant BAYC_BAKC_PAIR_POOL_ID = 1; + uint256 constant MAYC_BAKC_PAIR_POOL_ID = 2; + uint256 constant BAYC_SINGLE_POOL_ID = 3; + uint256 constant MAYC_SINGLE_POOL_ID = 4; + uint256 constant BAKC_SINGLE_POOL_ID = 5; + mapping(uint256 => PoolState) public poolStates; //address public vaultBot; @@ -248,9 +255,13 @@ contract ParaApeStaking is uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external { + console.log("depositPairNFT---------------------0"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.depositPairNFT( - poolStates, + poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -263,9 +274,13 @@ contract ParaApeStaking is uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external { + console.log("stakingPairNFT---------------------0"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.stakingPairNFT( - poolStates, + poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -279,8 +294,11 @@ contract ParaApeStaking is uint32[] calldata bakcTokenIds ) external { ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.withdrawPairNFT( - poolStates, + poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -295,8 +313,11 @@ contract ParaApeStaking is uint32[] calldata bakcTokenIds ) external { ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.claimPairNFT( - poolStates, + poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -310,8 +331,11 @@ contract ParaApeStaking is uint32[] calldata bakcTokenIds ) external { ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? BAYC_BAKC_PAIR_POOL_ID + : MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.compoundPairNFT( - poolStates, + poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -347,4 +371,13 @@ contract ParaApeStaking is vars.bakcMatchedCap = bakcMatchedCap; return vars; } + + function onERC721Received( + address, + address, + uint256, + bytes memory + ) external pure returns (bytes4) { + return this.onERC721Received.selector; + } } diff --git a/contracts/apestaking/logic/ApeStakingVaultLogic.sol b/contracts/apestaking/logic/ApeStakingVaultLogic.sol index 4220e2775..d4ba3dfdd 100644 --- a/contracts/apestaking/logic/ApeStakingVaultLogic.sol +++ b/contracts/apestaking/logic/ApeStakingVaultLogic.sol @@ -11,6 +11,7 @@ import "../../interfaces/ICApe.sol"; import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; +import "hardhat/console.sol"; /** * @title ApeStakingVaultLogic library @@ -26,12 +27,7 @@ library ApeStakingVaultLogic { uint256 constant BAYC_POOL_ID = 1; uint256 constant MAYC_POOL_ID = 2; uint256 constant BAKC_POOL_ID = 3; - uint256 constant BAYC_BAKC_PAIR_POOL_ID = 1; - uint256 constant MAYC_BAKC_PAIR_POOL_ID = 2; - uint256 constant BAYC_SINGLE_POOL_ID = 3; - uint256 constant MAYC_SINGLE_POOL_ID = 4; - uint256 constant BAKC_SINGLE_POOL_ID = 5; - uint256 internal constant WAD = 1e18; + event PairNFTDeposited( bool isBAYC, uint256 apeTokenId, @@ -47,7 +43,7 @@ library ApeStakingVaultLogic { ); function depositPairNFT( - mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, @@ -59,13 +55,6 @@ library ApeStakingVaultLogic { "wrong param" ); - IParaApeStaking.PoolState storage poolState; - { - uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; - poolState = poolStates[poolId]; - } vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; vars.apeToken = isBAYC ? vars.bayc : vars.mayc; vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; @@ -127,25 +116,19 @@ library ApeStakingVaultLogic { } function stakingPairNFT( - mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external { + console.log("stakingPairNFT---------------------1"); uint256 arrayLength = apeTokenIds.length; require( arrayLength == bakcTokenIds.length && arrayLength > 0, "wrong param" ); - IParaApeStaking.PoolState storage poolState; - { - uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; - poolState = poolStates[poolId]; - } ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); ApeCoinStaking.PairNftDepositWithAmount[] @@ -188,10 +171,19 @@ library ApeStakingVaultLogic { } // prepare Ape coin - uint256 totalNeed = (vars.positionCap + vars.bakcMatchedCap) * + console.log("---------------------0"); + uint256 totalBorrow = (vars.positionCap + vars.bakcMatchedCap) * arrayLength; - IPool(vars.pool).borrowPoolCApe(totalNeed); - IAutoCompoundApe(vars.cApe).withdraw(totalNeed); + uint256 latestBorrowIndex = IPool(vars.pool).borrowPoolCApe( + totalBorrow + ); + IAutoCompoundApe(vars.cApe).withdraw(totalBorrow); + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + poolState.cApeDebtShare += totalBorrow.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); //stake in ApeCoinStaking ApeCoinStaking.PairNftDepositWithAmount[] @@ -211,7 +203,7 @@ library ApeStakingVaultLogic { } function withdrawPairNFT( - mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, @@ -223,15 +215,8 @@ library ApeStakingVaultLogic { "wrong param" ); - _claimPairNFT(poolStates, vars, isBAYC, apeTokenIds, bakcTokenIds); + _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); - IParaApeStaking.PoolState storage poolState; - { - uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; - poolState = poolStates[poolId]; - } vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; vars.apeToken = isBAYC ? vars.bayc : vars.mayc; vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; @@ -319,14 +304,14 @@ library ApeStakingVaultLogic { } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - - //update reward index IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WAD + + _reayAndCompound( + poolState, + vars, + balanceDiff, + vars.positionCap + vars.bakcMatchedCap ); - uint256 shareAmount = balanceDiff.wadDiv(cApeExchangeRate); - poolState.accumulatedRewardsPerNft += shareAmount / totalPosition; } //transfer ape and BAKC bakc to nToken @@ -351,7 +336,7 @@ library ApeStakingVaultLogic { } function claimPairNFT( - mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, @@ -363,11 +348,11 @@ library ApeStakingVaultLogic { "wrong param" ); - _claimPairNFT(poolStates, vars, isBAYC, apeTokenIds, bakcTokenIds); + _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); } function compoundPairNFT( - mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, @@ -379,13 +364,6 @@ library ApeStakingVaultLogic { "wrong param" ); - IParaApeStaking.PoolState storage poolState; - { - uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; - poolState = poolStates[poolId]; - } uint256[] memory _nfts = new uint256[](arrayLength); ApeCoinStaking.PairNft[] memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); @@ -416,41 +394,39 @@ library ApeStakingVaultLogic { vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); //claim from ApeCoinStaking - ApeCoinStaking.PairNft[] + { + ApeCoinStaking.PairNft[] memory _otherPairs = new ApeCoinStaking.PairNft[](0); - if (isBAYC) { - vars.apeCoinStaking.claimSelfBAYC(_nfts); - vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); - } else { - vars.apeCoinStaking.claimSelfMAYC(_nfts); - vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); + if (isBAYC) { + vars.apeCoinStaking.claimSelfBAYC(_nfts); + vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.claimSelfMAYC(_nfts); + vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); + } } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - - //update reward index IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WAD); - uint256 shareAmount = balanceDiff.wadDiv(cApeExchangeRate); - poolState.accumulatedRewardsPerNft += - shareAmount / - poolState.totalPosition; + + //repay and compound + vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; + _reayAndCompound( + poolState, + vars, + balanceDiff, + vars.positionCap + vars.bakcMatchedCap + ); } function _claimPairNFT( - mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) internal { - IParaApeStaking.PoolState storage poolState; - { - uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; - poolState = poolStates[poolId]; - } vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; uint256 rewardShares; @@ -498,4 +474,64 @@ library ApeStakingVaultLogic { IERC20(vars.cApe).safeTransfer(claimFor, rewardShares); } } + + function _reayAndCompound( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 totalAmount, + uint256 positionCap + ) internal { + console.log("_reayAndCompound---------------------------0"); + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + uint256 latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); + uint256 cApeDebtShare = poolState.cApeDebtShare; + uint256 debtInterest = _calculateCurrentPositionDebtInterest( + cApeDebtShare, + poolState.totalPosition, + positionCap, + cApeExchangeRate, + latestBorrowIndex + ); + console.log("_reayAndCompound---------------------------totalAmount:", totalAmount); + console.log("_reayAndCompound---------------------------debtInterest:", debtInterest); + if (debtInterest >= totalAmount) { + console.log("_reayAndCompound---------------------------1"); + IERC20(vars.cApe).safeApprove(vars.pool, totalAmount); + IPool(vars.pool).repay(vars.cApe, totalAmount, address(this)); + cApeDebtShare -= totalAmount.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + } else { + //update reward index + console.log("_reayAndCompound---------------------------2"); + IERC20(vars.cApe).safeApprove(vars.pool, debtInterest); + IPool(vars.pool).repay(vars.cApe, debtInterest, address(this)); + uint256 remainingReward = totalAmount - debtInterest; + uint256 shareAmount = remainingReward.rayDiv(cApeExchangeRate); + poolState.accumulatedRewardsPerNft += + shareAmount / + poolState.totalPosition; + cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + } + console.log("_reayAndCompound---------------------------3"); + poolState.cApeDebtShare = cApeDebtShare; + } + + function _calculateCurrentPositionDebtInterest( + uint256 cApeDebtShare, + uint256 totalPosition, + uint256 perPositionCap, + uint256 cApeExchangeRate, + uint256 latestBorrowIndex + ) internal pure returns (uint256) { + uint256 currentDebt = cApeDebtShare.rayMul(cApeExchangeRate).rayMul( + latestBorrowIndex + ); + return (currentDebt - perPositionCap * totalPosition); + } } diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index 43c130393..34ae812f1 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -16,5 +16,6 @@ interface IApeStakingVault { mapping(uint256 => uint256) rewardsDebt; //apeTokenId => PairingStatus mapping(uint256 => PairingStatus) pairStatus; + uint256 cApeDebtShare; } } diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 9b558b272..3d7fe4925 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -20,6 +20,8 @@ interface IPoolApeStaking { uint256 cashAmount; } + function paraApeStaking() external view returns (address); + function borrowPoolCApe(uint256 amount) external returns (uint256); /** diff --git a/contracts/protocol/libraries/logic/BorrowLogic.sol b/contracts/protocol/libraries/logic/BorrowLogic.sol index cad627ac1..a528c633f 100644 --- a/contracts/protocol/libraries/logic/BorrowLogic.sol +++ b/contracts/protocol/libraries/logic/BorrowLogic.sol @@ -14,6 +14,7 @@ import {DataTypes} from "../types/DataTypes.sol"; import {ValidationLogic} from "./ValidationLogic.sol"; import {ReserveLogic} from "./ReserveLogic.sol"; import {GenericLogic} from "./GenericLogic.sol"; +import "hardhat/console.sol"; /** * @title BorrowLogic library @@ -143,6 +144,7 @@ library BorrowLogic { address asset, uint256 amount ) external returns (uint256) { + console.log("---------------------2"); DataTypes.ReserveData storage reserve = reservesData[asset]; DataTypes.ReserveCache memory reserveCache = reserve.cache(); @@ -150,6 +152,9 @@ library BorrowLogic { ValidationLogic.validateBorrowWithoutCollateral(reserveCache, amount); + console.log("variableDebtTokenAddress:", reserveCache.variableDebtTokenAddress); + console.log("borrowFor:", borrowFor); + console.log("borrow asset:", asset); (, reserveCache.nextScaledVariableDebt) = IVariableDebtToken( reserveCache.variableDebtTokenAddress ).mint( diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index f2ecf67ed..2d331077b 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -27,6 +27,7 @@ import {Math} from "../../dependencies/openzeppelin/contracts/Math.sol"; import {ISwapRouter} from "../../dependencies/univ3/interfaces/ISwapRouter.sol"; import {IPriceOracleGetter} from "../../interfaces/IPriceOracleGetter.sol"; import {Helpers} from "../libraries/helpers/Helpers.sol"; +import "hardhat/console.sol"; contract PoolApeStaking is ParaVersionedInitializable, @@ -52,7 +53,7 @@ contract PoolApeStaking is uint24 internal immutable APE_WETH_FEE; uint24 internal immutable WETH_USDC_FEE; address internal immutable WETH; - address internal immutable APE_STAKING_VAULT; + address internal immutable PARA_APE_STAKING; event ReserveUsedAsCollateralEnabled( address indexed reserve, @@ -103,24 +104,29 @@ contract PoolApeStaking is WETH = weth; APE_WETH_FEE = apeWethFee; WETH_USDC_FEE = wethUsdcFee; - APE_STAKING_VAULT = apeStakingVault; + PARA_APE_STAKING = apeStakingVault; } function getRevision() internal pure virtual override returns (uint256) { return POOL_REVISION; } + function paraApeStaking() external view returns (address) { + return PARA_APE_STAKING; + } + function borrowPoolCApe(uint256 amount) external nonReentrant returns (uint256) { - require(msg.sender == APE_STAKING_VAULT); + require(msg.sender == PARA_APE_STAKING); DataTypes.PoolStorage storage ps = poolStorage(); + console.log("---------------------1"); uint256 latestBorrowIndex = BorrowLogic.executeBorrowWithoutCollateral( ps._reserves, - APE_STAKING_VAULT, + PARA_APE_STAKING, address(APE_COMPOUND), amount ); diff --git a/contracts/protocol/tokenization/NTokenApeStaking.sol b/contracts/protocol/tokenization/NTokenApeStaking.sol index 4c11c4edf..34a16e6f1 100644 --- a/contracts/protocol/tokenization/NTokenApeStaking.sol +++ b/contracts/protocol/tokenization/NTokenApeStaking.sol @@ -70,6 +70,9 @@ abstract contract NTokenApeStaking is NToken, INTokenApeStaking { } getBAKC().setApprovalForAll(address(POOL), true); + address paraApeStaking = POOL.paraApeStaking(); + IERC721(underlyingAsset).setApprovalForAll(paraApeStaking, true); + super.initialize( initializingPool, underlyingAsset, diff --git a/contracts/protocol/tokenization/NTokenBAKC.sol b/contracts/protocol/tokenization/NTokenBAKC.sol index 3252e519f..68e17a130 100644 --- a/contracts/protocol/tokenization/NTokenBAKC.sol +++ b/contracts/protocol/tokenization/NTokenBAKC.sol @@ -69,6 +69,9 @@ contract NTokenBAKC is NToken { ape.approve(nMAYC, type(uint256).max); } IERC721(underlyingAsset).setApprovalForAll(address(POOL), true); + + address paraApeStaking = POOL.paraApeStaking(); + IERC721(underlyingAsset).setApprovalForAll(paraApeStaking, true); } function _transfer( diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 871052af3..c49312a78 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -291,6 +291,12 @@ import { NTokenChromieSquiggle__factory, CLFixedPriceSynchronicityPriceAdapter, CLFixedPriceSynchronicityPriceAdapter__factory, + ParaApeStaking, + ParaApeStaking__factory, + ApeStakingP2PLogic__factory, + ApeStakingP2PLogic, + ApeStakingVaultLogic, + ApeStakingVaultLogic__factory, } from "../types"; import {MockContract} from "ethereum-waffle"; import { @@ -309,6 +315,7 @@ import { getP2PPairStaking, getAutoYieldApe, getHelperContract, + getParaApeStaking, } from "./contracts-getters"; import { convertToCurrencyDecimals, @@ -337,6 +344,8 @@ import {pick, upperFirst} from "lodash"; import {ZERO_ADDRESS} from "./constants"; import {GLOBAL_OVERRIDES} from "./hardhat-constants"; import {parseEther} from "ethers/lib/utils"; +import {zeroAddress} from "ethereumjs-util"; +import {ParaApeStakingLibraryAddresses} from "../types/factories/contracts/apestaking/ParaApeStaking__factory"; export const deployPoolAddressesProvider = async ( marketId: string, @@ -891,6 +900,7 @@ export const deployPoolComponents = async ( allTokens.WETH.address, APE_WETH_FEE, WETH_USDC_FEE, + (await getParaApeStaking()).address, ], verify, false, @@ -2346,7 +2356,7 @@ export const deployApeCoinStaking = async (verify?: boolean) => { amount, "1666771200", "1761465600", - parseEther("100000"), + parseEther("50000"), GLOBAL_OVERRIDES ); return apeCoinStaking; @@ -2703,6 +2713,147 @@ export const deployP2PPairStaking = async (verify?: boolean) => { return await getP2PPairStaking(proxyInstance.address); }; +export const deployApeStakingP2PLogic = async (verify?: boolean) => + withSaveAndVerify( + new ApeStakingP2PLogic__factory(await getFirstSigner()), + eContractid.ApeStakingP2PLogic, + [], + verify + ) as Promise; + +export const deployApeStakingVaultLogic = async (verify?: boolean) => + withSaveAndVerify( + new ApeStakingVaultLogic__factory(await getFirstSigner()), + eContractid.ApeStakingVaultLogic, + [], + verify + ) as Promise; + +export const deployParaApeStakingLibraries = async ( + verify?: boolean +): Promise => { + const p2pLogic = await deployApeStakingP2PLogic(verify); + const vaultLogic = await deployApeStakingVaultLogic(verify); + + return { + ["contracts/apestaking/logic/ApeStakingP2PLogic.sol:ApeStakingP2PLogic"]: + p2pLogic.address, + ["contracts/apestaking/logic/ApeStakingVaultLogic.sol:ApeStakingVaultLogic"]: + vaultLogic.address, + }; +}; + +export const deployFakeParaApeStakingImpl = async (verify?: boolean) => { + const allTokens = await getAllTokens(); + const apeCoinStaking = + (await getContractAddressInDb(eContractid.ApeCoinStaking)) || + (await deployApeCoinStaking(verify)).address; + + const args = [ + zeroAddress(), + zeroAddress(), + zeroAddress(), + zeroAddress(), + zeroAddress(), + zeroAddress(), + zeroAddress(), + allTokens.APE.address, + allTokens.cAPE.address, + apeCoinStaking, + zeroAddress(), + 0, + ]; + + const libraries = await deployParaApeStakingLibraries(); + + return withSaveAndVerify( + new ParaApeStaking__factory(libraries, await getFirstSigner()), + eContractid.ParaApeStakingImpl, + [...args], + verify + ) as Promise; +}; + +export const deployParaApeStakingImpl = async ( + compoundFee: number, + verify?: boolean +) => { + const poolProxy = await getPoolProxy(); + const allTokens = await getAllTokens(); + const protocolDataProvider = await getProtocolDataProvider(); + const nBAYC = ( + await protocolDataProvider.getReserveTokensAddresses(allTokens.BAYC.address) + ).xTokenAddress; + const nMAYC = ( + await protocolDataProvider.getReserveTokensAddresses(allTokens.MAYC.address) + ).xTokenAddress; + const nBAKC = ( + await protocolDataProvider.getReserveTokensAddresses(allTokens.BAKC.address) + ).xTokenAddress; + const apeCoinStaking = + (await getContractAddressInDb(eContractid.ApeCoinStaking)) || + (await deployApeCoinStaking(verify)).address; + const aclManager = await getACLManager(); + const args = [ + poolProxy.address, + allTokens.BAYC.address, + allTokens.MAYC.address, + allTokens.BAKC.address, + nBAYC, + nMAYC, + nBAKC, + allTokens.APE.address, + allTokens.cAPE.address, + apeCoinStaking, + aclManager.address, + compoundFee, + ]; + + const libraries = await deployParaApeStakingLibraries(); + + return withSaveAndVerify( + new ParaApeStaking__factory(libraries, await getFirstSigner()), + eContractid.ParaApeStakingImpl, + [...args], + verify + ) as Promise; +}; + +export const deployParaApeStaking = async ( + fakeImplementation: boolean, + verify?: boolean +) => { + let stakingImplementation; + if (fakeImplementation) { + stakingImplementation = await deployFakeParaApeStakingImpl(verify); + } else { + stakingImplementation = await deployParaApeStakingImpl(0, verify); + } + const deployer = await getFirstSigner(); + const deployerAddress = await deployer.getAddress(); + const initData = + stakingImplementation.interface.encodeFunctionData("initialize"); + + const proxyInstance = await withSaveAndVerify( + new InitializableAdminUpgradeabilityProxy__factory(await getFirstSigner()), + eContractid.ParaApeStaking, + [], + verify + ); + await waitForTx( + await (proxyInstance as InitializableAdminUpgradeabilityProxy)[ + "initialize(address,address,bytes)" + ]( + stakingImplementation.address, + deployerAddress, + initData, + GLOBAL_OVERRIDES + ) + ); + + return await getParaApeStaking(proxyInstance.address); +}; + export const deployAutoYieldApeImpl = async (verify?: boolean) => { const allTokens = await getAllTokens(); const apeCoinStaking = diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index 60d60be45..e3e03c5c2 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -97,6 +97,7 @@ import { NTokenStakefish__factory, MockLendPool__factory, NTokenChromieSquiggle__factory, + ParaApeStaking__factory, } from "../types"; import { getEthersSigners, @@ -995,6 +996,17 @@ export const getP2PPairStaking = async (address?: tEthereumAddress) => await getFirstSigner() ); +export const getParaApeStaking = async (address?: tEthereumAddress) => + await ParaApeStaking__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.ParaApeStaking}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); + export const getHelperContract = async (address?: tEthereumAddress) => await HelperContract__factory.connect( address || diff --git a/helpers/types.ts b/helpers/types.ts index 20d97c1ae..f4fe1879d 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -260,6 +260,10 @@ export enum eContractid { HelperContractImpl = "HelperContractImpl", HelperContract = "HelperContract", P2PPairStakingImpl = "P2PPairStakingImpl", + ApeStakingP2PLogic = "ApeStakingP2PLogic", + ApeStakingVaultLogic = "ApeStakingVaultLogic", + ParaApeStakingImpl = "ParaApeStakingImpl", + ParaApeStaking = "ParaApeStaking", yAPE = "yAPE", yAPEImpl = "yAPEImpl", ParaProxyInterfacesImpl = "ParaProxyInterfacesImpl", diff --git a/package.json b/package.json index d1ac692b9..cf21f5564 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "coverage": "hardhat coverage --testfiles 'test/*.ts'", "format": "prettier --write 'contracts/**/*.sol' 'scripts/**/*.ts' 'helpers/**/*.ts' 'tasks/**/*.ts' 'test/**/*.ts' 'hardhat.config.ts' 'helper-hardhat-config.ts' 'market-config/**/*.ts'", "doc": "hardhat docgen", - "test": "hardhat test ./test/*.ts", + "test": "hardhat test ./test/para_ape_staking.spec.ts", "clean": "hardhat clean" }, "devDependencies": { diff --git a/scripts/deployments/steps/06_pool.ts b/scripts/deployments/steps/06_pool.ts index efdff9ff1..34742232f 100644 --- a/scripts/deployments/steps/06_pool.ts +++ b/scripts/deployments/steps/06_pool.ts @@ -1,6 +1,7 @@ import {ZERO_ADDRESS} from "../../../helpers/constants"; import { deployMockBendDaoLendPool, + deployParaApeStaking, deployPoolComponents, deployPoolParaProxyInterfaces, deployPoolPositionMover, @@ -30,6 +31,8 @@ export const step_06 = async (verify = false) => { const paraSpaceConfig = getParaSpaceConfig(); try { + await deployParaApeStaking(true); + const { poolCore, poolParameters, diff --git a/scripts/deployments/steps/23_renounceOwnership.ts b/scripts/deployments/steps/23_renounceOwnership.ts index 5c5d7e937..7c492116a 100644 --- a/scripts/deployments/steps/23_renounceOwnership.ts +++ b/scripts/deployments/steps/23_renounceOwnership.ts @@ -8,6 +8,7 @@ import { getInitializableAdminUpgradeabilityProxy, getNFTFloorOracle, getP2PPairStaking, + getParaApeStaking, getPausableZoneController, getPoolAddressesProvider, getPoolAddressesProviderRegistry, @@ -396,6 +397,31 @@ export const step_23 = async ( console.log(); } + //////////////////////////////////////////////////////////////////////////////// + // ParaApeStaking + //////////////////////////////////////////////////////////////////////////////// + if (await getContractAddressInDb(eContractid.ParaApeStaking)) { + console.time("transferring ParaApeStaking ownership..."); + const paraApeStaking = await getParaApeStaking(); + const paraApeStakingProxy = + await getInitializableAdminUpgradeabilityProxy(paraApeStaking.address); + const signers = await getEthersSigners(); + const adminAddress = signers[5].getAddress(); + if (DRY_RUN) { + const encodedData1 = paraApeStakingProxy.interface.encodeFunctionData( + "changeAdmin", + [adminAddress] + ); + await dryRunEncodedData(paraApeStakingProxy.address, encodedData1); + } else { + await waitForTx( + await paraApeStakingProxy.changeAdmin(adminAddress, GLOBAL_OVERRIDES) + ); + } + console.timeEnd("transferring ParaApeStaking ownership..."); + console.log(); + } + //////////////////////////////////////////////////////////////////////////////// // HelperContract //////////////////////////////////////////////////////////////////////////////// diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts new file mode 100644 index 000000000..6a1f1fb45 --- /dev/null +++ b/test/para_ape_staking.spec.ts @@ -0,0 +1,299 @@ +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect} from "chai"; +import { + AutoCompoundApe, + P2PPairStaking, + ParaApeStaking, + VariableDebtToken, +} from "../types"; +import {TestEnv} from "./helpers/make-suite"; +import {testEnvFixture} from "./helpers/setup-env"; +import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; +import { + getAutoCompoundApe, + getInitializableAdminUpgradeabilityProxy, + getP2PPairStaking, + getParaApeStaking, + getVariableDebtToken, +} from "../helpers/contracts-getters"; +import {MAX_UINT_AMOUNT} from "../helpers/constants"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; +import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; +import {parseEther} from "ethers/lib/utils"; +import {almostEqual} from "./helpers/uniswapv3-helper"; +import { + deployP2PPairStakingImpl, + deployParaApeStakingImpl, +} from "../helpers/contracts-deployments"; +import {GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; +import {ProtocolErrors} from "../helpers/types"; + +describe("Para Ape Staking Test", () => { + let testEnv: TestEnv; + let variableDebtCApeCoin: VariableDebtToken; + let paraApeStaking: ParaApeStaking; + let cApe: AutoCompoundApe; + let MINIMUM_LIQUIDITY; + + const fixture = async () => { + testEnv = await loadFixture(testEnvFixture); + const { + ape, + users: [user1, , , user4, user5, user6], + apeCoinStaking, + pool, + protocolDataProvider, + } = testEnv; + + //upgrade to non-fake implementation + const paraApeStakingImpl = await deployParaApeStakingImpl(0); + paraApeStaking = await getParaApeStaking(); + const paraApeStakingProxy = await getInitializableAdminUpgradeabilityProxy( + paraApeStaking.address + ); + await waitForTx( + await paraApeStakingProxy + .connect(user5.signer) + .upgradeTo(paraApeStakingImpl.address, GLOBAL_OVERRIDES) + ); + + cApe = await getAutoCompoundApe(); + MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + + const {variableDebtTokenAddress: variableDebtCApeCoinAddress} = + await protocolDataProvider.getReserveTokensAddresses(cApe.address); + variableDebtCApeCoin = await getVariableDebtToken( + variableDebtCApeCoinAddress + ); + console.log("paraApeStaking address:", paraApeStaking.address); + console.log("variableDebtCApeCoinAddress:", variableDebtCApeCoinAddress); + + // send extra tokens to the apestaking contract for rewards + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"]( + apeCoinStaking.address, + parseEther("100000000000") + ) + ); + + // user4 deposit MINIMUM_LIQUIDITY to make test case easy + await mintAndValidate(ape, "1", user6); + await waitForTx( + await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) + ); + + // user3 deposit and supply cApe to MM + await mintAndValidate(ape, "10000000", user4); + await waitForTx( + await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user4.signer) + .deposit(user4.address, parseEther("10000000")) + ); + await waitForTx( + await cApe.connect(user4.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await pool + .connect(user4.signer) + .supply(cApe.address, parseEther("10000000"), user4.address, 0) + ); + + return testEnv; + }; + + it("test BAYC + BAKC pool logic", async () => { + const { + users: [user1, user2, user3], + bayc, + bakc, + nBAYC, + nBAKC, + apeCoinStaking, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositPairNFT(true, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositPairNFT(true, [2], [2]) + ); + expect (await bayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect (await bayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect (await bayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + expect (await bakc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect (await bakc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect (await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); + + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .stakingPairNFT(true, [0, 1, 2], [0, 1, 2]) + ); + expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq(parseEther("200000")); + expect((await apeCoinStaking.nftPosition(1, 1)).stakedAmount).to.be.eq(parseEther("200000")); + expect((await apeCoinStaking.nftPosition(1, 2)).stakedAmount).to.be.eq(parseEther("200000")); + expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq(parseEther("50000")); + expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq(parseEther("50000")); + expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq(parseEther("50000")); + expect( + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + ).to.be.closeTo(parseEther("750000"), parseEther("10")); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .compoundPairNFT(true, [0, 1, 2], [0, 1, 2]) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimPairNFT(true, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .claimPairNFT(true, [2], [2]) + ); + const user1Balance = await cApe.balanceOf(user1.address); + const user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .withdrawPairNFT(true, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .withdrawPairNFT(true, [2], [2]) + ); + expect (await bayc.ownerOf(0)).to.be.equal(nBAYC.address); + expect (await bayc.ownerOf(1)).to.be.equal(nBAYC.address); + expect (await bayc.ownerOf(2)).to.be.equal(nBAYC.address); + expect (await bakc.ownerOf(0)).to.be.equal(nBAKC.address); + expect (await bakc.ownerOf(1)).to.be.equal(nBAKC.address); + expect (await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + }); + + it("test MAYC + BAKC pool logic", async () => { + const { + users: [user1, user2, user3], + mayc, + bakc, + nMAYC, + nBAKC, + apeCoinStaking, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await nMAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositPairNFT(false, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositPairNFT(false, [2], [2]) + ); + expect (await mayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect (await mayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect (await mayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + expect (await bakc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect (await bakc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect (await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); + + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .stakingPairNFT(false, [0, 1, 2], [0, 1, 2]) + ); + expect((await apeCoinStaking.nftPosition(2, 0)).stakedAmount).to.be.eq(parseEther("100000")); + expect((await apeCoinStaking.nftPosition(2, 1)).stakedAmount).to.be.eq(parseEther("100000")); + expect((await apeCoinStaking.nftPosition(2, 2)).stakedAmount).to.be.eq(parseEther("100000")); + expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq(parseEther("50000")); + expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq(parseEther("50000")); + expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq(parseEther("50000")); + expect( + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + ).to.be.closeTo(parseEther("450000"), parseEther("10")); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .compoundPairNFT(false, [0, 1, 2], [0, 1, 2]) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimPairNFT(false, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .claimPairNFT(false, [2], [2]) + ); + const user1Balance = await cApe.balanceOf(user1.address); + const user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .withdrawPairNFT(false, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .withdrawPairNFT(false, [2], [2]) + ); + expect (await mayc.ownerOf(0)).to.be.equal(nMAYC.address); + expect (await mayc.ownerOf(1)).to.be.equal(nMAYC.address); + expect (await mayc.ownerOf(2)).to.be.equal(nMAYC.address); + expect (await bakc.ownerOf(0)).to.be.equal(nBAKC.address); + expect (await bakc.ownerOf(1)).to.be.equal(nBAKC.address); + expect (await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + }); +}); From e4765ecb69258031083d1caf00ce5e80b550ee05 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 6 Jul 2023 23:10:16 +0800 Subject: [PATCH 42/99] chore: bayc + mayc + bakc base logic and test case --- contracts/apestaking/ParaApeStaking.sol | 144 ++- .../apestaking/logic/ApeStakingVaultLogic.sol | 828 ++++++++++++++++-- contracts/interfaces/IApeStakingVault.sol | 24 +- contracts/interfaces/IParaApeStaking.sol | 5 +- .../protocol/libraries/logic/BorrowLogic.sol | 5 - contracts/protocol/pool/PoolApeStaking.sol | 2 - test/para_ape_staking.spec.ts | 346 ++++++-- 7 files changed, 1182 insertions(+), 172 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 897285481..dac3221c9 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -15,7 +15,6 @@ import {WadRayMath} from "../protocol/libraries/math/WadRayMath.sol"; import "./logic/ApeStakingP2PLogic.sol"; import "./logic/ApeStakingVaultLogic.sol"; import "./logic/ApeStakingCommonLogic.sol"; -import "hardhat/console.sol"; contract ParaApeStaking is Initializable, @@ -240,28 +239,19 @@ contract ParaApeStaking is return ApeStakingP2PLogic.getApeCoinStakingCap(stakingType, vars); } - uint256 constant BAYC_BAKC_PAIR_POOL_ID = 1; - uint256 constant MAYC_BAKC_PAIR_POOL_ID = 2; - uint256 constant BAYC_SINGLE_POOL_ID = 3; - uint256 constant MAYC_SINGLE_POOL_ID = 4; - uint256 constant BAKC_SINGLE_POOL_ID = 5; - - mapping(uint256 => PoolState) public poolStates; - - //address public vaultBot; + VaultStorage internal vaultStorage; function depositPairNFT( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external { - console.log("depositPairNFT---------------------0"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.depositPairNFT( - poolStates[poolId], + vaultStorage.poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -274,13 +264,12 @@ contract ParaApeStaking is uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external { - console.log("stakingPairNFT---------------------0"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.stakingPairNFT( - poolStates[poolId], + vaultStorage.poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -295,10 +284,10 @@ contract ParaApeStaking is ) external { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.withdrawPairNFT( - poolStates[poolId], + vaultStorage.poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -314,10 +303,10 @@ contract ParaApeStaking is ) external { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.claimPairNFT( - poolStates[poolId], + vaultStorage.poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -332,10 +321,10 @@ contract ParaApeStaking is ) external { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? BAYC_BAKC_PAIR_POOL_ID - : MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingVaultLogic.compoundPairNFT( - poolStates[poolId], + vaultStorage.poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -343,11 +332,106 @@ contract ParaApeStaking is ); } - function depositBAYC(uint256[] calldata apeTokenIds) external {} + function depositNFT(address nft, uint32[] calldata tokenIds) external { + require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = (nft == bayc) + ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID + : (nft == mayc) + ? ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID + : ApeStakingVaultLogic.BAKC_SINGLE_POOL_ID; + ApeStakingVaultLogic.depositNFT( + vaultStorage.poolStates[poolId], + vars, + nft, + tokenIds + ); + } + + function stakingApe(address nft, uint32[] calldata tokenIds) external { + require(nft == bayc || nft == mayc, "wrong nft"); + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = (nft == bayc) + ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID + : ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID; + ApeStakingVaultLogic.stakingApe( + vaultStorage.poolStates[poolId], + vars, + nft, + tokenIds + ); + } + + function stakingBAKC( + address nft, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = (nft == bayc) + ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID + : ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID; + ApeStakingVaultLogic.stakingBAKC( + vaultStorage.poolStates[poolId], + vaultStorage.poolStates[ApeStakingVaultLogic.BAKC_SINGLE_POOL_ID], + vars, + nft, + apeTokenIds, + bakcTokenIds + ); + } + + function compoundApe(address nft, uint32[] calldata tokenIds) external { + require(nft == bayc || nft == mayc, "wrong nft"); + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = (nft == bayc) + ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID + : ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID; + ApeStakingVaultLogic.compoundApe( + vaultStorage.poolStates[poolId], + vars, + nft, + tokenIds + ); + } - function depositMAYC(uint256[] calldata apeTokenIds) external {} + function compoundBAKC( + address nft, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + require(nft == bayc || nft == mayc, "wrong nft"); + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingVaultLogic.compoundBAKC( + vaultStorage, + vars, + nft, + apeTokenIds, + bakcTokenIds + ); + } + + function claimNFT(address nft, uint32[] calldata tokenIds) external { + require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = (nft == bayc) + ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID + : (nft == mayc) + ? ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID + : ApeStakingVaultLogic.BAKC_SINGLE_POOL_ID; + ApeStakingVaultLogic.claimNFT( + vaultStorage.poolStates[poolId], + vars, + nft, + tokenIds + ); + } - function depositBAKC(uint256[] calldata bakcTokenIds) external {} + function withdrawNFT(address nft, uint32[] calldata tokenIds) external { + require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingVaultLogic.withdrawNFT(vaultStorage, vars, nft, tokenIds); + } function _createCacheVars() internal diff --git a/contracts/apestaking/logic/ApeStakingVaultLogic.sol b/contracts/apestaking/logic/ApeStakingVaultLogic.sol index d4ba3dfdd..f7f5ce43d 100644 --- a/contracts/apestaking/logic/ApeStakingVaultLogic.sol +++ b/contracts/apestaking/logic/ApeStakingVaultLogic.sol @@ -11,7 +11,6 @@ import "../../interfaces/ICApe.sol"; import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; -import "hardhat/console.sol"; /** * @title ApeStakingVaultLogic library @@ -24,6 +23,12 @@ library ApeStakingVaultLogic { using SafeERC20 for IERC20; using WadRayMath for uint256; + uint256 public constant BAYC_BAKC_PAIR_POOL_ID = 1; + uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; + uint256 public constant BAYC_SINGLE_POOL_ID = 3; + uint256 public constant MAYC_SINGLE_POOL_ID = 4; + uint256 public constant BAKC_SINGLE_POOL_ID = 5; + uint256 constant BAYC_POOL_ID = 1; uint256 constant MAYC_POOL_ID = 2; uint256 constant BAKC_POOL_ID = 3; @@ -42,6 +47,13 @@ library ApeStakingVaultLogic { uint256 bakcTokenId ); + event NFTDeposited(address nft, uint256 tokenId); + event NFTStaked(address nft, uint256 tokenId); + event NFTPairStaked(address nft, uint256 apeTokenId, uint256 bakcTokenId); + event NFTCompounded(address nft, uint256 tokenId); + event NFTClaimed(address nft, uint256 tokenId); + event NFTWithdrawn(address nft, uint256 tokenId); + function depositPairNFT( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, @@ -59,6 +71,7 @@ library ApeStakingVaultLogic { vars.apeToken = isBAYC ? vars.bayc : vars.mayc; vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; address msgSender = msg.sender; + vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -98,6 +111,12 @@ library ApeStakingVaultLogic { isPaired: true }); + //update token status + poolState.tokenStatus[apeTokenId] = IApeStakingVault.TokenStatus({ + rewardsDebt: vars.accumulatedRewardsPerNft, + isInPool: true + }); + //transfer ape and BAKC IERC721(vars.apeToken).safeTransferFrom( vars.nApe, @@ -113,6 +132,8 @@ library ApeStakingVaultLogic { //emit event emit PairNFTDeposited(isBAYC, apeTokenId, bakcTokenId); } + + poolState.totalPosition += arrayLength.toUint128(); } function stakingPairNFT( @@ -122,7 +143,6 @@ library ApeStakingVaultLogic { uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external { - console.log("stakingPairNFT---------------------1"); uint256 arrayLength = apeTokenIds.length; require( arrayLength == bakcTokenIds.length && arrayLength > 0, @@ -136,7 +156,6 @@ library ApeStakingVaultLogic { arrayLength ); vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; - vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -152,9 +171,6 @@ library ApeStakingVaultLogic { ); } - //update state - poolState.rewardsDebt[apeTokenId] = vars.accumulatedRewardsPerNft; - // construct staking data _nfts[index] = ApeCoinStaking.SingleNft({ tokenId: apeTokenId, @@ -171,19 +187,9 @@ library ApeStakingVaultLogic { } // prepare Ape coin - console.log("---------------------0"); uint256 totalBorrow = (vars.positionCap + vars.bakcMatchedCap) * arrayLength; - uint256 latestBorrowIndex = IPool(vars.pool).borrowPoolCApe( - totalBorrow - ); - IAutoCompoundApe(vars.cApe).withdraw(totalBorrow); - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); - poolState.cApeDebtShare += totalBorrow.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate - ); + _borrowCApeFromPool(poolState, vars, totalBorrow); //stake in ApeCoinStaking ApeCoinStaking.PairNftDepositWithAmount[] @@ -197,9 +203,6 @@ library ApeStakingVaultLogic { vars.apeCoinStaking.depositMAYC(_nfts); vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); } - - //update state - poolState.totalPosition += arrayLength; } function withdrawPairNFT( @@ -248,6 +251,7 @@ library ApeStakingVaultLogic { // update pair status delete poolState.pairStatus[apeTokenId]; + delete poolState.tokenStatus[apeTokenId]; // we only need to check pair staking position (, bool isPaired) = vars.apeCoinStaking.mainToBakc( @@ -271,12 +275,11 @@ library ApeStakingVaultLogic { } } + //update state + poolState.totalPosition -= arrayLength.toUint128(); + //withdraw from ApeCoinStaking and compound if (vars.stakingPair > 0) { - //update state - uint256 totalPosition = poolState.totalPosition - vars.stakingPair; - poolState.totalPosition = totalPosition; - { ApeCoinStaking.SingleNft[] memory _nfts = vars._nfts; ApeCoinStaking.PairNftWithdrawWithAmount[] @@ -306,15 +309,20 @@ library ApeStakingVaultLogic { uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - _reayAndCompound( + uint256 totalRepay = _reayAndCompound( poolState, vars, balanceDiff, vars.positionCap + vars.bakcMatchedCap ); + + if (totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); + IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + } } - //transfer ape and BAKC bakc to nToken + //transfer ape and BAKC back to nToken for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -396,7 +404,7 @@ library ApeStakingVaultLogic { //claim from ApeCoinStaking { ApeCoinStaking.PairNft[] - memory _otherPairs = new ApeCoinStaking.PairNft[](0); + memory _otherPairs = new ApeCoinStaking.PairNft[](0); if (isBAYC) { vars.apeCoinStaking.claimSelfBAYC(_nfts); vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); @@ -412,12 +420,17 @@ library ApeStakingVaultLogic { //repay and compound vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; - _reayAndCompound( + uint256 totalRepay = _reayAndCompound( poolState, vars, balanceDiff, vars.positionCap + vars.bakcMatchedCap ); + + if (totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); + IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + } } function _claimPairNFT( @@ -427,7 +440,6 @@ library ApeStakingVaultLogic { uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) internal { - vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; uint256 rewardShares; address claimFor; @@ -452,22 +464,658 @@ library ApeStakingVaultLogic { poolState.pairStatus[apeTokenId].tokenId == bakcTokenId, "wrong ape and bakc pair" ); - (, bool isPaired) = vars.apeCoinStaking.mainToBakc( - vars.apeStakingPoolId, - apeTokenId + + //update reward, to save gas we don't claim pending reward in ApeCoinStaking. + rewardShares += (vars.accumulatedRewardsPerNft - + poolState.tokenStatus[apeTokenId].rewardsDebt); + poolState.tokenStatus[apeTokenId].rewardsDebt = vars + .accumulatedRewardsPerNft; + + //emit event + emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); + } + + if (rewardShares > 0) { + IERC20(vars.cApe).safeTransfer(claimFor, rewardShares); + } + } + + function depositNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) external { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, "wrong param"); + + address msgSender = msg.sender; + vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + if (nft == vars.bakc) { + address nTokenOwner = IERC721(vars.nBakc).ownerOf(tokenId); + require(msgSender == nTokenOwner, "not owner"); + + (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( + BAKC_POOL_ID, + tokenId + ); + require(stakedAmount == 0, "bakc already staked"); + + IERC721(nft).safeTransferFrom( + vars.nBakc, + address(this), + tokenId + ); + } else { + vars.nApe = (nft == vars.bayc) ? vars.nBayc : vars.nMayc; + vars.apeStakingPoolId = (nft == vars.bayc) + ? BAYC_POOL_ID + : MAYC_POOL_ID; + + address nApeOwner = IERC721(vars.nApe).ownerOf(tokenId); + require(msgSender == nApeOwner, "not ape owner"); + + (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( + vars.apeStakingPoolId, + tokenId + ); + require(stakedAmount == 0, "ape already staked"); + + (, bool isPaired) = vars.apeCoinStaking.mainToBakc( + vars.apeStakingPoolId, + tokenId + ); + require(!isPaired, "ape already pair staked"); + + IERC721(nft).safeTransferFrom( + vars.nApe, + address(this), + tokenId + ); + } + + //update token status + poolState.tokenStatus[tokenId] = IApeStakingVault.TokenStatus({ + rewardsDebt: vars.accumulatedRewardsPerNft, + isInPool: true + }); + + //emit event + emit NFTDeposited(nft, tokenId); + } + + poolState.totalPosition += arrayLength.toUint128(); + } + + function stakingApe( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) external { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, "wrong param"); + + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + vars.positionCap = (nft == vars.bayc) + ? vars.baycMatchedCap + : vars.maycMatchedCap; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + require( + poolState.tokenStatus[tokenId].isInPool, + "ape not in single pool" + ); + + // construct staking data + _nfts[index] = ApeCoinStaking.SingleNft({ + tokenId: tokenId, + amount: vars.positionCap.toUint224() + }); + + //emit event + emit NFTStaked(nft, tokenId); + } + + // prepare Ape coin + uint256 totalBorrow = vars.positionCap * arrayLength; + _borrowCApeFromPool(poolState, vars, totalBorrow); + + //stake in ApeCoinStaking + if (nft == vars.bayc) { + vars.apeCoinStaking.depositBAYC(_nfts); + } else { + vars.apeCoinStaking.depositMAYC(_nfts); + } + } + + function stakingBAKC( + IParaApeStaking.PoolState storage apePoolState, + IParaApeStaking.PoolState storage bakcPoolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + ApeCoinStaking.PairNftDepositWithAmount[] + memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + arrayLength + ); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + require( + apePoolState.tokenStatus[apeTokenId].isInPool, + "ape not in single pool" + ); + require( + bakcPoolState.tokenStatus[bakcTokenId].isInPool, + "ape not in single pool" + ); + + // construct staking data + _nftPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184() + }); + + //emit event + emit NFTPairStaked(nft, apeTokenId, bakcTokenId); + } + + // prepare Ape coin + uint256 totalBorrow = vars.bakcMatchedCap * arrayLength; + _borrowCApeFromPool(bakcPoolState, vars, totalBorrow); + + //stake in ApeCoinStaking + ApeCoinStaking.PairNftDepositWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 0 + ); + if (nft == vars.bayc) { + vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); + } + } + + function compoundApe( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) external { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, "wrong param"); + + uint256[] memory _nfts = new uint256[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + require( + poolState.tokenStatus[tokenId].isInPool, + "ape not in single pool" + ); + + // construct staking data + _nfts[index] = tokenId; + + //emit event + emit NFTCompounded(nft, tokenId); + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + //claim from ApeCoinStaking + if (nft == vars.bayc) { + vars.apeCoinStaking.claimSelfBAYC(_nfts); + vars.positionCap = vars.baycMatchedCap; + } else { + vars.apeCoinStaking.claimSelfMAYC(_nfts); + vars.positionCap = vars.maycMatchedCap; + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + + //repay and compound + uint256 totalRepay = _reayAndCompound( + poolState, + vars, + balanceDiff, + vars.positionCap + ); + + if (totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); + IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + } + } + + function compoundBAKC( + IParaApeStaking.VaultStorage storage vaultStorage, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + IParaApeStaking.PoolState storage apePoolState; + if (nft == vars.bayc) { + apePoolState = vaultStorage.poolStates[ + ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID + ]; + vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; + } else { + apePoolState = vaultStorage.poolStates[ + ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID + ]; + vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; + } + IParaApeStaking.PoolState storage bakcPoolState = vaultStorage + .poolStates[BAKC_SINGLE_POOL_ID]; + + uint256 totalReward; + { + ApeCoinStaking.PairNft[] + memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + require( + apePoolState.tokenStatus[apeTokenId].isInPool, + "ape not in single pool" + ); + require( + bakcPoolState.tokenStatus[bakcTokenId].isInPool, + "ape not in single pool" + ); + + // construct staking data + _nftPairs[index] = ApeCoinStaking.PairNft({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId + }); + + //emit event + emit NFTPairStaked(nft, apeTokenId, bakcTokenId); + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + //claim from ApeCoinStaking + { + ApeCoinStaking.PairNft[] + memory _otherPairs = new ApeCoinStaking.PairNft[](0); + if (nft == vars.bayc) { + vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); + } + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + totalReward = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit(address(this), totalReward); + } + + //repay and compound + uint256 totalRepay = _reayAndCompoundBAKC( + apePoolState, + bakcPoolState, + vars, + totalReward + ); + + if (totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); + IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + } + } + + function claimNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) external { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, "wrong param"); + + _claimNFT(poolState, vars, nft, tokenIds); + } + + function withdrawNFT( + IParaApeStaking.VaultStorage storage vaultStorage, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) external { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, "wrong param"); + + IParaApeStaking.PoolState storage curPoolState; + address nToken; + if (nft == vars.bayc) { + curPoolState = vaultStorage.poolStates[BAYC_SINGLE_POOL_ID]; + nToken = vars.nBayc; + } else if (nft == vars.mayc) { + curPoolState = vaultStorage.poolStates[MAYC_SINGLE_POOL_ID]; + nToken = vars.nMayc; + } else { + curPoolState = vaultStorage.poolStates[BAKC_SINGLE_POOL_ID]; + nToken = vars.nBakc; + } + + //claim pending reward + _claimNFT(curPoolState, vars, nft, tokenIds); + + //update state + curPoolState.totalPosition -= arrayLength.toUint128(); + + if (nft == vars.bayc || nft == vars.mayc) { + _unstakeApe(vaultStorage, vars, nft, tokenIds); + } else { + _unstakeBAKC(vaultStorage, vars, tokenIds); + } + + //transfer nft back to nToken + address msgSender = msg.sender; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + address nTokenOwner = IERC721(nToken).ownerOf(tokenId); + require(msgSender == nTokenOwner, "not owner"); + + delete curPoolState.tokenStatus[tokenId]; + + IERC721(nft).safeTransferFrom(address(this), nToken, tokenId); + + //emit event + emit NFTWithdrawn(nft, tokenId); + } + } + + function _unstakeApe( + IParaApeStaking.VaultStorage storage vaultStorage, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) internal { + uint256 arrayLength = tokenIds.length; + + IParaApeStaking.PoolState storage apePoolState; + IParaApeStaking.PoolState storage bakcPoolState = vaultStorage + .poolStates[BAKC_SINGLE_POOL_ID]; + if (nft == vars.bayc) { + vars.apeStakingPoolId = BAYC_POOL_ID; + vars.positionCap = vars.baycMatchedCap; + apePoolState = vaultStorage.poolStates[BAYC_SINGLE_POOL_ID]; + vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; + } else { + vars.apeStakingPoolId = MAYC_POOL_ID; + vars.positionCap = vars.maycMatchedCap; + apePoolState = vaultStorage.poolStates[MAYC_SINGLE_POOL_ID]; + vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; + } + vars._nfts = new ApeCoinStaking.SingleNft[](arrayLength); + vars._nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); + uint256 singleStakingCount; + uint256 pairStakingCount; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + //check ape position + { + (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( + vars.apeStakingPoolId, + tokenId + ); + if (stakedAmount > 0) { + vars._nfts[singleStakingCount] = ApeCoinStaking.SingleNft({ + tokenId: tokenId, + amount: vars.positionCap.toUint224() + }); + singleStakingCount++; + } + } + + //check bakc position + { + (uint256 bakcTokenId, bool isPaired) = vars + .apeCoinStaking + .mainToBakc(vars.apeStakingPoolId, tokenId); + if (isPaired) { + vars._nftPairs[pairStakingCount] = ApeCoinStaking + .PairNftWithdrawWithAmount({ + mainTokenId: tokenId, + bakcTokenId: bakcTokenId.toUint32(), + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); + pairStakingCount++; + } + } + } + + uint256 totalRepay = 0; + if (singleStakingCount > 0) { + ApeCoinStaking.SingleNft[] memory _nfts = vars._nfts; + assembly { + mstore(_nfts, singleStakingCount) + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + if (nft == vars.bayc) { + vars.apeCoinStaking.withdrawBAYC(vars._nfts, address(this)); + } else { + vars.apeCoinStaking.withdrawMAYC(vars._nfts, address(this)); + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + + totalRepay += _reayAndCompound( + apePoolState, + vars, + balanceDiff, + vars.positionCap + ); + } + + if (pairStakingCount > 0) { + ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs = vars + ._nftPairs; + assembly { + mstore(_nftPairs, pairStakingCount) + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); + if (nft == vars.bayc) { + vars.apeCoinStaking.withdrawBAKC(vars._nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.withdrawBAKC(_otherPairs, vars._nftPairs); + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + + totalRepay += _reayAndCompoundBAKC( + apePoolState, + bakcPoolState, + vars, + balanceDiff + ); + } + + if (totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); + IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + } + } + + function _unstakeBAKC( + IParaApeStaking.VaultStorage storage vaultStorage, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint32[] calldata tokenIds + ) internal { + uint256 arrayLength = tokenIds.length; + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory baycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory maycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); + uint256 baycPairCount; + uint256 maycPairCount; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + (uint256 mainTokenId, bool isPaired) = vars + .apeCoinStaking + .bakcToMain(tokenId, BAYC_POOL_ID); + if (isPaired) { + baycPair[baycPairCount] = ApeCoinStaking + .PairNftWithdrawWithAmount({ + mainTokenId: mainTokenId.toUint32(), + bakcTokenId: tokenId, + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); + baycPairCount++; + continue; + } + + (mainTokenId, isPaired) = vars.apeCoinStaking.bakcToMain( + tokenId, + MAYC_POOL_ID ); - //if it's not staking in ApeCoinStaking, we skip calculating reward - if (!isPaired) { + if (isPaired) { + maycPair[maycPairCount] = ApeCoinStaking + .PairNftWithdrawWithAmount({ + mainTokenId: mainTokenId.toUint32(), + bakcTokenId: tokenId, + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); + maycPairCount++; continue; } + } + + assembly { + mstore(baycPair, baycPairCount) + } + assembly { + mstore(maycPair, maycPairCount) + } + + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); + + uint256 totalRepay = 0; + if (baycPairCount > 0) { + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + vars.apeCoinStaking.withdrawBAKC(baycPair, _otherPairs); + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + + vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; + totalRepay += _reayAndCompoundBAKC( + vaultStorage.poolStates[BAYC_SINGLE_POOL_ID], + vaultStorage.poolStates[BAKC_SINGLE_POOL_ID], + vars, + balanceDiff + ); + } + if (maycPairCount > 0) { + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + vars.apeCoinStaking.withdrawBAKC(baycPair, maycPair); + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + + vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; + totalRepay += _reayAndCompoundBAKC( + vaultStorage.poolStates[MAYC_SINGLE_POOL_ID], + vaultStorage.poolStates[BAKC_SINGLE_POOL_ID], + vars, + balanceDiff + ); + } + + if (totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); + IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + } + } + + function _claimNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) internal { + vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + uint256 rewardShares; + address claimFor; + uint256 arrayLength = tokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + //just need to check ape ntoken owner + { + address nToken = (nft == vars.bayc) + ? vars.nBayc + : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; + address nTokenOwner = IERC721(nToken).ownerOf(tokenId); + if (claimFor == address(0)) { + claimFor = nTokenOwner; + } else { + require( + nTokenOwner == claimFor, + "claim not for same owner" + ); + } + } //update reward, to save gas we don't claim pending reward in ApeCoinStaking. rewardShares += (vars.accumulatedRewardsPerNft - - poolState.rewardsDebt[apeTokenId]); - poolState.rewardsDebt[apeTokenId] = vars.accumulatedRewardsPerNft; + poolState.tokenStatus[tokenId].rewardsDebt); + poolState.tokenStatus[tokenId].rewardsDebt = vars + .accumulatedRewardsPerNft; //emit event - emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); + emit NFTClaimed(nft, tokenId); } if (rewardShares > 0) { @@ -480,46 +1128,99 @@ library ApeStakingVaultLogic { IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint256 totalAmount, uint256 positionCap - ) internal { - console.log("_reayAndCompound---------------------------0"); + ) internal returns (uint256) { uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( WadRayMath.RAY ); uint256 latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); uint256 cApeDebtShare = poolState.cApeDebtShare; + uint128 currentTotalPosition = poolState.totalPosition; uint256 debtInterest = _calculateCurrentPositionDebtInterest( cApeDebtShare, - poolState.totalPosition, + currentTotalPosition, positionCap, cApeExchangeRate, latestBorrowIndex ); - console.log("_reayAndCompound---------------------------totalAmount:", totalAmount); - console.log("_reayAndCompound---------------------------debtInterest:", debtInterest); if (debtInterest >= totalAmount) { - console.log("_reayAndCompound---------------------------1"); - IERC20(vars.cApe).safeApprove(vars.pool, totalAmount); - IPool(vars.pool).repay(vars.cApe, totalAmount, address(this)); cApeDebtShare -= totalAmount.rayDiv(latestBorrowIndex).rayDiv( cApeExchangeRate ); + poolState.cApeDebtShare = cApeDebtShare; + return totalAmount; } else { + //repay debt + cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + //update reward index - console.log("_reayAndCompound---------------------------2"); - IERC20(vars.cApe).safeApprove(vars.pool, debtInterest); - IPool(vars.pool).repay(vars.cApe, debtInterest, address(this)); - uint256 remainingReward = totalAmount - debtInterest; - uint256 shareAmount = remainingReward.rayDiv(cApeExchangeRate); - poolState.accumulatedRewardsPerNft += - shareAmount / - poolState.totalPosition; + if (currentTotalPosition != 0) { + uint256 remainingReward = totalAmount - debtInterest; + uint256 shareAmount = remainingReward.rayDiv(cApeExchangeRate); + poolState.accumulatedRewardsPerNft += + shareAmount.toUint128() / + currentTotalPosition; + } + poolState.cApeDebtShare = cApeDebtShare; + return debtInterest; + } + } + + function _reayAndCompoundBAKC( + IParaApeStaking.PoolState storage apePoolState, + IParaApeStaking.PoolState storage bakcPoolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 totalAmount + ) internal returns (uint256) { + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + uint256 latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); + uint256 cApeDebtShare = bakcPoolState.cApeDebtShare; + uint128 apeTotalPosition = apePoolState.totalPosition; + uint128 bakcTotalPosition = bakcPoolState.totalPosition; + uint256 debtInterest = _calculateCurrentPositionDebtInterest( + cApeDebtShare, + bakcTotalPosition, + vars.bakcMatchedCap, + cApeExchangeRate, + latestBorrowIndex + ); + if (debtInterest >= totalAmount) { + cApeDebtShare -= totalAmount.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + bakcPoolState.cApeDebtShare = cApeDebtShare; + return totalAmount; + } else { + //repay debt cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( cApeExchangeRate ); + + //update reward index + uint256 remainingReward = totalAmount - debtInterest; + uint256 shareAmount = remainingReward.rayDiv(cApeExchangeRate); + uint256 apeShareAmount = shareAmount.percentMul( + vars.apeRewardRatio + ); + + if (apeTotalPosition != 0) { + apePoolState.accumulatedRewardsPerNft += + apeShareAmount.toUint128() / + apeTotalPosition; + } + if (bakcTotalPosition != 0) { + bakcPoolState.accumulatedRewardsPerNft += + (shareAmount - apeShareAmount).toUint128() / + bakcTotalPosition; + } + bakcPoolState.cApeDebtShare = cApeDebtShare; + return debtInterest; } - console.log("_reayAndCompound---------------------------3"); - poolState.cApeDebtShare = cApeDebtShare; } function _calculateCurrentPositionDebtInterest( @@ -534,4 +1235,21 @@ library ApeStakingVaultLogic { ); return (currentDebt - perPositionCap * totalPosition); } + + function _borrowCApeFromPool( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 totalBorrow + ) internal { + uint256 latestBorrowIndex = IPool(vars.pool).borrowPoolCApe( + totalBorrow + ); + IAutoCompoundApe(vars.cApe).withdraw(totalBorrow); + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + poolState.cApeDebtShare += totalBorrow.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + } } diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index 34ae812f1..5e4fd3bfb 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -9,13 +9,29 @@ interface IApeStakingVault { uint248 tokenId; bool isPaired; } + struct TokenStatus { + //record tokenId reward debt position + uint128 rewardsDebt; + // identify if tokenId is in pool + bool isInPool; + } struct PoolState { - uint256 accumulatedRewardsPerNft; - uint256 totalPosition; + // accumulated cApe reward for per NFT position + uint128 accumulatedRewardsPerNft; + // total NFT position count + uint128 totalPosition; //tokenId => reward debt position - mapping(uint256 => uint256) rewardsDebt; - //apeTokenId => PairingStatus + mapping(uint256 => TokenStatus) tokenStatus; + //for pair pool, apeTokenId => PairingStatus mapping(uint256 => PairingStatus) pairStatus; + //pool cape debt token share uint256 cApeDebtShare; } + + struct VaultStorage { + mapping(uint256 => PoolState) poolStates; + address vaultBot; + uint256 baycPairStakingRewardRatio; + uint256 maycPairStakingRewardRatio; + } } diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index 240764617..49d41496b 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -28,11 +28,12 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { address nApe; uint256 apeStakingPoolId; uint256 positionCap; - uint256 accumulatedRewardsPerNft; + uint128 accumulatedRewardsPerNft; uint256 balanceBefore; uint256 balanceAfter; ApeCoinStaking.SingleNft[] _nfts; ApeCoinStaking.PairNftWithdrawWithAmount[] _nftPairs; - uint256 stakingPair; + uint128 stakingPair; + uint256 apeRewardRatio; } } diff --git a/contracts/protocol/libraries/logic/BorrowLogic.sol b/contracts/protocol/libraries/logic/BorrowLogic.sol index a528c633f..cad627ac1 100644 --- a/contracts/protocol/libraries/logic/BorrowLogic.sol +++ b/contracts/protocol/libraries/logic/BorrowLogic.sol @@ -14,7 +14,6 @@ import {DataTypes} from "../types/DataTypes.sol"; import {ValidationLogic} from "./ValidationLogic.sol"; import {ReserveLogic} from "./ReserveLogic.sol"; import {GenericLogic} from "./GenericLogic.sol"; -import "hardhat/console.sol"; /** * @title BorrowLogic library @@ -144,7 +143,6 @@ library BorrowLogic { address asset, uint256 amount ) external returns (uint256) { - console.log("---------------------2"); DataTypes.ReserveData storage reserve = reservesData[asset]; DataTypes.ReserveCache memory reserveCache = reserve.cache(); @@ -152,9 +150,6 @@ library BorrowLogic { ValidationLogic.validateBorrowWithoutCollateral(reserveCache, amount); - console.log("variableDebtTokenAddress:", reserveCache.variableDebtTokenAddress); - console.log("borrowFor:", borrowFor); - console.log("borrow asset:", asset); (, reserveCache.nextScaledVariableDebt) = IVariableDebtToken( reserveCache.variableDebtTokenAddress ).mint( diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 2d331077b..b5d9a3bd3 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -27,7 +27,6 @@ import {Math} from "../../dependencies/openzeppelin/contracts/Math.sol"; import {ISwapRouter} from "../../dependencies/univ3/interfaces/ISwapRouter.sol"; import {IPriceOracleGetter} from "../../interfaces/IPriceOracleGetter.sol"; import {Helpers} from "../libraries/helpers/Helpers.sol"; -import "hardhat/console.sol"; contract PoolApeStaking is ParaVersionedInitializable, @@ -123,7 +122,6 @@ contract PoolApeStaking is require(msg.sender == PARA_APE_STAKING); DataTypes.PoolStorage storage ps = poolStorage(); - console.log("---------------------1"); uint256 latestBorrowIndex = BorrowLogic.executeBorrowWithoutCollateral( ps._reserves, PARA_APE_STAKING, diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 6a1f1fb45..3815ecd4b 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -43,6 +43,8 @@ describe("Para Ape Staking Test", () => { apeCoinStaking, pool, protocolDataProvider, + configurator, + poolAdmin, } = testEnv; //upgrade to non-fake implementation @@ -65,8 +67,6 @@ describe("Para Ape Staking Test", () => { variableDebtCApeCoin = await getVariableDebtToken( variableDebtCApeCoinAddress ); - console.log("paraApeStaking address:", paraApeStaking.address); - console.log("variableDebtCApeCoinAddress:", variableDebtCApeCoinAddress); // send extra tokens to the apestaking contract for rewards await waitForTx( @@ -87,15 +87,20 @@ describe("Para Ape Staking Test", () => { await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) ); - // user3 deposit and supply cApe to MM - await mintAndValidate(ape, "10000000", user4); + // user4 deposit and supply cApe to MM + expect( + await configurator + .connect(poolAdmin.signer) + .setSupplyCap(cApe.address, "200000000") + ); + await mintAndValidate(ape, "100000000", user4); await waitForTx( await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) ); await waitForTx( await cApe .connect(user4.signer) - .deposit(user4.address, parseEther("10000000")) + .deposit(user4.address, parseEther("100000000")) ); await waitForTx( await cApe.connect(user4.signer).approve(pool.address, MAX_UINT_AMOUNT) @@ -103,7 +108,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await pool .connect(user4.signer) - .supply(cApe.address, parseEther("10000000"), user4.address, 0) + .supply(cApe.address, parseEther("100000000"), user4.address, 0) ); return testEnv; @@ -141,66 +146,74 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking.connect(user2.signer).depositPairNFT(true, [2], [2]) ); - expect (await bayc.ownerOf(0)).to.be.equal(paraApeStaking.address); - expect (await bayc.ownerOf(1)).to.be.equal(paraApeStaking.address); - expect (await bayc.ownerOf(2)).to.be.equal(paraApeStaking.address); - expect (await bakc.ownerOf(0)).to.be.equal(paraApeStaking.address); - expect (await bakc.ownerOf(1)).to.be.equal(paraApeStaking.address); - expect (await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); + expect(await bayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await bayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await bayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); await waitForTx( await paraApeStaking .connect(user3.signer) .stakingPairNFT(true, [0, 1, 2], [0, 1, 2]) ); - expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq(parseEther("200000")); - expect((await apeCoinStaking.nftPosition(1, 1)).stakedAmount).to.be.eq(parseEther("200000")); - expect((await apeCoinStaking.nftPosition(1, 2)).stakedAmount).to.be.eq(parseEther("200000")); - expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq(parseEther("50000")); - expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq(parseEther("50000")); - expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq(parseEther("50000")); + expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(1, 1)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(1, 2)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq( + parseEther("50000") + ); expect( - await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) ).to.be.closeTo(parseEther("750000"), parseEther("10")); await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking - .connect(user3.signer) - .compoundPairNFT(true, [0, 1, 2], [0, 1, 2]) + await paraApeStaking + .connect(user3.signer) + .compoundPairNFT(true, [0, 1, 2], [0, 1, 2]) ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .claimPairNFT(true, [0, 1], [0, 1]) + await paraApeStaking + .connect(user1.signer) + .claimPairNFT(true, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking - .connect(user2.signer) - .claimPairNFT(true, [2], [2]) + await paraApeStaking.connect(user2.signer).claimPairNFT(true, [2], [2]) ); const user1Balance = await cApe.balanceOf(user1.address); const user2Balance = await cApe.balanceOf(user2.address); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .withdrawPairNFT(true, [0, 1], [0, 1]) + await paraApeStaking + .connect(user1.signer) + .withdrawPairNFT(true, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking - .connect(user2.signer) - .withdrawPairNFT(true, [2], [2]) + await paraApeStaking.connect(user2.signer).withdrawPairNFT(true, [2], [2]) ); - expect (await bayc.ownerOf(0)).to.be.equal(nBAYC.address); - expect (await bayc.ownerOf(1)).to.be.equal(nBAYC.address); - expect (await bayc.ownerOf(2)).to.be.equal(nBAYC.address); - expect (await bakc.ownerOf(0)).to.be.equal(nBAKC.address); - expect (await bakc.ownerOf(1)).to.be.equal(nBAKC.address); - expect (await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + expect(await bayc.ownerOf(0)).to.be.equal(nBAYC.address); + expect(await bayc.ownerOf(1)).to.be.equal(nBAYC.address); + expect(await bayc.ownerOf(2)).to.be.equal(nBAYC.address); + expect(await bakc.ownerOf(0)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); }); it("test MAYC + BAKC pool logic", async () => { @@ -235,65 +248,250 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking.connect(user2.signer).depositPairNFT(false, [2], [2]) ); - expect (await mayc.ownerOf(0)).to.be.equal(paraApeStaking.address); - expect (await mayc.ownerOf(1)).to.be.equal(paraApeStaking.address); - expect (await mayc.ownerOf(2)).to.be.equal(paraApeStaking.address); - expect (await bakc.ownerOf(0)).to.be.equal(paraApeStaking.address); - expect (await bakc.ownerOf(1)).to.be.equal(paraApeStaking.address); - expect (await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); await waitForTx( await paraApeStaking .connect(user3.signer) .stakingPairNFT(false, [0, 1, 2], [0, 1, 2]) ); - expect((await apeCoinStaking.nftPosition(2, 0)).stakedAmount).to.be.eq(parseEther("100000")); - expect((await apeCoinStaking.nftPosition(2, 1)).stakedAmount).to.be.eq(parseEther("100000")); - expect((await apeCoinStaking.nftPosition(2, 2)).stakedAmount).to.be.eq(parseEther("100000")); - expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq(parseEther("50000")); - expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq(parseEther("50000")); - expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq(parseEther("50000")); + expect((await apeCoinStaking.nftPosition(2, 0)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(2, 1)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(2, 2)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq( + parseEther("50000") + ); expect( - await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) ).to.be.closeTo(parseEther("450000"), parseEther("10")); await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking - .connect(user3.signer) - .compoundPairNFT(false, [0, 1, 2], [0, 1, 2]) + await paraApeStaking + .connect(user3.signer) + .compoundPairNFT(false, [0, 1, 2], [0, 1, 2]) ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .claimPairNFT(false, [0, 1], [0, 1]) + await paraApeStaking + .connect(user1.signer) + .claimPairNFT(false, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking - .connect(user2.signer) - .claimPairNFT(false, [2], [2]) + await paraApeStaking.connect(user2.signer).claimPairNFT(false, [2], [2]) ); const user1Balance = await cApe.balanceOf(user1.address); const user2Balance = await cApe.balanceOf(user2.address); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .withdrawPairNFT(false, [0, 1], [0, 1]) + await paraApeStaking + .connect(user1.signer) + .withdrawPairNFT(false, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking - .connect(user2.signer) - .withdrawPairNFT(false, [2], [2]) + await paraApeStaking + .connect(user2.signer) + .withdrawPairNFT(false, [2], [2]) ); - expect (await mayc.ownerOf(0)).to.be.equal(nMAYC.address); - expect (await mayc.ownerOf(1)).to.be.equal(nMAYC.address); - expect (await mayc.ownerOf(2)).to.be.equal(nMAYC.address); - expect (await bakc.ownerOf(0)).to.be.equal(nBAKC.address); - expect (await bakc.ownerOf(1)).to.be.equal(nBAKC.address); - expect (await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + expect(await mayc.ownerOf(0)).to.be.equal(nMAYC.address); + expect(await mayc.ownerOf(1)).to.be.equal(nMAYC.address); + expect(await mayc.ownerOf(2)).to.be.equal(nMAYC.address); + expect(await bakc.ownerOf(0)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); }); + + it("test single pool logic", async () => { + const { + users: [user1, user2, user3, user4], + bayc, + mayc, + bakc, + nBAYC, + nMAYC, + nBAKC, + apeCoinStaking, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(mayc, "3", user2, true); + await supplyAndValidate(bakc, "3", user3, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositNFT(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .depositNFT(bakc.address, [0, 1, 2]) + ); + expect(await bayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await bayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await bayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingApe(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingApe(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingBAKC(bayc.address, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingBAKC(mayc.address, [2], [2]) + ); + expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(1, 1)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(1, 2)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(2, 0)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(2, 1)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(2, 2)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect( + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + ).to.be.closeTo(parseEther("1050000"), parseEther("10")); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundApe(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundApe(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundBAKC(bayc.address, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundBAKC(mayc.address, [2], [2]) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimNFT(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .claimNFT(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .claimNFT(bakc.address, [0, 1, 2]) + ); + const user1Balance = await cApe.balanceOf(user1.address); + const user2Balance = await cApe.balanceOf(user2.address); + const user3Balance = await cApe.balanceOf(user3.address); + //base on both baycPairStakingRewardRatio and maycPairStakingRewardRatio are 0 + expect(user1Balance).to.be.closeTo(user2Balance, parseEther("100")); + expect(user1Balance).to.be.closeTo(user3Balance, parseEther("100")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .withdrawNFT(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .withdrawNFT(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .withdrawNFT(bakc.address, [0, 1, 2]) + ); + expect(await bayc.ownerOf(0)).to.be.equal(nBAYC.address); + expect(await bayc.ownerOf(1)).to.be.equal(nBAYC.address); + expect(await bayc.ownerOf(2)).to.be.equal(nBAYC.address); + expect(await mayc.ownerOf(0)).to.be.equal(nMAYC.address); + expect(await mayc.ownerOf(1)).to.be.equal(nMAYC.address); + expect(await mayc.ownerOf(2)).to.be.equal(nMAYC.address); + expect(await bakc.ownerOf(0)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + }); + + it("stakingPairNFT cannot stake single pool nft", async () => {}); + + it("stakingApe cannot stake pair pool ape", async () => {}); + + it("stakingBAKC cannot stake pair pool nft", async () => {}); + + it("compoundPairNFT cannot stake single pool nft", async () => {}); + + it("compoundNFT cannot compound pair pool nft", async () => {}); + + it("compoundBAKC cannot compound pair pool nft", async () => {}); }); From 74989457851687eac05b463a11522a9aae182171 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 7 Jul 2023 10:59:40 +0800 Subject: [PATCH 43/99] chore: spit logic contact to reduce contract size --- contracts/apestaking/ParaApeStaking.sol | 156 ++-- .../logic/ApeStakingCommonLogic.sol | 78 ++ .../logic/ApeStakingPairPoolLogic.sol | 480 ++++++++++++ ...ogic.sol => ApeStakingSinglePoolLogic.sol} | 686 +++--------------- helpers/contracts-deployments.ts | 28 +- helpers/types.ts | 3 +- 6 files changed, 776 insertions(+), 655 deletions(-) create mode 100644 contracts/apestaking/logic/ApeStakingPairPoolLogic.sol rename contracts/apestaking/logic/{ApeStakingVaultLogic.sol => ApeStakingSinglePoolLogic.sol} (51%) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index dac3221c9..1e620d08d 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -13,8 +13,10 @@ import "../interfaces/ICApe.sol"; import {PercentageMath} from "../protocol/libraries/math/PercentageMath.sol"; import {WadRayMath} from "../protocol/libraries/math/WadRayMath.sol"; import "./logic/ApeStakingP2PLogic.sol"; -import "./logic/ApeStakingVaultLogic.sol"; +import "./logic/ApeStakingPairPoolLogic.sol"; +import "./logic/ApeStakingSinglePoolLogic.sol"; import "./logic/ApeStakingCommonLogic.sol"; +import "../protocol/libraries/helpers/Errors.sol"; contract ParaApeStaking is Initializable, @@ -116,6 +118,64 @@ contract ParaApeStaking is IERC20(apeCoin).safeApprove(cApe, type(uint256).max); } + /** + * @dev Only pool admin can call functions marked by this modifier. + **/ + modifier onlyPoolAdmin() { + _onlyPoolAdmin(); + _; + } + + /** + * @dev Only emergency or pool admin can call functions marked by this modifier. + **/ + modifier onlyEmergencyOrPoolAdmin() { + _onlyPoolOrEmergencyAdmin(); + _; + } + + function _onlyPoolAdmin() internal view { + require( + aclManager.isPoolAdmin(msg.sender), + Errors.CALLER_NOT_POOL_ADMIN + ); + } + + function _onlyPoolOrEmergencyAdmin() internal view { + require( + aclManager.isPoolAdmin(msg.sender) || + aclManager.isEmergencyAdmin(msg.sender), + Errors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN + ); + } + + /** + * @notice Pauses the contract. Only pool admin or emergency admin can call this function + **/ + function pause() external onlyEmergencyOrPoolAdmin { + _pause(); + } + + /** + * @notice Unpause the contract. Only pool admin can call this function + **/ + function unpause() external onlyPoolAdmin { + _unpause(); + } + + function rescueERC20( + address token, + address to, + uint256 amount + ) external onlyPoolAdmin { + IERC20(token).safeTransfer(to, amount); + emit RescueERC20(token, to, amount); + } + + /* + + */ + function cancelListing(ListingOrder calldata listingOrder) external nonReentrant @@ -245,12 +305,12 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external { + ) external whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; - ApeStakingVaultLogic.depositPairNFT( + ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ApeStakingPairPoolLogic.depositPairNFT( vaultStorage.poolStates[poolId], vars, isBAYC, @@ -263,12 +323,12 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external { + ) external whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; - ApeStakingVaultLogic.stakingPairNFT( + ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ApeStakingPairPoolLogic.stakingPairNFT( vaultStorage.poolStates[poolId], vars, isBAYC, @@ -281,12 +341,12 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external { + ) external whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; - ApeStakingVaultLogic.withdrawPairNFT( + ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ApeStakingPairPoolLogic.withdrawPairNFT( vaultStorage.poolStates[poolId], vars, isBAYC, @@ -300,12 +360,12 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external { + ) external whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; - ApeStakingVaultLogic.claimPairNFT( + ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ApeStakingPairPoolLogic.claimPairNFT( vaultStorage.poolStates[poolId], vars, isBAYC, @@ -318,12 +378,12 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external { + ) external whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingVaultLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingVaultLogic.MAYC_BAKC_PAIR_POOL_ID; - ApeStakingVaultLogic.compoundPairNFT( + ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ApeStakingPairPoolLogic.compoundPairNFT( vaultStorage.poolStates[poolId], vars, isBAYC, @@ -332,15 +392,15 @@ contract ParaApeStaking is ); } - function depositNFT(address nft, uint32[] calldata tokenIds) external { + function depositNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused { require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) - ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID + ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID : (nft == mayc) - ? ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID - : ApeStakingVaultLogic.BAKC_SINGLE_POOL_ID; - ApeStakingVaultLogic.depositNFT( + ? ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID + : ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID; + ApeStakingSinglePoolLogic.depositNFT( vaultStorage.poolStates[poolId], vars, nft, @@ -348,13 +408,13 @@ contract ParaApeStaking is ); } - function stakingApe(address nft, uint32[] calldata tokenIds) external { + function stakingApe(address nft, uint32[] calldata tokenIds) external whenNotPaused { require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) - ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID - : ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID; - ApeStakingVaultLogic.stakingApe( + ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID + : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; + ApeStakingSinglePoolLogic.stakingApe( vaultStorage.poolStates[poolId], vars, nft, @@ -366,14 +426,14 @@ contract ParaApeStaking is address nft, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external { + ) external whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) - ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID - : ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID; - ApeStakingVaultLogic.stakingBAKC( + ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID + : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; + ApeStakingSinglePoolLogic.stakingBAKC( vaultStorage.poolStates[poolId], - vaultStorage.poolStates[ApeStakingVaultLogic.BAKC_SINGLE_POOL_ID], + vaultStorage.poolStates[ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID], vars, nft, apeTokenIds, @@ -381,13 +441,13 @@ contract ParaApeStaking is ); } - function compoundApe(address nft, uint32[] calldata tokenIds) external { + function compoundApe(address nft, uint32[] calldata tokenIds) external whenNotPaused { require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) - ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID - : ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID; - ApeStakingVaultLogic.compoundApe( + ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID + : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; + ApeStakingSinglePoolLogic.compoundApe( vaultStorage.poolStates[poolId], vars, nft, @@ -399,10 +459,10 @@ contract ParaApeStaking is address nft, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external { + ) external whenNotPaused { require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - ApeStakingVaultLogic.compoundBAKC( + ApeStakingSinglePoolLogic.compoundBAKC( vaultStorage, vars, nft, @@ -411,15 +471,15 @@ contract ParaApeStaking is ); } - function claimNFT(address nft, uint32[] calldata tokenIds) external { + function claimNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused { require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) - ? ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID + ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID : (nft == mayc) - ? ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID - : ApeStakingVaultLogic.BAKC_SINGLE_POOL_ID; - ApeStakingVaultLogic.claimNFT( + ? ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID + : ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID; + ApeStakingSinglePoolLogic.claimNFT( vaultStorage.poolStates[poolId], vars, nft, @@ -427,10 +487,10 @@ contract ParaApeStaking is ); } - function withdrawNFT(address nft, uint32[] calldata tokenIds) external { + function withdrawNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused { require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - ApeStakingVaultLogic.withdrawNFT(vaultStorage, vars, nft, tokenIds); + ApeStakingSinglePoolLogic.withdrawNFT(vaultStorage, vars, nft, tokenIds); } function _createCacheVars() diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 524f1a2ca..bbd1ddf90 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -9,6 +9,8 @@ import "../../interfaces/IAutoCompoundApe.sol"; import "../../interfaces/ICApe.sol"; import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; +import {IPool} from "../../interfaces/IPool.sol"; /** * @title ApeStakingVaultLogic library @@ -19,6 +21,7 @@ library ApeStakingCommonLogic { using PercentageMath for uint256; using SafeCast for uint256; using SafeERC20 for IERC20; + using WadRayMath for uint256; function depositCApeShareForUser( mapping(address => uint256) storage cApeShareBalance, @@ -30,6 +33,81 @@ library ApeStakingCommonLogic { } } + function calculateRepayAndCompound( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 totalAmount, + uint256 positionCap + ) internal returns (uint256) { + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + uint256 latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); + uint256 cApeDebtShare = poolState.cApeDebtShare; + uint128 currentTotalPosition = poolState.totalPosition; + uint256 debtInterest = calculateCurrentPositionDebtInterest( + cApeDebtShare, + currentTotalPosition, + positionCap, + cApeExchangeRate, + latestBorrowIndex + ); + if (debtInterest >= totalAmount) { + cApeDebtShare -= totalAmount.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + poolState.cApeDebtShare = cApeDebtShare; + return totalAmount; + } else { + //repay debt + cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + + //update reward index + if (currentTotalPosition != 0) { + uint256 remainingReward = totalAmount - debtInterest; + uint256 shareAmount = remainingReward.rayDiv(cApeExchangeRate); + poolState.accumulatedRewardsPerNft += + shareAmount.toUint128() / + currentTotalPosition; + } + poolState.cApeDebtShare = cApeDebtShare; + return debtInterest; + } + } + + function borrowCApeFromPool( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 totalBorrow + ) internal { + uint256 latestBorrowIndex = IPool(vars.pool).borrowPoolCApe( + totalBorrow + ); + IAutoCompoundApe(vars.cApe).withdraw(totalBorrow); + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + poolState.cApeDebtShare += totalBorrow.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + } + + function calculateCurrentPositionDebtInterest( + uint256 cApeDebtShare, + uint256 totalPosition, + uint256 perPositionCap, + uint256 cApeExchangeRate, + uint256 latestBorrowIndex + ) internal pure returns (uint256) { + uint256 currentDebt = cApeDebtShare.rayMul(cApeExchangeRate).rayMul( + latestBorrowIndex + ); + return (currentDebt - perPositionCap * totalPosition); + } + /* function stake( StakingType stakingType, diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol new file mode 100644 index 000000000..2e2e90b97 --- /dev/null +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.10; + +import {IPool} from "../../interfaces/IPool.sol"; +import "../../interfaces/IParaApeStaking.sol"; +import {IERC20, SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; +import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; +import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; +import "../../interfaces/IAutoCompoundApe.sol"; +import "../../interfaces/ICApe.sol"; +import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; +import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; +import "./ApeStakingCommonLogic.sol"; + +/** + * @title ApeStakingPairPoolLogic library + * + * @notice Implements the base logic for ape staking vault + */ +library ApeStakingPairPoolLogic { + using PercentageMath for uint256; + using SafeCast for uint256; + using SafeERC20 for IERC20; + using WadRayMath for uint256; + + uint256 public constant BAYC_BAKC_PAIR_POOL_ID = 1; + uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; + uint256 public constant BAYC_SINGLE_POOL_ID = 3; + uint256 public constant MAYC_SINGLE_POOL_ID = 4; + uint256 public constant BAKC_SINGLE_POOL_ID = 5; + + uint256 constant BAYC_POOL_ID = 1; + uint256 constant MAYC_POOL_ID = 2; + uint256 constant BAKC_POOL_ID = 3; + + event PairNFTDeposited( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + event PairNFTStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event PairNFTWithdrew(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event PairNFTClaimed(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event PairNFTCompounded( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + + function depositPairNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; + vars.apeToken = isBAYC ? vars.bayc : vars.mayc; + vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; + address msgSender = msg.sender; + vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + //check ntoken owner + { + address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); + address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); + require( + msgSender == nApeOwner && msgSender == nBakcOwner, + "not owner" + ); + } + + // check both ape and bakc are not staking + { + (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( + vars.apeStakingPoolId, + apeTokenId + ); + require(stakedAmount == 0, "ape already staked"); + (stakedAmount, ) = vars.apeCoinStaking.nftPosition( + BAKC_POOL_ID, + bakcTokenId + ); + require(stakedAmount == 0, "bakc already staked"); + (, bool isPaired) = vars.apeCoinStaking.mainToBakc( + vars.apeStakingPoolId, + apeTokenId + ); + require(!isPaired, "ape already pair staked"); + } + + //update pair status + poolState.pairStatus[apeTokenId] = IApeStakingVault.PairingStatus({ + tokenId: bakcTokenId, + isPaired: true + }); + + //update token status + poolState.tokenStatus[apeTokenId] = IApeStakingVault.TokenStatus({ + rewardsDebt: vars.accumulatedRewardsPerNft, + isInPool: true + }); + + //transfer ape and BAKC + IERC721(vars.apeToken).safeTransferFrom( + vars.nApe, + address(this), + apeTokenId + ); + IERC721(vars.bakc).safeTransferFrom( + vars.nBakc, + address(this), + bakcTokenId + ); + + //emit event + emit PairNFTDeposited(isBAYC, apeTokenId, bakcTokenId); + } + + poolState.totalPosition += arrayLength.toUint128(); + } + + function stakingPairNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + ApeCoinStaking.PairNftDepositWithAmount[] + memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + arrayLength + ); + vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + // check pair status + { + IApeStakingVault.PairingStatus + memory localPairStatus = poolState.pairStatus[apeTokenId]; + require( + localPairStatus.tokenId == bakcTokenId && + localPairStatus.isPaired, + "wrong pair status" + ); + } + + // construct staking data + _nfts[index] = ApeCoinStaking.SingleNft({ + tokenId: apeTokenId, + amount: vars.positionCap.toUint224() + }); + _nftPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184() + }); + + //emit event + emit PairNFTStaked(isBAYC, apeTokenId, bakcTokenId); + } + + // prepare Ape coin + uint256 totalBorrow = (vars.positionCap + vars.bakcMatchedCap) * + arrayLength; + ApeStakingCommonLogic.borrowCApeFromPool(poolState, vars, totalBorrow); + + //stake in ApeCoinStaking + ApeCoinStaking.PairNftDepositWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 0 + ); + if (isBAYC) { + vars.apeCoinStaking.depositBAYC(_nfts); + vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.depositMAYC(_nfts); + vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); + } + } + + function withdrawPairNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); + + vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; + vars.apeToken = isBAYC ? vars.bayc : vars.mayc; + vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; + vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; + vars._nfts = new ApeCoinStaking.SingleNft[](arrayLength); + vars._nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + //check pair status + require( + poolState.pairStatus[apeTokenId].tokenId == bakcTokenId, + "wrong ape and bakc pair" + ); + + //check ntoken owner + { + address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); + address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); + address msgSender = msg.sender; + require( + msgSender == nApeOwner || msgSender == nBakcOwner, + "not owner" + ); + } + + // update pair status + delete poolState.pairStatus[apeTokenId]; + delete poolState.tokenStatus[apeTokenId]; + + // we only need to check pair staking position + (, bool isPaired) = vars.apeCoinStaking.mainToBakc( + vars.apeStakingPoolId, + apeTokenId + ); + if (isPaired) { + vars._nfts[vars.stakingPair] = ApeCoinStaking.SingleNft({ + tokenId: apeTokenId, + amount: vars.positionCap.toUint224() + }); + + vars._nftPairs[vars.stakingPair] = ApeCoinStaking + .PairNftWithdrawWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); + vars.stakingPair++; + } + } + + //update state + poolState.totalPosition -= arrayLength.toUint128(); + + //withdraw from ApeCoinStaking and compound + if (vars.stakingPair > 0) { + { + ApeCoinStaking.SingleNft[] memory _nfts = vars._nfts; + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _nftPairs = vars._nftPairs; + uint256 stakingPair = vars.stakingPair; + assembly { + mstore(_nfts, stakingPair) + } + assembly { + mstore(_nftPairs, stakingPair) + } + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); + if (isBAYC) { + vars.apeCoinStaking.withdrawBAYC(vars._nfts, address(this)); + vars.apeCoinStaking.withdrawBAKC(vars._nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.withdrawMAYC(vars._nfts, address(this)); + vars.apeCoinStaking.withdrawBAKC(_otherPairs, vars._nftPairs); + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + + uint256 totalRepay = ApeStakingCommonLogic.calculateRepayAndCompound( + poolState, + vars, + balanceDiff, + vars.positionCap + vars.bakcMatchedCap + ); + + if (totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); + IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + } + } + + //transfer ape and BAKC back to nToken + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + IERC721(vars.apeToken).safeTransferFrom( + address(this), + vars.nApe, + apeTokenId + ); + IERC721(vars.bakc).safeTransferFrom( + address(this), + vars.nBakc, + bakcTokenId + ); + + //emit event + emit PairNFTWithdrew(isBAYC, apeTokenId, bakcTokenId); + } + } + + function claimPairNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); + } + + function compoundPairNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + "wrong param" + ); + + uint256[] memory _nfts = new uint256[](arrayLength); + ApeCoinStaking.PairNft[] + memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + // check pair status + IApeStakingVault.PairingStatus memory localPairStatus = poolState + .pairStatus[apeTokenId]; + require( + localPairStatus.tokenId == bakcTokenId && + localPairStatus.isPaired, + "wrong pair status" + ); + + // construct staking data + _nfts[index] = apeTokenId; + _nftPairs[index] = ApeCoinStaking.PairNft({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId + }); + + //emit event + emit PairNFTCompounded(isBAYC, apeTokenId, bakcTokenId); + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + + //claim from ApeCoinStaking + { + ApeCoinStaking.PairNft[] + memory _otherPairs = new ApeCoinStaking.PairNft[](0); + if (isBAYC) { + vars.apeCoinStaking.claimSelfBAYC(_nfts); + vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.claimSelfMAYC(_nfts); + vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); + } + } + + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + + //repay and compound + vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; + uint256 totalRepay = ApeStakingCommonLogic.calculateRepayAndCompound( + poolState, + vars, + balanceDiff, + vars.positionCap + vars.bakcMatchedCap + ); + + if (totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); + IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + } + } + + function _claimPairNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) internal { + vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + uint256 rewardShares; + address claimFor; + uint256 arrayLength = apeTokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + //just need to check ape ntoken owner + { + address nApe = isBAYC ? vars.nBayc : vars.nMayc; + address nApeOwner = IERC721(nApe).ownerOf(apeTokenId); + if (claimFor == address(0)) { + claimFor = nApeOwner; + } else { + require(nApeOwner == claimFor, "claim not for same owner"); + } + } + + //check pair status + require( + poolState.pairStatus[apeTokenId].tokenId == bakcTokenId, + "wrong ape and bakc pair" + ); + + //update reward, to save gas we don't claim pending reward in ApeCoinStaking. + rewardShares += (vars.accumulatedRewardsPerNft - + poolState.tokenStatus[apeTokenId].rewardsDebt); + poolState.tokenStatus[apeTokenId].rewardsDebt = vars + .accumulatedRewardsPerNft; + + //emit event + emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); + } + + if (rewardShares > 0) { + IERC20(vars.cApe).safeTransfer(claimFor, rewardShares); + } + } + + + + +} diff --git a/contracts/apestaking/logic/ApeStakingVaultLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol similarity index 51% rename from contracts/apestaking/logic/ApeStakingVaultLogic.sol rename to contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index f7f5ce43d..cd0b50914 100644 --- a/contracts/apestaking/logic/ApeStakingVaultLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -11,13 +11,14 @@ import "../../interfaces/ICApe.sol"; import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; +import "./ApeStakingCommonLogic.sol"; /** - * @title ApeStakingVaultLogic library + * @title ApeStakingSinglePoolLogic library * * @notice Implements the base logic for ape staking vault */ -library ApeStakingVaultLogic { +library ApeStakingSinglePoolLogic { using PercentageMath for uint256; using SafeCast for uint256; using SafeERC20 for IERC20; @@ -33,20 +34,6 @@ library ApeStakingVaultLogic { uint256 constant MAYC_POOL_ID = 2; uint256 constant BAKC_POOL_ID = 3; - event PairNFTDeposited( - bool isBAYC, - uint256 apeTokenId, - uint256 bakcTokenId - ); - event PairNFTStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); - event PairNFTWithdrew(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); - event PairNFTClaimed(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); - event PairNFTCompounded( - bool isBAYC, - uint256 apeTokenId, - uint256 bakcTokenId - ); - event NFTDeposited(address nft, uint256 tokenId); event NFTStaked(address nft, uint256 tokenId); event NFTPairStaked(address nft, uint256 apeTokenId, uint256 bakcTokenId); @@ -54,431 +41,6 @@ library ApeStakingVaultLogic { event NFTClaimed(address nft, uint256 tokenId); event NFTWithdrawn(address nft, uint256 tokenId); - function depositPairNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external { - uint256 arrayLength = apeTokenIds.length; - require( - arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" - ); - - vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; - vars.apeToken = isBAYC ? vars.bayc : vars.mayc; - vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; - address msgSender = msg.sender; - vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; - - //check ntoken owner - { - address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); - address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); - require( - msgSender == nApeOwner && msgSender == nBakcOwner, - "not owner" - ); - } - - // check both ape and bakc are not staking - { - (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( - vars.apeStakingPoolId, - apeTokenId - ); - require(stakedAmount == 0, "ape already staked"); - (stakedAmount, ) = vars.apeCoinStaking.nftPosition( - BAKC_POOL_ID, - bakcTokenId - ); - require(stakedAmount == 0, "bakc already staked"); - (, bool isPaired) = vars.apeCoinStaking.mainToBakc( - vars.apeStakingPoolId, - apeTokenId - ); - require(!isPaired, "ape already pair staked"); - } - - //update pair status - poolState.pairStatus[apeTokenId] = IApeStakingVault.PairingStatus({ - tokenId: bakcTokenId, - isPaired: true - }); - - //update token status - poolState.tokenStatus[apeTokenId] = IApeStakingVault.TokenStatus({ - rewardsDebt: vars.accumulatedRewardsPerNft, - isInPool: true - }); - - //transfer ape and BAKC - IERC721(vars.apeToken).safeTransferFrom( - vars.nApe, - address(this), - apeTokenId - ); - IERC721(vars.bakc).safeTransferFrom( - vars.nBakc, - address(this), - bakcTokenId - ); - - //emit event - emit PairNFTDeposited(isBAYC, apeTokenId, bakcTokenId); - } - - poolState.totalPosition += arrayLength.toUint128(); - } - - function stakingPairNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external { - uint256 arrayLength = apeTokenIds.length; - require( - arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" - ); - - ApeCoinStaking.SingleNft[] - memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); - ApeCoinStaking.PairNftDepositWithAmount[] - memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - arrayLength - ); - vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; - - // check pair status - { - IApeStakingVault.PairingStatus - memory localPairStatus = poolState.pairStatus[apeTokenId]; - require( - localPairStatus.tokenId == bakcTokenId && - localPairStatus.isPaired, - "wrong pair status" - ); - } - - // construct staking data - _nfts[index] = ApeCoinStaking.SingleNft({ - tokenId: apeTokenId, - amount: vars.positionCap.toUint224() - }); - _nftPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ - mainTokenId: apeTokenId, - bakcTokenId: bakcTokenId, - amount: vars.bakcMatchedCap.toUint184() - }); - - //emit event - emit PairNFTStaked(isBAYC, apeTokenId, bakcTokenId); - } - - // prepare Ape coin - uint256 totalBorrow = (vars.positionCap + vars.bakcMatchedCap) * - arrayLength; - _borrowCApeFromPool(poolState, vars, totalBorrow); - - //stake in ApeCoinStaking - ApeCoinStaking.PairNftDepositWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 0 - ); - if (isBAYC) { - vars.apeCoinStaking.depositBAYC(_nfts); - vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); - } else { - vars.apeCoinStaking.depositMAYC(_nfts); - vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); - } - } - - function withdrawPairNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external { - uint256 arrayLength = apeTokenIds.length; - require( - arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" - ); - - _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); - - vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; - vars.apeToken = isBAYC ? vars.bayc : vars.mayc; - vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; - vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; - vars._nfts = new ApeCoinStaking.SingleNft[](arrayLength); - vars._nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - arrayLength - ); - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; - - //check pair status - require( - poolState.pairStatus[apeTokenId].tokenId == bakcTokenId, - "wrong ape and bakc pair" - ); - - //check ntoken owner - { - address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); - address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); - address msgSender = msg.sender; - require( - msgSender == nApeOwner || msgSender == nBakcOwner, - "not owner" - ); - } - - // update pair status - delete poolState.pairStatus[apeTokenId]; - delete poolState.tokenStatus[apeTokenId]; - - // we only need to check pair staking position - (, bool isPaired) = vars.apeCoinStaking.mainToBakc( - vars.apeStakingPoolId, - apeTokenId - ); - if (isPaired) { - vars._nfts[vars.stakingPair] = ApeCoinStaking.SingleNft({ - tokenId: apeTokenId, - amount: vars.positionCap.toUint224() - }); - - vars._nftPairs[vars.stakingPair] = ApeCoinStaking - .PairNftWithdrawWithAmount({ - mainTokenId: apeTokenId, - bakcTokenId: bakcTokenId, - amount: vars.bakcMatchedCap.toUint184(), - isUncommit: true - }); - vars.stakingPair++; - } - } - - //update state - poolState.totalPosition -= arrayLength.toUint128(); - - //withdraw from ApeCoinStaking and compound - if (vars.stakingPair > 0) { - { - ApeCoinStaking.SingleNft[] memory _nfts = vars._nfts; - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _nftPairs = vars._nftPairs; - uint256 stakingPair = vars.stakingPair; - assembly { - mstore(_nfts, stakingPair) - } - assembly { - mstore(_nftPairs, stakingPair) - } - } - - vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); - if (isBAYC) { - vars.apeCoinStaking.withdrawBAYC(vars._nfts, address(this)); - vars.apeCoinStaking.withdrawBAKC(vars._nftPairs, _otherPairs); - } else { - vars.apeCoinStaking.withdrawMAYC(vars._nfts, address(this)); - vars.apeCoinStaking.withdrawBAKC(_otherPairs, vars._nftPairs); - } - vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - - uint256 totalRepay = _reayAndCompound( - poolState, - vars, - balanceDiff, - vars.positionCap + vars.bakcMatchedCap - ); - - if (totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); - IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); - } - } - - //transfer ape and BAKC back to nToken - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; - - IERC721(vars.apeToken).safeTransferFrom( - address(this), - vars.nApe, - apeTokenId - ); - IERC721(vars.bakc).safeTransferFrom( - address(this), - vars.nBakc, - bakcTokenId - ); - - //emit event - emit PairNFTWithdrew(isBAYC, apeTokenId, bakcTokenId); - } - } - - function claimPairNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external { - uint256 arrayLength = apeTokenIds.length; - require( - arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" - ); - - _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); - } - - function compoundPairNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external { - uint256 arrayLength = apeTokenIds.length; - require( - arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" - ); - - uint256[] memory _nfts = new uint256[](arrayLength); - ApeCoinStaking.PairNft[] - memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; - - // check pair status - IApeStakingVault.PairingStatus memory localPairStatus = poolState - .pairStatus[apeTokenId]; - require( - localPairStatus.tokenId == bakcTokenId && - localPairStatus.isPaired, - "wrong pair status" - ); - - // construct staking data - _nfts[index] = apeTokenId; - _nftPairs[index] = ApeCoinStaking.PairNft({ - mainTokenId: apeTokenId, - bakcTokenId: bakcTokenId - }); - - //emit event - emit PairNFTCompounded(isBAYC, apeTokenId, bakcTokenId); - } - - vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - - //claim from ApeCoinStaking - { - ApeCoinStaking.PairNft[] - memory _otherPairs = new ApeCoinStaking.PairNft[](0); - if (isBAYC) { - vars.apeCoinStaking.claimSelfBAYC(_nfts); - vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); - } else { - vars.apeCoinStaking.claimSelfMAYC(_nfts); - vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); - } - } - - vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - - //repay and compound - vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; - uint256 totalRepay = _reayAndCompound( - poolState, - vars, - balanceDiff, - vars.positionCap + vars.bakcMatchedCap - ); - - if (totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); - IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); - } - } - - function _claimPairNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) internal { - vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - uint256 rewardShares; - address claimFor; - uint256 arrayLength = apeTokenIds.length; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; - - //just need to check ape ntoken owner - { - address nApe = isBAYC ? vars.nBayc : vars.nMayc; - address nApeOwner = IERC721(nApe).ownerOf(apeTokenId); - if (claimFor == address(0)) { - claimFor = nApeOwner; - } else { - require(nApeOwner == claimFor, "claim not for same owner"); - } - } - - //check pair status - require( - poolState.pairStatus[apeTokenId].tokenId == bakcTokenId, - "wrong ape and bakc pair" - ); - - //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - rewardShares += (vars.accumulatedRewardsPerNft - - poolState.tokenStatus[apeTokenId].rewardsDebt); - poolState.tokenStatus[apeTokenId].rewardsDebt = vars - .accumulatedRewardsPerNft; - - //emit event - emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); - } - - if (rewardShares > 0) { - IERC20(vars.cApe).safeTransfer(claimFor, rewardShares); - } - } function depositNFT( IParaApeStaking.PoolState storage poolState, @@ -512,8 +74,8 @@ library ApeStakingVaultLogic { } else { vars.nApe = (nft == vars.bayc) ? vars.nBayc : vars.nMayc; vars.apeStakingPoolId = (nft == vars.bayc) - ? BAYC_POOL_ID - : MAYC_POOL_ID; + ? BAYC_POOL_ID + : MAYC_POOL_ID; address nApeOwner = IERC721(vars.nApe).ownerOf(tokenId); require(msgSender == nApeOwner, "not ape owner"); @@ -539,8 +101,8 @@ library ApeStakingVaultLogic { //update token status poolState.tokenStatus[tokenId] = IApeStakingVault.TokenStatus({ - rewardsDebt: vars.accumulatedRewardsPerNft, - isInPool: true + rewardsDebt: vars.accumulatedRewardsPerNft, + isInPool: true }); //emit event @@ -560,10 +122,10 @@ library ApeStakingVaultLogic { require(arrayLength > 0, "wrong param"); ApeCoinStaking.SingleNft[] - memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); vars.positionCap = (nft == vars.bayc) - ? vars.baycMatchedCap - : vars.maycMatchedCap; + ? vars.baycMatchedCap + : vars.maycMatchedCap; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -574,8 +136,8 @@ library ApeStakingVaultLogic { // construct staking data _nfts[index] = ApeCoinStaking.SingleNft({ - tokenId: tokenId, - amount: vars.positionCap.toUint224() + tokenId: tokenId, + amount: vars.positionCap.toUint224() }); //emit event @@ -584,7 +146,7 @@ library ApeStakingVaultLogic { // prepare Ape coin uint256 totalBorrow = vars.positionCap * arrayLength; - _borrowCApeFromPool(poolState, vars, totalBorrow); + ApeStakingCommonLogic.borrowCApeFromPool(poolState, vars, totalBorrow); //stake in ApeCoinStaking if (nft == vars.bayc) { @@ -609,9 +171,9 @@ library ApeStakingVaultLogic { ); ApeCoinStaking.PairNftDepositWithAmount[] - memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - arrayLength - ); + memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + arrayLength + ); for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -627,9 +189,9 @@ library ApeStakingVaultLogic { // construct staking data _nftPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ - mainTokenId: apeTokenId, - bakcTokenId: bakcTokenId, - amount: vars.bakcMatchedCap.toUint184() + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184() }); //emit event @@ -638,13 +200,13 @@ library ApeStakingVaultLogic { // prepare Ape coin uint256 totalBorrow = vars.bakcMatchedCap * arrayLength; - _borrowCApeFromPool(bakcPoolState, vars, totalBorrow); + ApeStakingCommonLogic.borrowCApeFromPool(bakcPoolState, vars, totalBorrow); //stake in ApeCoinStaking ApeCoinStaking.PairNftDepositWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 0 - ); + memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 0 + ); if (nft == vars.bayc) { vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); } else { @@ -691,7 +253,7 @@ library ApeStakingVaultLogic { IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); //repay and compound - uint256 totalRepay = _reayAndCompound( + uint256 totalRepay = ApeStakingCommonLogic.calculateRepayAndCompound( poolState, vars, balanceDiff, @@ -720,22 +282,22 @@ library ApeStakingVaultLogic { IParaApeStaking.PoolState storage apePoolState; if (nft == vars.bayc) { apePoolState = vaultStorage.poolStates[ - ApeStakingVaultLogic.BAYC_SINGLE_POOL_ID + BAYC_SINGLE_POOL_ID ]; vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; } else { apePoolState = vaultStorage.poolStates[ - ApeStakingVaultLogic.MAYC_SINGLE_POOL_ID + MAYC_SINGLE_POOL_ID ]; vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; } IParaApeStaking.PoolState storage bakcPoolState = vaultStorage - .poolStates[BAKC_SINGLE_POOL_ID]; + .poolStates[BAKC_SINGLE_POOL_ID]; uint256 totalReward; { ApeCoinStaking.PairNft[] - memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); + memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -751,8 +313,8 @@ library ApeStakingVaultLogic { // construct staking data _nftPairs[index] = ApeCoinStaking.PairNft({ - mainTokenId: apeTokenId, - bakcTokenId: bakcTokenId + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId }); //emit event @@ -763,7 +325,7 @@ library ApeStakingVaultLogic { //claim from ApeCoinStaking { ApeCoinStaking.PairNft[] - memory _otherPairs = new ApeCoinStaking.PairNft[](0); + memory _otherPairs = new ApeCoinStaking.PairNft[](0); if (nft == vars.bayc) { vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); } else { @@ -776,7 +338,7 @@ library ApeStakingVaultLogic { } //repay and compound - uint256 totalRepay = _reayAndCompoundBAKC( + uint256 totalRepay = _calculateRepayAndCompoundBAKC( apePoolState, bakcPoolState, vars, @@ -862,7 +424,7 @@ library ApeStakingVaultLogic { IParaApeStaking.PoolState storage apePoolState; IParaApeStaking.PoolState storage bakcPoolState = vaultStorage - .poolStates[BAKC_SINGLE_POOL_ID]; + .poolStates[BAKC_SINGLE_POOL_ID]; if (nft == vars.bayc) { vars.apeStakingPoolId = BAYC_POOL_ID; vars.positionCap = vars.baycMatchedCap; @@ -891,8 +453,8 @@ library ApeStakingVaultLogic { ); if (stakedAmount > 0) { vars._nfts[singleStakingCount] = ApeCoinStaking.SingleNft({ - tokenId: tokenId, - amount: vars.positionCap.toUint224() + tokenId: tokenId, + amount: vars.positionCap.toUint224() }); singleStakingCount++; } @@ -901,16 +463,16 @@ library ApeStakingVaultLogic { //check bakc position { (uint256 bakcTokenId, bool isPaired) = vars - .apeCoinStaking - .mainToBakc(vars.apeStakingPoolId, tokenId); + .apeCoinStaking + .mainToBakc(vars.apeStakingPoolId, tokenId); if (isPaired) { vars._nftPairs[pairStakingCount] = ApeCoinStaking - .PairNftWithdrawWithAmount({ - mainTokenId: tokenId, - bakcTokenId: bakcTokenId.toUint32(), - amount: vars.bakcMatchedCap.toUint184(), - isUncommit: true - }); + .PairNftWithdrawWithAmount({ + mainTokenId: tokenId, + bakcTokenId: bakcTokenId.toUint32(), + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); pairStakingCount++; } } @@ -933,7 +495,7 @@ library ApeStakingVaultLogic { uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - totalRepay += _reayAndCompound( + totalRepay += ApeStakingCommonLogic.calculateRepayAndCompound( apePoolState, vars, balanceDiff, @@ -943,16 +505,16 @@ library ApeStakingVaultLogic { if (pairStakingCount > 0) { ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs = vars - ._nftPairs; + ._nftPairs; assembly { mstore(_nftPairs, pairStakingCount) } vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); if (nft == vars.bayc) { vars.apeCoinStaking.withdrawBAKC(vars._nftPairs, _otherPairs); } else { @@ -962,7 +524,7 @@ library ApeStakingVaultLogic { uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - totalRepay += _reayAndCompoundBAKC( + totalRepay += _calculateRepayAndCompoundBAKC( apePoolState, bakcPoolState, vars, @@ -983,29 +545,29 @@ library ApeStakingVaultLogic { ) internal { uint256 arrayLength = tokenIds.length; ApeCoinStaking.PairNftWithdrawWithAmount[] - memory baycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - arrayLength - ); + memory baycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); ApeCoinStaking.PairNftWithdrawWithAmount[] - memory maycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - arrayLength - ); + memory maycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); uint256 baycPairCount; uint256 maycPairCount; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; (uint256 mainTokenId, bool isPaired) = vars - .apeCoinStaking - .bakcToMain(tokenId, BAYC_POOL_ID); + .apeCoinStaking + .bakcToMain(tokenId, BAYC_POOL_ID); if (isPaired) { baycPair[baycPairCount] = ApeCoinStaking - .PairNftWithdrawWithAmount({ - mainTokenId: mainTokenId.toUint32(), - bakcTokenId: tokenId, - amount: vars.bakcMatchedCap.toUint184(), - isUncommit: true - }); + .PairNftWithdrawWithAmount({ + mainTokenId: mainTokenId.toUint32(), + bakcTokenId: tokenId, + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); baycPairCount++; continue; } @@ -1016,12 +578,12 @@ library ApeStakingVaultLogic { ); if (isPaired) { maycPair[maycPairCount] = ApeCoinStaking - .PairNftWithdrawWithAmount({ - mainTokenId: mainTokenId.toUint32(), - bakcTokenId: tokenId, - amount: vars.bakcMatchedCap.toUint184(), - isUncommit: true - }); + .PairNftWithdrawWithAmount({ + mainTokenId: mainTokenId.toUint32(), + bakcTokenId: tokenId, + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); maycPairCount++; continue; } @@ -1035,9 +597,9 @@ library ApeStakingVaultLogic { } ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); uint256 totalRepay = 0; if (baycPairCount > 0) { @@ -1048,7 +610,7 @@ library ApeStakingVaultLogic { IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; - totalRepay += _reayAndCompoundBAKC( + totalRepay += _calculateRepayAndCompoundBAKC( vaultStorage.poolStates[BAYC_SINGLE_POOL_ID], vaultStorage.poolStates[BAKC_SINGLE_POOL_ID], vars, @@ -1063,7 +625,7 @@ library ApeStakingVaultLogic { IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; - totalRepay += _reayAndCompoundBAKC( + totalRepay += _calculateRepayAndCompoundBAKC( vaultStorage.poolStates[MAYC_SINGLE_POOL_ID], vaultStorage.poolStates[BAKC_SINGLE_POOL_ID], vars, @@ -1093,10 +655,10 @@ library ApeStakingVaultLogic { //just need to check ape ntoken owner { address nToken = (nft == vars.bayc) - ? vars.nBayc - : (nft == vars.mayc) - ? vars.nMayc - : vars.nBakc; + ? vars.nBayc + : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; address nTokenOwner = IERC721(nToken).ownerOf(tokenId); if (claimFor == address(0)) { claimFor = nTokenOwner; @@ -1110,9 +672,9 @@ library ApeStakingVaultLogic { //update reward, to save gas we don't claim pending reward in ApeCoinStaking. rewardShares += (vars.accumulatedRewardsPerNft - - poolState.tokenStatus[tokenId].rewardsDebt); + poolState.tokenStatus[tokenId].rewardsDebt); poolState.tokenStatus[tokenId].rewardsDebt = vars - .accumulatedRewardsPerNft; + .accumulatedRewardsPerNft; //emit event emit NFTClaimed(nft, tokenId); @@ -1123,52 +685,7 @@ library ApeStakingVaultLogic { } } - function _reayAndCompound( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - uint256 totalAmount, - uint256 positionCap - ) internal returns (uint256) { - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); - uint256 latestBorrowIndex = IPool(vars.pool) - .getReserveNormalizedVariableDebt(vars.cApe); - uint256 cApeDebtShare = poolState.cApeDebtShare; - uint128 currentTotalPosition = poolState.totalPosition; - uint256 debtInterest = _calculateCurrentPositionDebtInterest( - cApeDebtShare, - currentTotalPosition, - positionCap, - cApeExchangeRate, - latestBorrowIndex - ); - if (debtInterest >= totalAmount) { - cApeDebtShare -= totalAmount.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate - ); - poolState.cApeDebtShare = cApeDebtShare; - return totalAmount; - } else { - //repay debt - cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate - ); - - //update reward index - if (currentTotalPosition != 0) { - uint256 remainingReward = totalAmount - debtInterest; - uint256 shareAmount = remainingReward.rayDiv(cApeExchangeRate); - poolState.accumulatedRewardsPerNft += - shareAmount.toUint128() / - currentTotalPosition; - } - poolState.cApeDebtShare = cApeDebtShare; - return debtInterest; - } - } - - function _reayAndCompoundBAKC( + function _calculateRepayAndCompoundBAKC( IParaApeStaking.PoolState storage apePoolState, IParaApeStaking.PoolState storage bakcPoolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, @@ -1178,11 +695,11 @@ library ApeStakingVaultLogic { WadRayMath.RAY ); uint256 latestBorrowIndex = IPool(vars.pool) - .getReserveNormalizedVariableDebt(vars.cApe); + .getReserveNormalizedVariableDebt(vars.cApe); uint256 cApeDebtShare = bakcPoolState.cApeDebtShare; uint128 apeTotalPosition = apePoolState.totalPosition; uint128 bakcTotalPosition = bakcPoolState.totalPosition; - uint256 debtInterest = _calculateCurrentPositionDebtInterest( + uint256 debtInterest = ApeStakingCommonLogic.calculateCurrentPositionDebtInterest( cApeDebtShare, bakcTotalPosition, vars.bakcMatchedCap, @@ -1210,46 +727,17 @@ library ApeStakingVaultLogic { if (apeTotalPosition != 0) { apePoolState.accumulatedRewardsPerNft += - apeShareAmount.toUint128() / - apeTotalPosition; + apeShareAmount.toUint128() / + apeTotalPosition; } if (bakcTotalPosition != 0) { bakcPoolState.accumulatedRewardsPerNft += - (shareAmount - apeShareAmount).toUint128() / - bakcTotalPosition; + (shareAmount - apeShareAmount).toUint128() / + bakcTotalPosition; } bakcPoolState.cApeDebtShare = cApeDebtShare; return debtInterest; } } - function _calculateCurrentPositionDebtInterest( - uint256 cApeDebtShare, - uint256 totalPosition, - uint256 perPositionCap, - uint256 cApeExchangeRate, - uint256 latestBorrowIndex - ) internal pure returns (uint256) { - uint256 currentDebt = cApeDebtShare.rayMul(cApeExchangeRate).rayMul( - latestBorrowIndex - ); - return (currentDebt - perPositionCap * totalPosition); - } - - function _borrowCApeFromPool( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - uint256 totalBorrow - ) internal { - uint256 latestBorrowIndex = IPool(vars.pool).borrowPoolCApe( - totalBorrow - ); - IAutoCompoundApe(vars.cApe).withdraw(totalBorrow); - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); - poolState.cApeDebtShare += totalBorrow.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate - ); - } } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index c49312a78..ac6c849f0 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -297,6 +297,9 @@ import { ApeStakingP2PLogic, ApeStakingVaultLogic, ApeStakingVaultLogic__factory, + ApeStakingPairPoolLogic__factory, + ApeStakingPairPoolLogic, + ApeStakingSinglePoolLogic__factory, ApeStakingSinglePoolLogic, } from "../types"; import {MockContract} from "ethereum-waffle"; import { @@ -2721,25 +2724,36 @@ export const deployApeStakingP2PLogic = async (verify?: boolean) => verify ) as Promise; -export const deployApeStakingVaultLogic = async (verify?: boolean) => +export const deployApeStakingPairPoolLogic = async (verify?: boolean) => withSaveAndVerify( - new ApeStakingVaultLogic__factory(await getFirstSigner()), - eContractid.ApeStakingVaultLogic, + new ApeStakingPairPoolLogic__factory(await getFirstSigner()), + eContractid.ApeStakingPairPoolLogic, [], verify - ) as Promise; + ) as Promise; + +export const deployApeStakingSinglePoolLogic = async (verify?: boolean) => + withSaveAndVerify( + new ApeStakingSinglePoolLogic__factory(await getFirstSigner()), + eContractid.ApeStakingSinglePoolLogic, + [], + verify + ) as Promise; export const deployParaApeStakingLibraries = async ( verify?: boolean ): Promise => { const p2pLogic = await deployApeStakingP2PLogic(verify); - const vaultLogic = await deployApeStakingVaultLogic(verify); + const pairPoolLogic = await deployApeStakingPairPoolLogic(verify); + const singlePoolLogic = await deployApeStakingSinglePoolLogic(verify); return { ["contracts/apestaking/logic/ApeStakingP2PLogic.sol:ApeStakingP2PLogic"]: p2pLogic.address, - ["contracts/apestaking/logic/ApeStakingVaultLogic.sol:ApeStakingVaultLogic"]: - vaultLogic.address, + ["contracts/apestaking/logic/ApeStakingPairPoolLogic.sol:ApeStakingPairPoolLogic"]: + pairPoolLogic.address, + ["contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol:ApeStakingSinglePoolLogic"]: + singlePoolLogic.address, }; }; diff --git a/helpers/types.ts b/helpers/types.ts index f4fe1879d..b3560d94c 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -261,7 +261,8 @@ export enum eContractid { HelperContract = "HelperContract", P2PPairStakingImpl = "P2PPairStakingImpl", ApeStakingP2PLogic = "ApeStakingP2PLogic", - ApeStakingVaultLogic = "ApeStakingVaultLogic", + ApeStakingPairPoolLogic = "ApeStakingPairPoolLogic", + ApeStakingSinglePoolLogic = "ApeStakingSinglePoolLogic", ParaApeStakingImpl = "ParaApeStakingImpl", ParaApeStaking = "ParaApeStaking", yAPE = "yAPE", From e439449a5ca342294f69e2b9b4d0d2fb72458664 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 7 Jul 2023 14:02:36 +0800 Subject: [PATCH 44/99] chore: add owner interface --- contracts/apestaking/ParaApeStaking.sol | 98 ++++- .../logic/ApeStakingCommonLogic.sol | 362 +----------------- .../logic/ApeStakingPairPoolLogic.sol | 57 +-- .../logic/ApeStakingSinglePoolLogic.sol | 175 +++++---- contracts/interfaces/IApeStakingVault.sol | 19 +- contracts/interfaces/IParaApeStaking.sol | 9 + helpers/contracts-deployments.ts | 19 +- 7 files changed, 240 insertions(+), 499 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 1e620d08d..4f1d60170 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -43,7 +43,6 @@ contract ParaApeStaking is address internal immutable apeCoin; address internal immutable cApe; ApeCoinStaking internal immutable apeCoinStaking; - uint256 public immutable compoundFee; uint256 private immutable baycMatchedCap; uint256 private immutable maycMatchedCap; uint256 private immutable bakcMatchedCap; @@ -57,6 +56,9 @@ contract ParaApeStaking is private positionCApeShareDebt; mapping(address => uint256) private cApeShareBalance; + address public apeStakingBot; + uint64 public compoundFee; + constructor( address _pool, address _bayc, @@ -68,8 +70,7 @@ contract ParaApeStaking is address _apeCoin, address _cApe, address _apeCoinStaking, - address _aclManager, - uint256 _compoundFee + address _aclManager ) { pool = _pool; bayc = _bayc; @@ -82,7 +83,6 @@ contract ParaApeStaking is cApe = _cApe; apeCoinStaking = ApeCoinStaking(_apeCoinStaking); aclManager = IACLManager(_aclManager); - compoundFee = _compoundFee; ( , @@ -134,6 +134,11 @@ contract ParaApeStaking is _; } + modifier onlyApeStakingBot() { + require(apeStakingBot == msg.sender, "not ape staking bot"); + _; + } + function _onlyPoolAdmin() internal view { require( aclManager.isPoolAdmin(msg.sender), @@ -144,13 +149,29 @@ contract ParaApeStaking is function _onlyPoolOrEmergencyAdmin() internal view { require( aclManager.isPoolAdmin(msg.sender) || - aclManager.isEmergencyAdmin(msg.sender), + aclManager.isEmergencyAdmin(msg.sender), Errors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN ); } + function setApeStakingBot(address _apeStakingBot) external onlyPoolAdmin { + address oldValue = apeStakingBot; + if (oldValue != _apeStakingBot) { + apeStakingBot = _apeStakingBot; + emit ApeStakingBotUpdated(oldValue, _apeStakingBot); + } + } + + function setCompoundFee(uint64 _compoundFee) external onlyPoolAdmin { + uint64 oldValue = compoundFee; + if (oldValue != _compoundFee) { + compoundFee = _compoundFee; + emit CompoundFeeUpdated(oldValue, _compoundFee); + } + } + /** - * @notice Pauses the contract. Only pool admin or emergency admin can call this function + * @notice Pauses the contract. Only pool admin or emergency admin can call this function **/ function pause() external onlyEmergencyOrPoolAdmin { _pause(); @@ -173,8 +194,8 @@ contract ParaApeStaking is } /* - - */ + * P2P Pair Staking Logic + */ function cancelListing(ListingOrder calldata listingOrder) external @@ -299,8 +320,28 @@ contract ParaApeStaking is return ApeStakingP2PLogic.getApeCoinStakingCap(stakingType, vars); } + /* + * Ape Staking Vault Logic + */ + VaultStorage internal vaultStorage; + function setSinglePoolApeRewardRatio( + uint128 baycRewardRatio, + uint128 maycRewardRatio + ) external onlyPoolAdmin { + uint128 oldValue = vaultStorage.baycPairStakingRewardRatio; + if (oldValue != baycRewardRatio) { + vaultStorage.baycPairStakingRewardRatio = baycRewardRatio; + emit BaycPairStakingRewardRatioUpdated(oldValue, baycRewardRatio); + } + oldValue = vaultStorage.maycPairStakingRewardRatio; + if (oldValue != maycRewardRatio) { + vaultStorage.maycPairStakingRewardRatio = maycRewardRatio; + emit MaycPairStakingRewardRatioUpdated(oldValue, maycRewardRatio); + } + } + function depositPairNFT( bool isBAYC, uint32[] calldata apeTokenIds, @@ -378,7 +419,7 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused { + ) external whenNotPaused onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID @@ -392,7 +433,10 @@ contract ParaApeStaking is ); } - function depositNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused { + function depositNFT(address nft, uint32[] calldata tokenIds) + external + whenNotPaused + { require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) @@ -408,7 +452,10 @@ contract ParaApeStaking is ); } - function stakingApe(address nft, uint32[] calldata tokenIds) external whenNotPaused { + function stakingApe(address nft, uint32[] calldata tokenIds) + external + whenNotPaused + { require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) @@ -433,7 +480,9 @@ contract ParaApeStaking is : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; ApeStakingSinglePoolLogic.stakingBAKC( vaultStorage.poolStates[poolId], - vaultStorage.poolStates[ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID], + vaultStorage.poolStates[ + ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID + ], vars, nft, apeTokenIds, @@ -441,7 +490,11 @@ contract ParaApeStaking is ); } - function compoundApe(address nft, uint32[] calldata tokenIds) external whenNotPaused { + function compoundApe(address nft, uint32[] calldata tokenIds) + external + whenNotPaused + onlyApeStakingBot + { require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) @@ -459,7 +512,7 @@ contract ParaApeStaking is address nft, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused { + ) external whenNotPaused onlyApeStakingBot { require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); ApeStakingSinglePoolLogic.compoundBAKC( @@ -471,7 +524,10 @@ contract ParaApeStaking is ); } - function claimNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused { + function claimNFT(address nft, uint32[] calldata tokenIds) + external + whenNotPaused + { require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) @@ -487,10 +543,18 @@ contract ParaApeStaking is ); } - function withdrawNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused { + function withdrawNFT(address nft, uint32[] calldata tokenIds) + external + whenNotPaused + { require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - ApeStakingSinglePoolLogic.withdrawNFT(vaultStorage, vars, nft, tokenIds); + ApeStakingSinglePoolLogic.withdrawNFT( + vaultStorage, + vars, + nft, + tokenIds + ); } function _createCacheVars() diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index bbd1ddf90..282d5d119 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -43,7 +43,7 @@ library ApeStakingCommonLogic { WadRayMath.RAY ); uint256 latestBorrowIndex = IPool(vars.pool) - .getReserveNormalizedVariableDebt(vars.cApe); + .getReserveNormalizedVariableDebt(vars.cApe); uint256 cApeDebtShare = poolState.cApeDebtShare; uint128 currentTotalPosition = poolState.totalPosition; uint256 debtInterest = calculateCurrentPositionDebtInterest( @@ -70,8 +70,8 @@ library ApeStakingCommonLogic { uint256 remainingReward = totalAmount - debtInterest; uint256 shareAmount = remainingReward.rayDiv(cApeExchangeRate); poolState.accumulatedRewardsPerNft += - shareAmount.toUint128() / - currentTotalPosition; + shareAmount.toUint128() / + currentTotalPosition; } poolState.cApeDebtShare = cApeDebtShare; return debtInterest; @@ -107,360 +107,4 @@ library ApeStakingCommonLogic { ); return (currentDebt - perPositionCap * totalPosition); } - - /* - function stake( - StakingType stakingType, - uint32 mainTokenId, - uint32 bakcTokenId - ) external nonReentrant { - (address ape, address apeNToken) = _getApeAndNTokenAddress(stakingType); - - //validate owner - address apeNTokenOwner = IERC721(apeNToken).ownerOf(mainTokenId); - require(msg.sender == apeNTokenOwner, "not ntoken owner"); - if ( - stakingType == StakingType.BAYCPairStaking || - stakingType == StakingType.MAYCPairStaking - ) { - address bakcNTokenOwner = IERC721(nBakc).ownerOf(bakcTokenId); - require(msg.sender == bakcNTokenOwner, "not bakc ntoken owner"); - } - - //get all token - _handleApeTransfer(ape, apeNToken, mainTokenId); - uint256 apePositionCap = getApeCoinStakingCap(stakingType); - uint256 latestBorrowIndex = IPool(pool).borrowPoolCApe(apePositionCap); - IAutoCompoundApe(cApe).withdraw(apePositionCap); - if ( - stakingType == StakingType.BAYCPairStaking || - stakingType == StakingType.MAYCPairStaking - ) { - IERC721(bakc).safeTransferFrom(nBakc, address(this), bakcTokenId); - } - - //update status - apeMatchedCount[ape][mainTokenId] += 1; - { - uint256 cApeExchangeRate = ICApe(cApe).getPooledApeByShares( - WadRayMath.RAY - ); - positionCApeShareDebt[stakingType][mainTokenId] = apePositionCap - .rayDiv(latestBorrowIndex) - .rayDiv(cApeExchangeRate); - } - - //stake for ApeCoinStaking - if ( - stakingType == StakingType.BAYCStaking || - stakingType == StakingType.BAYCPairStaking - ) { - ApeCoinStaking.SingleNft[] - memory singleNft = new ApeCoinStaking.SingleNft[](1); - singleNft[0].tokenId = mainTokenId; - singleNft[0].amount = apePositionCap.toUint224(); - if (stakingType == StakingType.BAYCStaking) { - apeCoinStaking.depositBAYC(singleNft); - } else { - apeCoinStaking.depositMAYC(singleNft); - } - } else { - ApeCoinStaking.PairNftDepositWithAmount[] - memory _stakingPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 1 - ); - _stakingPairs[0].mainTokenId = mainTokenId; - _stakingPairs[0].bakcTokenId = bakcTokenId; - _stakingPairs[0].amount = apePositionCap.toUint184(); - ApeCoinStaking.PairNftDepositWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 0 - ); - if (stakingType == StakingType.BAYCPairStaking) { - apeCoinStaking.depositBAKC(_stakingPairs, _otherPairs); - } else { - apeCoinStaking.depositBAKC(_otherPairs, _stakingPairs); - } - } - } - - function unstake(VaultPosition calldata position) external nonReentrant { - (address ape, address apeNToken) = _getApeAndNTokenAddress( - position.stakingType - ); - - // check owner - address apeNTokenOwner = IERC721(apeNToken).ownerOf( - position.mainTokenId - ); - if ( - position.stakingType == StakingType.BAYCPairStaking || - position.stakingType == StakingType.MAYCPairStaking - ) { - require(msg.sender == apeNTokenOwner, "no permission to break up"); - } else { - address nBakcOwner = IERC721(nBakc).ownerOf(position.bakcTokenId); - require( - msg.sender == apeNTokenOwner || msg.sender == nBakcOwner, - "no permission to break up" - ); - } - - //exit from ApeCoinStaking - uint256 apePositionCap = getApeCoinStakingCap(position.stakingType); - uint256 beforeBalance = IERC20(apeCoin).balanceOf(address(this)); - if ( - position.stakingType == StakingType.BAYCPairStaking || - position.stakingType == StakingType.MAYCPairStaking - ) { - ApeCoinStaking.SingleNft[] - memory _nfts = new ApeCoinStaking.SingleNft[](1); - _nfts[0].tokenId = position.mainTokenId; - _nfts[0].amount = apePositionCap.toUint224(); - if (position.stakingType == StakingType.BAYCStaking) { - apeCoinStaking.withdrawSelfBAYC(_nfts); - } else { - apeCoinStaking.withdrawSelfMAYC(_nfts); - } - } else { - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _nfts = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 1 - ); - _nfts[0].mainTokenId = position.mainTokenId; - _nfts[0].bakcTokenId = position.bakcTokenId; - _nfts[0].amount = apePositionCap.toUint184(); - _nfts[0].isUncommit = true; - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); - if (position.stakingType == StakingType.BAYCPairStaking) { - apeCoinStaking.withdrawBAKC(_nfts, _otherPairs); - } else { - apeCoinStaking.withdrawBAKC(_otherPairs, _nfts); - } - } - uint256 afterBalance = IERC20(apeCoin).balanceOf(address(this)); - uint256 unstakeAmount = afterBalance - beforeBalance; - IAutoCompoundApe(cApe).deposit(address(this), unstakeAmount); - - //repay ape position debt and interest debt - { - uint256 cApeExchangeRate = ICApe(cApe).getPooledApeByShares( - WadRayMath.RAY - ); - uint256 latestBorrowIndex = IPool(pool) - .getReserveNormalizedVariableDebt(cApe); - uint256 debtInterest = _calculateCurrentPositionDebtInterest( - position, - apePositionCap, - cApeExchangeRate, - latestBorrowIndex - ); - uint256 totalDebt = debtInterest + apePositionCap; - require(totalDebt <= unstakeAmount, "can't repay debt"); - IPool(pool).repay(cApe, totalDebt, address(this)); - delete positionCApeShareDebt[position.stakingType][ - position.mainTokenId - ]; - - // distribute left ape coin reward to nBAYC/nMAYC owner as cApe - if (unstakeAmount > totalDebt) { - uint256 leftAmount = unstakeAmount - totalDebt; - uint256 feeAmount = leftAmount.percentMul(compoundFee); - leftAmount -= feeAmount; - uint256 leftShare = leftAmount.rayDiv(cApeExchangeRate); - _depositCApeShareForUser(apeNTokenOwner, leftShare); - } - } - - // transfer Ape or BAKC to nToken - uint256 matchedCount = apeMatchedCount[ape][position.mainTokenId]; - if (matchedCount == 1) { - IERC721(ape).safeTransferFrom( - address(this), - apeNToken, - position.mainTokenId - ); - } - apeMatchedCount[ape][position.mainTokenId] = matchedCount - 1; - if ( - position.stakingType == StakingType.BAYCPairStaking || - position.stakingType == StakingType.MAYCPairStaking - ) { - IERC721(bakc).safeTransferFrom( - address(this), - nBakc, - position.bakcTokenId - ); - } - } - - function claimAndCompound(VaultPosition[] calldata positions) - external - nonReentrant - { - //ignore getShareByPooledApe return 0 case. - uint256 cApeExchangeRate = ICApe(cApe).getPooledApeByShares( - WadRayMath.RAY - ); - uint256 latestBorrowIndex = IPool(pool) - .getReserveNormalizedVariableDebt(cApe); - uint256 _compoundFee = compoundFee; - uint256 totalReward; - uint256 totalDebtInterest; - uint256 totalFee; - uint256 positionLength = positions.length; - for (uint256 index = 0; index < positionLength; index++) { - VaultPosition calldata position = positions[index]; - ( - uint256 reward, - uint256 debtInterest, - uint256 fee - ) = _claimAndCompound( - position, - cApeExchangeRate, - latestBorrowIndex, - _compoundFee - ); - totalReward += reward; - totalDebtInterest += debtInterest; - totalFee += fee; - } - if (totalReward > 0) { - IAutoCompoundApe(cApe).deposit(address(this), totalReward); - IPool(pool).repay(cApe, totalDebtInterest, address(this)); - IERC20(apeCoin).safeTransfer(compoundBot, totalFee); - } - } - - function _claimAndCompound( - VaultPosition calldata position, - uint256 cApeExchangeRate, - uint256 latestBorrowIndex, - uint256 _compoundFee - ) - internal - returns ( - uint256, - uint256, - uint256 - ) - { - (, address apeNToken) = _getApeAndNTokenAddress(position.stakingType); - - //get reward amount - uint256 rewardAmount; - { - uint256 balanceBefore = IERC20(apeCoin).balanceOf(address(this)); - if ( - position.stakingType == StakingType.BAYCStaking || - position.stakingType == StakingType.MAYCStaking - ) { - uint256[] memory _nfts = new uint256[](1); - _nfts[0] = position.mainTokenId; - if (position.stakingType == StakingType.BAYCStaking) { - apeCoinStaking.claimSelfBAYC(_nfts); - } else { - apeCoinStaking.claimSelfMAYC(_nfts); - } - } else { - ApeCoinStaking.PairNft[] - memory _nfts = new ApeCoinStaking.PairNft[](1); - _nfts[0].mainTokenId = position.mainTokenId; - _nfts[0].bakcTokenId = position.bakcTokenId; - ApeCoinStaking.PairNft[] - memory _otherPairs = new ApeCoinStaking.PairNft[](0); - if (position.stakingType == StakingType.BAYCPairStaking) { - apeCoinStaking.claimSelfBAKC(_nfts, _otherPairs); - } else { - apeCoinStaking.claimSelfBAKC(_otherPairs, _nfts); - } - } - uint256 balanceAfter = IERC20(apeCoin).balanceOf(address(this)); - rewardAmount = balanceAfter - balanceBefore; - } - - // calculate debt - uint256 debtInterest; - { - uint256 apePositionCap = getApeCoinStakingCap(position.stakingType); - debtInterest = _calculateCurrentPositionDebtInterest( - position, - apePositionCap, - cApeExchangeRate, - latestBorrowIndex - ); - - //simply revert if rewardAmount < debtInterest, or it's hard to update positionCApeVariableDebtIndex - require(rewardAmount >= debtInterest, ""); - positionCApeShareDebt[position.stakingType][ - position.mainTokenId - ] = apePositionCap.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate - ); - } - - uint256 remainingReward = rewardAmount - debtInterest; - uint256 feeAmount = remainingReward.percentMul(_compoundFee); - remainingReward -= feeAmount; - uint256 rewardShare = remainingReward.rayDiv(cApeExchangeRate); - _depositCApeShareForUser( - IERC721(apeNToken).ownerOf(position.mainTokenId), - rewardShare - ); - - return (rewardAmount, debtInterest, feeAmount); - } - - function _depositCApeShareForUser(address user, uint256 amount) internal { - if (amount > 0) { - cApeShareBalance[user] += amount; - } - } - - function getApeCoinStakingCap(StakingType stakingType) - public - view - returns (uint256) - { - if (stakingType == StakingType.BAYCStaking) { - return baycMatchedCap; - } else if (stakingType == StakingType.MAYCStaking) { - return maycMatchedCap; - } else { - return bakcMatchedCap; - } - } - - function _calculateCurrentPositionDebtInterest( - VaultPosition calldata position, - uint256 apePositionCap, - uint256 cApeExchangeRate, - uint256 latestBorrowIndex - ) internal view returns (uint256) { - uint256 shareDebt = positionCApeShareDebt[position.stakingType][ - position.mainTokenId - ]; - uint256 currentDebt = shareDebt.rayMul(cApeExchangeRate).rayMul( - latestBorrowIndex - ); - return (currentDebt - apePositionCap); - } - - function _handleApeTransfer( - address apeToken, - address apeNToken, - uint256 tokenId - ) internal { - address currentOwner = IERC721(apeToken).ownerOf(tokenId); - if (currentOwner != address(this)) { - IERC721(apeToken).safeTransferFrom( - apeNToken, - address(this), - tokenId - ); - } - }*/ } diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 2e2e90b97..e411b5244 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -61,11 +61,17 @@ library ApeStakingPairPoolLogic { "wrong param" ); - vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; - vars.apeToken = isBAYC ? vars.bayc : vars.mayc; - vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; + if (isBAYC) { + vars.apeStakingPoolId = BAYC_POOL_ID; + vars.apeToken = vars.bayc; + vars.nApe = vars.nBayc; + } else { + vars.apeStakingPoolId = MAYC_POOL_ID; + vars.apeToken = vars.mayc; + vars.nApe = vars.nMayc; + } address msgSender = msg.sender; - vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -107,7 +113,7 @@ library ApeStakingPairPoolLogic { //update token status poolState.tokenStatus[apeTokenId] = IApeStakingVault.TokenStatus({ - rewardsDebt: vars.accumulatedRewardsPerNft, + rewardsDebt: accumulatedRewardsPerNft, isInPool: true }); @@ -214,10 +220,17 @@ library ApeStakingPairPoolLogic { _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); - vars.apeStakingPoolId = isBAYC ? BAYC_POOL_ID : MAYC_POOL_ID; - vars.apeToken = isBAYC ? vars.bayc : vars.mayc; - vars.nApe = isBAYC ? vars.nBayc : vars.nMayc; - vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; + if (isBAYC) { + vars.apeStakingPoolId = BAYC_POOL_ID; + vars.apeToken = vars.bayc; + vars.nApe = vars.nBayc; + vars.positionCap = vars.baycMatchedCap; + } else { + vars.apeStakingPoolId = MAYC_POOL_ID; + vars.apeToken = vars.mayc; + vars.nApe = vars.nMayc; + vars.positionCap = vars.maycMatchedCap; + } vars._nfts = new ApeCoinStaking.SingleNft[](arrayLength); vars._nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength @@ -303,12 +316,13 @@ library ApeStakingPairPoolLogic { uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - uint256 totalRepay = ApeStakingCommonLogic.calculateRepayAndCompound( - poolState, - vars, - balanceDiff, - vars.positionCap + vars.bakcMatchedCap - ); + uint256 totalRepay = ApeStakingCommonLogic + .calculateRepayAndCompound( + poolState, + vars, + balanceDiff, + vars.positionCap + vars.bakcMatchedCap + ); if (totalRepay > 0) { IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); @@ -434,10 +448,10 @@ library ApeStakingPairPoolLogic { uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) internal { - vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; uint256 rewardShares; address claimFor; uint256 arrayLength = apeTokenIds.length; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -460,10 +474,11 @@ library ApeStakingPairPoolLogic { ); //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - rewardShares += (vars.accumulatedRewardsPerNft - + rewardShares += (accumulatedRewardsPerNft - poolState.tokenStatus[apeTokenId].rewardsDebt); - poolState.tokenStatus[apeTokenId].rewardsDebt = vars - .accumulatedRewardsPerNft; + poolState + .tokenStatus[apeTokenId] + .rewardsDebt = accumulatedRewardsPerNft; //emit event emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); @@ -473,8 +488,4 @@ library ApeStakingPairPoolLogic { IERC20(vars.cApe).safeTransfer(claimFor, rewardShares); } } - - - - } diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index cd0b50914..1f1ae4884 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -41,7 +41,6 @@ library ApeStakingSinglePoolLogic { event NFTClaimed(address nft, uint256 tokenId); event NFTWithdrawn(address nft, uint256 tokenId); - function depositNFT( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, @@ -74,8 +73,8 @@ library ApeStakingSinglePoolLogic { } else { vars.nApe = (nft == vars.bayc) ? vars.nBayc : vars.nMayc; vars.apeStakingPoolId = (nft == vars.bayc) - ? BAYC_POOL_ID - : MAYC_POOL_ID; + ? BAYC_POOL_ID + : MAYC_POOL_ID; address nApeOwner = IERC721(vars.nApe).ownerOf(tokenId); require(msgSender == nApeOwner, "not ape owner"); @@ -101,8 +100,8 @@ library ApeStakingSinglePoolLogic { //update token status poolState.tokenStatus[tokenId] = IApeStakingVault.TokenStatus({ - rewardsDebt: vars.accumulatedRewardsPerNft, - isInPool: true + rewardsDebt: vars.accumulatedRewardsPerNft, + isInPool: true }); //emit event @@ -122,10 +121,10 @@ library ApeStakingSinglePoolLogic { require(arrayLength > 0, "wrong param"); ApeCoinStaking.SingleNft[] - memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); vars.positionCap = (nft == vars.bayc) - ? vars.baycMatchedCap - : vars.maycMatchedCap; + ? vars.baycMatchedCap + : vars.maycMatchedCap; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -136,8 +135,8 @@ library ApeStakingSinglePoolLogic { // construct staking data _nfts[index] = ApeCoinStaking.SingleNft({ - tokenId: tokenId, - amount: vars.positionCap.toUint224() + tokenId: tokenId, + amount: vars.positionCap.toUint224() }); //emit event @@ -171,9 +170,9 @@ library ApeStakingSinglePoolLogic { ); ApeCoinStaking.PairNftDepositWithAmount[] - memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - arrayLength - ); + memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + arrayLength + ); for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -189,9 +188,9 @@ library ApeStakingSinglePoolLogic { // construct staking data _nftPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ - mainTokenId: apeTokenId, - bakcTokenId: bakcTokenId, - amount: vars.bakcMatchedCap.toUint184() + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184() }); //emit event @@ -200,13 +199,17 @@ library ApeStakingSinglePoolLogic { // prepare Ape coin uint256 totalBorrow = vars.bakcMatchedCap * arrayLength; - ApeStakingCommonLogic.borrowCApeFromPool(bakcPoolState, vars, totalBorrow); + ApeStakingCommonLogic.borrowCApeFromPool( + bakcPoolState, + vars, + totalBorrow + ); //stake in ApeCoinStaking ApeCoinStaking.PairNftDepositWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 0 - ); + memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 0 + ); if (nft == vars.bayc) { vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); } else { @@ -281,23 +284,19 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage apePoolState; if (nft == vars.bayc) { - apePoolState = vaultStorage.poolStates[ - BAYC_SINGLE_POOL_ID - ]; + apePoolState = vaultStorage.poolStates[BAYC_SINGLE_POOL_ID]; vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; } else { - apePoolState = vaultStorage.poolStates[ - MAYC_SINGLE_POOL_ID - ]; + apePoolState = vaultStorage.poolStates[MAYC_SINGLE_POOL_ID]; vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; } IParaApeStaking.PoolState storage bakcPoolState = vaultStorage - .poolStates[BAKC_SINGLE_POOL_ID]; + .poolStates[BAKC_SINGLE_POOL_ID]; uint256 totalReward; { ApeCoinStaking.PairNft[] - memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); + memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -313,8 +312,8 @@ library ApeStakingSinglePoolLogic { // construct staking data _nftPairs[index] = ApeCoinStaking.PairNft({ - mainTokenId: apeTokenId, - bakcTokenId: bakcTokenId + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId }); //emit event @@ -325,7 +324,7 @@ library ApeStakingSinglePoolLogic { //claim from ApeCoinStaking { ApeCoinStaking.PairNft[] - memory _otherPairs = new ApeCoinStaking.PairNft[](0); + memory _otherPairs = new ApeCoinStaking.PairNft[](0); if (nft == vars.bayc) { vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); } else { @@ -424,7 +423,7 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage apePoolState; IParaApeStaking.PoolState storage bakcPoolState = vaultStorage - .poolStates[BAKC_SINGLE_POOL_ID]; + .poolStates[BAKC_SINGLE_POOL_ID]; if (nft == vars.bayc) { vars.apeStakingPoolId = BAYC_POOL_ID; vars.positionCap = vars.baycMatchedCap; @@ -453,8 +452,8 @@ library ApeStakingSinglePoolLogic { ); if (stakedAmount > 0) { vars._nfts[singleStakingCount] = ApeCoinStaking.SingleNft({ - tokenId: tokenId, - amount: vars.positionCap.toUint224() + tokenId: tokenId, + amount: vars.positionCap.toUint224() }); singleStakingCount++; } @@ -463,16 +462,16 @@ library ApeStakingSinglePoolLogic { //check bakc position { (uint256 bakcTokenId, bool isPaired) = vars - .apeCoinStaking - .mainToBakc(vars.apeStakingPoolId, tokenId); + .apeCoinStaking + .mainToBakc(vars.apeStakingPoolId, tokenId); if (isPaired) { vars._nftPairs[pairStakingCount] = ApeCoinStaking - .PairNftWithdrawWithAmount({ - mainTokenId: tokenId, - bakcTokenId: bakcTokenId.toUint32(), - amount: vars.bakcMatchedCap.toUint184(), - isUncommit: true - }); + .PairNftWithdrawWithAmount({ + mainTokenId: tokenId, + bakcTokenId: bakcTokenId.toUint32(), + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); pairStakingCount++; } } @@ -505,16 +504,16 @@ library ApeStakingSinglePoolLogic { if (pairStakingCount > 0) { ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs = vars - ._nftPairs; + ._nftPairs; assembly { mstore(_nftPairs, pairStakingCount) } vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); if (nft == vars.bayc) { vars.apeCoinStaking.withdrawBAKC(vars._nftPairs, _otherPairs); } else { @@ -545,29 +544,29 @@ library ApeStakingSinglePoolLogic { ) internal { uint256 arrayLength = tokenIds.length; ApeCoinStaking.PairNftWithdrawWithAmount[] - memory baycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - arrayLength - ); + memory baycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); ApeCoinStaking.PairNftWithdrawWithAmount[] - memory maycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - arrayLength - ); + memory maycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); uint256 baycPairCount; uint256 maycPairCount; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; (uint256 mainTokenId, bool isPaired) = vars - .apeCoinStaking - .bakcToMain(tokenId, BAYC_POOL_ID); + .apeCoinStaking + .bakcToMain(tokenId, BAYC_POOL_ID); if (isPaired) { baycPair[baycPairCount] = ApeCoinStaking - .PairNftWithdrawWithAmount({ - mainTokenId: mainTokenId.toUint32(), - bakcTokenId: tokenId, - amount: vars.bakcMatchedCap.toUint184(), - isUncommit: true - }); + .PairNftWithdrawWithAmount({ + mainTokenId: mainTokenId.toUint32(), + bakcTokenId: tokenId, + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); baycPairCount++; continue; } @@ -578,12 +577,12 @@ library ApeStakingSinglePoolLogic { ); if (isPaired) { maycPair[maycPairCount] = ApeCoinStaking - .PairNftWithdrawWithAmount({ - mainTokenId: mainTokenId.toUint32(), - bakcTokenId: tokenId, - amount: vars.bakcMatchedCap.toUint184(), - isUncommit: true - }); + .PairNftWithdrawWithAmount({ + mainTokenId: mainTokenId.toUint32(), + bakcTokenId: tokenId, + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true + }); maycPairCount++; continue; } @@ -597,9 +596,9 @@ library ApeStakingSinglePoolLogic { } ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); uint256 totalRepay = 0; if (baycPairCount > 0) { @@ -655,10 +654,10 @@ library ApeStakingSinglePoolLogic { //just need to check ape ntoken owner { address nToken = (nft == vars.bayc) - ? vars.nBayc - : (nft == vars.mayc) - ? vars.nMayc - : vars.nBakc; + ? vars.nBayc + : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; address nTokenOwner = IERC721(nToken).ownerOf(tokenId); if (claimFor == address(0)) { claimFor = nTokenOwner; @@ -672,9 +671,9 @@ library ApeStakingSinglePoolLogic { //update reward, to save gas we don't claim pending reward in ApeCoinStaking. rewardShares += (vars.accumulatedRewardsPerNft - - poolState.tokenStatus[tokenId].rewardsDebt); + poolState.tokenStatus[tokenId].rewardsDebt); poolState.tokenStatus[tokenId].rewardsDebt = vars - .accumulatedRewardsPerNft; + .accumulatedRewardsPerNft; //emit event emit NFTClaimed(nft, tokenId); @@ -695,17 +694,18 @@ library ApeStakingSinglePoolLogic { WadRayMath.RAY ); uint256 latestBorrowIndex = IPool(vars.pool) - .getReserveNormalizedVariableDebt(vars.cApe); + .getReserveNormalizedVariableDebt(vars.cApe); uint256 cApeDebtShare = bakcPoolState.cApeDebtShare; uint128 apeTotalPosition = apePoolState.totalPosition; uint128 bakcTotalPosition = bakcPoolState.totalPosition; - uint256 debtInterest = ApeStakingCommonLogic.calculateCurrentPositionDebtInterest( - cApeDebtShare, - bakcTotalPosition, - vars.bakcMatchedCap, - cApeExchangeRate, - latestBorrowIndex - ); + uint256 debtInterest = ApeStakingCommonLogic + .calculateCurrentPositionDebtInterest( + cApeDebtShare, + bakcTotalPosition, + vars.bakcMatchedCap, + cApeExchangeRate, + latestBorrowIndex + ); if (debtInterest >= totalAmount) { cApeDebtShare -= totalAmount.rayDiv(latestBorrowIndex).rayDiv( cApeExchangeRate @@ -727,17 +727,16 @@ library ApeStakingSinglePoolLogic { if (apeTotalPosition != 0) { apePoolState.accumulatedRewardsPerNft += - apeShareAmount.toUint128() / - apeTotalPosition; + apeShareAmount.toUint128() / + apeTotalPosition; } if (bakcTotalPosition != 0) { bakcPoolState.accumulatedRewardsPerNft += - (shareAmount - apeShareAmount).toUint128() / - bakcTotalPosition; + (shareAmount - apeShareAmount).toUint128() / + bakcTotalPosition; } bakcPoolState.cApeDebtShare = cApeDebtShare; return debtInterest; } } - } diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index 5e4fd3bfb..208ef4a1c 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -30,8 +30,21 @@ interface IApeStakingVault { struct VaultStorage { mapping(uint256 => PoolState) poolStates; - address vaultBot; - uint256 baycPairStakingRewardRatio; - uint256 maycPairStakingRewardRatio; + uint128 baycPairStakingRewardRatio; + uint128 maycPairStakingRewardRatio; } + + /** + * @dev Emitted during setSinglePoolApeRewardRatio() + * @param oldRatio The value of the old baycPairStakingRewardRatio + * @param newRatio The value of the new baycPairStakingRewardRatio + **/ + event BaycPairStakingRewardRatioUpdated(uint128 oldRatio, uint128 newRatio); + + /** + * @dev Emitted during setSinglePoolApeRewardRatio() + * @param oldRatio The value of the old maycPairStakingRewardRatio + * @param newRatio The value of the new maycPairStakingRewardRatio + **/ + event MaycPairStakingRewardRatioUpdated(uint128 oldRatio, uint128 newRatio); } diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index 49d41496b..61d9f325d 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -36,4 +36,13 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { uint128 stakingPair; uint256 apeRewardRatio; } + + /** + * @dev Emitted during setApeStakingBot() + * @param oldBot The address of the old compound bot + * @param newBot The address of the new compound bot + **/ + event ApeStakingBotUpdated(address oldBot, address newBot); + + event CompoundFeeUpdated(uint64 oldValue, uint64 newValue); } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index ac6c849f0..44cd6ba3f 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -299,7 +299,8 @@ import { ApeStakingVaultLogic__factory, ApeStakingPairPoolLogic__factory, ApeStakingPairPoolLogic, - ApeStakingSinglePoolLogic__factory, ApeStakingSinglePoolLogic, + ApeStakingSinglePoolLogic__factory, + ApeStakingSinglePoolLogic, } from "../types"; import {MockContract} from "ethereum-waffle"; import { @@ -2733,12 +2734,12 @@ export const deployApeStakingPairPoolLogic = async (verify?: boolean) => ) as Promise; export const deployApeStakingSinglePoolLogic = async (verify?: boolean) => - withSaveAndVerify( - new ApeStakingSinglePoolLogic__factory(await getFirstSigner()), - eContractid.ApeStakingSinglePoolLogic, - [], - verify - ) as Promise; + withSaveAndVerify( + new ApeStakingSinglePoolLogic__factory(await getFirstSigner()), + eContractid.ApeStakingSinglePoolLogic, + [], + verify + ) as Promise; export const deployParaApeStakingLibraries = async ( verify?: boolean @@ -2751,9 +2752,9 @@ export const deployParaApeStakingLibraries = async ( ["contracts/apestaking/logic/ApeStakingP2PLogic.sol:ApeStakingP2PLogic"]: p2pLogic.address, ["contracts/apestaking/logic/ApeStakingPairPoolLogic.sol:ApeStakingPairPoolLogic"]: - pairPoolLogic.address, + pairPoolLogic.address, ["contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol:ApeStakingSinglePoolLogic"]: - singlePoolLogic.address, + singlePoolLogic.address, }; }; From 9774dcf7787345da57e3497685fd85eac1f9e89f Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 7 Jul 2023 16:14:22 +0800 Subject: [PATCH 45/99] chore: compound fee --- contracts/apestaking/ParaApeStaking.sol | 44 ++++- .../logic/ApeStakingCommonLogic.sol | 26 +-- .../logic/ApeStakingPairPoolLogic.sol | 45 +++-- .../logic/ApeStakingSinglePoolLogic.sol | 171 ++++++++++-------- contracts/interfaces/IParaApeStaking.sol | 5 +- test/para_ape_staking.spec.ts | 40 +++- 6 files changed, 209 insertions(+), 122 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 4f1d60170..434f0488f 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -170,6 +170,10 @@ contract ParaApeStaking is } } + function claimCompoundFee(address receiver) external onlyApeStakingBot { + this.claimCApeReward(receiver); + } + /** * @notice Pauses the contract. Only pool admin or emergency admin can call this function **/ @@ -378,22 +382,27 @@ contract ParaApeStaking is ); } - function withdrawPairNFT( + function compoundPairNFT( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused { + ) external whenNotPaused onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; - ApeStakingPairPoolLogic.withdrawPairNFT( + ApeStakingPairPoolLogic.compoundPairNFT( vaultStorage.poolStates[poolId], vars, isBAYC, apeTokenIds, bakcTokenIds ); + + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } // to save gas we don't claim pending reward in ApeCoinStaking. @@ -415,22 +424,27 @@ contract ParaApeStaking is ); } - function compoundPairNFT( + function withdrawPairNFT( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused onlyApeStakingBot { + ) external whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; - ApeStakingPairPoolLogic.compoundPairNFT( + ApeStakingPairPoolLogic.withdrawPairNFT( vaultStorage.poolStates[poolId], vars, isBAYC, apeTokenIds, bakcTokenIds ); + + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } function depositNFT(address nft, uint32[] calldata tokenIds) @@ -493,10 +507,11 @@ contract ParaApeStaking is function compoundApe(address nft, uint32[] calldata tokenIds) external whenNotPaused - onlyApeStakingBot + onlyApeStakingBot { require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; uint256 poolId = (nft == bayc) ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; @@ -506,6 +521,10 @@ contract ParaApeStaking is nft, tokenIds ); + + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } function compoundBAKC( @@ -515,6 +534,7 @@ contract ParaApeStaking is ) external whenNotPaused onlyApeStakingBot { require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; ApeStakingSinglePoolLogic.compoundBAKC( vaultStorage, vars, @@ -522,6 +542,10 @@ contract ParaApeStaking is apeTokenIds, bakcTokenIds ); + + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } function claimNFT(address nft, uint32[] calldata tokenIds) @@ -549,12 +573,17 @@ contract ParaApeStaking is { require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; ApeStakingSinglePoolLogic.withdrawNFT( vaultStorage, vars, nft, tokenIds ); + + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } function _createCacheVars() @@ -573,7 +602,6 @@ contract ParaApeStaking is vars.apeCoin = apeCoin; vars.cApe = cApe; vars.apeCoinStaking = apeCoinStaking; - vars.compoundFee = compoundFee; vars.baycMatchedCap = baycMatchedCap; vars.maycMatchedCap = maycMatchedCap; vars.bakcMatchedCap = bakcMatchedCap; diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 282d5d119..8be5aaba6 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -36,9 +36,8 @@ library ApeStakingCommonLogic { function calculateRepayAndCompound( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - uint256 totalAmount, uint256 positionCap - ) internal returns (uint256) { + ) internal returns (uint256, uint256) { uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( WadRayMath.RAY ); @@ -53,28 +52,33 @@ library ApeStakingCommonLogic { cApeExchangeRate, latestBorrowIndex ); - if (debtInterest >= totalAmount) { - cApeDebtShare -= totalAmount.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate - ); + if (debtInterest >= vars.totalClaimedApe) { + cApeDebtShare -= vars + .totalClaimedApe + .rayDiv(latestBorrowIndex) + .rayDiv(cApeExchangeRate); poolState.cApeDebtShare = cApeDebtShare; - return totalAmount; + return (vars.totalClaimedApe, 0); } else { //repay debt cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( cApeExchangeRate ); + uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) + .rayDiv(cApeExchangeRate); + uint256 compoundFee = shareRewardAmount.percentMul( + vars.compoundFee + ); + shareRewardAmount = shareRewardAmount - compoundFee; //update reward index if (currentTotalPosition != 0) { - uint256 remainingReward = totalAmount - debtInterest; - uint256 shareAmount = remainingReward.rayDiv(cApeExchangeRate); poolState.accumulatedRewardsPerNft += - shareAmount.toUint128() / + shareRewardAmount.toUint128() / currentTotalPosition; } poolState.cApeDebtShare = cApeDebtShare; - return debtInterest; + return (debtInterest, compoundFee); } } diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index e411b5244..6a57bf9e7 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -313,20 +313,26 @@ library ApeStakingPairPoolLogic { vars.apeCoinStaking.withdrawBAKC(_otherPairs, vars._nftPairs); } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); - uint256 totalRepay = ApeStakingCommonLogic + (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic .calculateRepayAndCompound( poolState, vars, - balanceDiff, vars.positionCap + vars.bakcMatchedCap ); - if (totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); - IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + if (vars.totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); + IPool(vars.pool).repay( + vars.cApe, + vars.totalRepay, + address(this) + ); } } @@ -423,21 +429,24 @@ library ApeStakingPairPoolLogic { } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); //repay and compound vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; - uint256 totalRepay = ApeStakingCommonLogic.calculateRepayAndCompound( - poolState, - vars, - balanceDiff, - vars.positionCap + vars.bakcMatchedCap - ); + (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic + .calculateRepayAndCompound( + poolState, + vars, + vars.positionCap + vars.bakcMatchedCap + ); - if (totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); - IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + if (vars.totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); + IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } } diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 1f1ae4884..5fdfc937e 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -252,20 +252,19 @@ library ApeStakingSinglePoolLogic { vars.positionCap = vars.maycMatchedCap; } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); //repay and compound - uint256 totalRepay = ApeStakingCommonLogic.calculateRepayAndCompound( - poolState, - vars, - balanceDiff, - vars.positionCap - ); + (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic + .calculateRepayAndCompound(poolState, vars, vars.positionCap); - if (totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); - IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + if (vars.totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); + IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } } @@ -293,7 +292,6 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage bakcPoolState = vaultStorage .poolStates[BAKC_SINGLE_POOL_ID]; - uint256 totalReward; { ApeCoinStaking.PairNft[] memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); @@ -332,21 +330,22 @@ library ApeStakingSinglePoolLogic { } } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - totalReward = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), totalReward); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); } //repay and compound - uint256 totalRepay = _calculateRepayAndCompoundBAKC( - apePoolState, - bakcPoolState, - vars, - totalReward - ); - - if (totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); - IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + ( + vars.totalRepay, + vars.totalCompoundFee + ) = _calculateRepayAndCompoundBAKC(apePoolState, bakcPoolState, vars); + + if (vars.totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); + IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } } @@ -477,7 +476,6 @@ library ApeStakingSinglePoolLogic { } } - uint256 totalRepay = 0; if (singleStakingCount > 0) { ApeCoinStaking.SingleNft[] memory _nfts = vars._nfts; assembly { @@ -491,15 +489,18 @@ library ApeStakingSinglePoolLogic { vars.apeCoinStaking.withdrawMAYC(vars._nfts, address(this)); } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - - totalRepay += ApeStakingCommonLogic.calculateRepayAndCompound( - apePoolState, - vars, - balanceDiff, - vars.positionCap + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe ); + + (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic + .calculateRepayAndCompound( + apePoolState, + vars, + vars.positionCap + ); } if (pairStakingCount > 0) { @@ -520,20 +521,27 @@ library ApeStakingSinglePoolLogic { vars.apeCoinStaking.withdrawBAKC(_otherPairs, vars._nftPairs); } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); - - totalRepay += _calculateRepayAndCompoundBAKC( - apePoolState, - bakcPoolState, - vars, - balanceDiff + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe ); + + ( + uint256 bakcTotalRepay, + uint256 bakcCompoundFee + ) = _calculateRepayAndCompoundBAKC( + apePoolState, + bakcPoolState, + vars + ); + vars.totalRepay += bakcTotalRepay; + vars.totalCompoundFee += bakcCompoundFee; } - if (totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); - IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + if (vars.totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); + IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } } @@ -600,41 +608,52 @@ library ApeStakingSinglePoolLogic { 0 ); - uint256 totalRepay = 0; if (baycPairCount > 0) { vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); vars.apeCoinStaking.withdrawBAKC(baycPair, _otherPairs); vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; - totalRepay += _calculateRepayAndCompoundBAKC( + ( + vars.totalRepay, + vars.totalCompoundFee + ) = _calculateRepayAndCompoundBAKC( vaultStorage.poolStates[BAYC_SINGLE_POOL_ID], vaultStorage.poolStates[BAKC_SINGLE_POOL_ID], - vars, - balanceDiff + vars ); } if (maycPairCount > 0) { vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); vars.apeCoinStaking.withdrawBAKC(baycPair, maycPair); vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - uint256 balanceDiff = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit(address(this), balanceDiff); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; - totalRepay += _calculateRepayAndCompoundBAKC( - vaultStorage.poolStates[MAYC_SINGLE_POOL_ID], - vaultStorage.poolStates[BAKC_SINGLE_POOL_ID], - vars, - balanceDiff - ); + ( + uint256 maycTotalRepay, + uint256 maycCompoundFee + ) = _calculateRepayAndCompoundBAKC( + vaultStorage.poolStates[MAYC_SINGLE_POOL_ID], + vaultStorage.poolStates[BAKC_SINGLE_POOL_ID], + vars + ); + vars.totalRepay += maycTotalRepay; + vars.totalCompoundFee += maycCompoundFee; } - if (totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, totalRepay); - IPool(vars.pool).repay(vars.cApe, totalRepay, address(this)); + if (vars.totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); + IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } } @@ -687,9 +706,8 @@ library ApeStakingSinglePoolLogic { function _calculateRepayAndCompoundBAKC( IParaApeStaking.PoolState storage apePoolState, IParaApeStaking.PoolState storage bakcPoolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - uint256 totalAmount - ) internal returns (uint256) { + IParaApeStaking.ApeStakingVaultCacheVars memory vars + ) internal returns (uint256, uint256) { uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( WadRayMath.RAY ); @@ -706,12 +724,13 @@ library ApeStakingSinglePoolLogic { cApeExchangeRate, latestBorrowIndex ); - if (debtInterest >= totalAmount) { - cApeDebtShare -= totalAmount.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate - ); + if (debtInterest >= vars.totalClaimedApe) { + cApeDebtShare -= vars + .totalClaimedApe + .rayDiv(latestBorrowIndex) + .rayDiv(cApeExchangeRate); bakcPoolState.cApeDebtShare = cApeDebtShare; - return totalAmount; + return (vars.totalClaimedApe, 0); } else { //repay debt cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( @@ -719,9 +738,13 @@ library ApeStakingSinglePoolLogic { ); //update reward index - uint256 remainingReward = totalAmount - debtInterest; - uint256 shareAmount = remainingReward.rayDiv(cApeExchangeRate); - uint256 apeShareAmount = shareAmount.percentMul( + uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) + .rayDiv(cApeExchangeRate); + uint256 compoundFee = shareRewardAmount.percentMul( + vars.compoundFee + ); + shareRewardAmount = shareRewardAmount - compoundFee; + uint256 apeShareAmount = shareRewardAmount.percentMul( vars.apeRewardRatio ); @@ -732,11 +755,11 @@ library ApeStakingSinglePoolLogic { } if (bakcTotalPosition != 0) { bakcPoolState.accumulatedRewardsPerNft += - (shareAmount - apeShareAmount).toUint128() / + (shareRewardAmount - apeShareAmount).toUint128() / bakcTotalPosition; } bakcPoolState.cApeDebtShare = cApeDebtShare; - return debtInterest; + return (debtInterest, compoundFee); } } } diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index 61d9f325d..949a6b675 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -18,11 +18,11 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { address apeCoin; address cApe; ApeCoinStaking apeCoinStaking; - uint256 compoundFee; uint256 baycMatchedCap; uint256 maycMatchedCap; uint256 bakcMatchedCap; //optional + uint256 compoundFee; bytes32 DOMAIN_SEPARATOR; address apeToken; address nApe; @@ -31,10 +31,13 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { uint128 accumulatedRewardsPerNft; uint256 balanceBefore; uint256 balanceAfter; + uint256 totalClaimedApe; ApeCoinStaking.SingleNft[] _nfts; ApeCoinStaking.PairNftWithdrawWithAmount[] _nftPairs; uint128 stakingPair; uint256 apeRewardRatio; + uint256 totalRepay; + uint256 totalCompoundFee; } /** diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 3815ecd4b..53b622d6d 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -59,6 +59,12 @@ describe("Para Ape Staking Test", () => { .upgradeTo(paraApeStakingImpl.address, GLOBAL_OVERRIDES) ); + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .setApeStakingBot(user4.address) + ); + cApe = await getAutoCompoundApe(); MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); @@ -91,16 +97,16 @@ describe("Para Ape Staking Test", () => { expect( await configurator .connect(poolAdmin.signer) - .setSupplyCap(cApe.address, "200000000") + .setSupplyCap(cApe.address, "20000000000") ); - await mintAndValidate(ape, "100000000", user4); + await mintAndValidate(ape, "10000000000", user4); await waitForTx( await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) ); await waitForTx( await cApe .connect(user4.signer) - .deposit(user4.address, parseEther("100000000")) + .deposit(user4.address, parseEther("10000000000")) ); await waitForTx( await cApe.connect(user4.signer).approve(pool.address, MAX_UINT_AMOUNT) @@ -108,7 +114,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await pool .connect(user4.signer) - .supply(cApe.address, parseEther("100000000"), user4.address, 0) + .supply(cApe.address, parseEther("10000000000"), user4.address, 0) ); return testEnv; @@ -116,14 +122,19 @@ describe("Para Ape Staking Test", () => { it("test BAYC + BAKC pool logic", async () => { const { - users: [user1, user2, user3], + users: [user1, user2, , user4], bayc, bakc, nBAYC, nBAKC, + poolAdmin, apeCoinStaking, } = await loadFixture(fixture); + await waitForTx( + await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(1000) + ); + await supplyAndValidate(bayc, "3", user1, true); await supplyAndValidate(bakc, "3", user1, true); @@ -155,7 +166,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking - .connect(user3.signer) + .connect(user4.signer) .stakingPairNFT(true, [0, 1, 2], [0, 1, 2]) ); expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq( @@ -184,10 +195,13 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking - .connect(user3.signer) + .connect(user4.signer) .compoundPairNFT(true, [0, 1, 2], [0, 1, 2]) ); + const compoundFee = await paraApeStaking.pendingCApeReward(paraApeStaking.address); + expect(compoundFee).to.be.closeTo(parseEther("1080"), parseEther("10")); + await waitForTx( await paraApeStaking .connect(user1.signer) @@ -214,11 +228,17 @@ describe("Para Ape Staking Test", () => { expect(await bakc.ownerOf(0)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + + await waitForTx( + await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) + ); + const compoundFeeBalance = await cApe.balanceOf(user4.address); + expect(compoundFeeBalance).to.be.equal(compoundFee); }); it("test MAYC + BAKC pool logic", async () => { const { - users: [user1, user2, user3], + users: [user1, user2, , user4], mayc, bakc, nMAYC, @@ -257,7 +277,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking - .connect(user3.signer) + .connect(user4.signer) .stakingPairNFT(false, [0, 1, 2], [0, 1, 2]) ); expect((await apeCoinStaking.nftPosition(2, 0)).stakedAmount).to.be.eq( @@ -286,7 +306,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking - .connect(user3.signer) + .connect(user4.signer) .compoundPairNFT(false, [0, 1, 2], [0, 1, 2]) ); From ad7601910b8f77c8ca767cb5653d6948716ba681 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Sat, 8 Jul 2023 10:00:55 +0800 Subject: [PATCH 46/99] chore: fix compound fee --- contracts/apestaking/ParaApeStaking.sol | 26 ++----- .../logic/ApeStakingCommonLogic.sol | 27 +++++-- .../logic/ApeStakingPairPoolLogic.sol | 15 +++- .../logic/ApeStakingSinglePoolLogic.sol | 77 ++++++++++++++---- contracts/interfaces/IApeStakingVault.sol | 4 +- contracts/interfaces/IParaApeStaking.sol | 2 +- test/para_ape_staking.spec.ts | 78 +++++++++++++++++-- 7 files changed, 179 insertions(+), 50 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 434f0488f..a2d94a237 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -17,6 +17,7 @@ import "./logic/ApeStakingPairPoolLogic.sol"; import "./logic/ApeStakingSinglePoolLogic.sol"; import "./logic/ApeStakingCommonLogic.sol"; import "../protocol/libraries/helpers/Errors.sol"; +import "hardhat/console.sol"; contract ParaApeStaking is Initializable, @@ -394,15 +395,12 @@ contract ParaApeStaking is : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingPairPoolLogic.compoundPairNFT( vaultStorage.poolStates[poolId], + cApeShareBalance, vars, isBAYC, apeTokenIds, bakcTokenIds ); - - if (vars.totalCompoundFee > 0) { - cApeShareBalance[address(this)] += vars.totalCompoundFee; - } } // to save gas we don't claim pending reward in ApeCoinStaking. @@ -436,15 +434,12 @@ contract ParaApeStaking is : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingPairPoolLogic.withdrawPairNFT( vaultStorage.poolStates[poolId], + cApeShareBalance, vars, isBAYC, apeTokenIds, bakcTokenIds ); - - if (vars.totalCompoundFee > 0) { - cApeShareBalance[address(this)] += vars.totalCompoundFee; - } } function depositNFT(address nft, uint32[] calldata tokenIds) @@ -517,14 +512,11 @@ contract ParaApeStaking is : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; ApeStakingSinglePoolLogic.compoundApe( vaultStorage.poolStates[poolId], + cApeShareBalance, vars, nft, tokenIds ); - - if (vars.totalCompoundFee > 0) { - cApeShareBalance[address(this)] += vars.totalCompoundFee; - } } function compoundBAKC( @@ -537,15 +529,12 @@ contract ParaApeStaking is vars.compoundFee = compoundFee; ApeStakingSinglePoolLogic.compoundBAKC( vaultStorage, + cApeShareBalance, vars, nft, apeTokenIds, bakcTokenIds ); - - if (vars.totalCompoundFee > 0) { - cApeShareBalance[address(this)] += vars.totalCompoundFee; - } } function claimNFT(address nft, uint32[] calldata tokenIds) @@ -576,14 +565,11 @@ contract ParaApeStaking is vars.compoundFee = compoundFee; ApeStakingSinglePoolLogic.withdrawNFT( vaultStorage, + cApeShareBalance, vars, nft, tokenIds ); - - if (vars.totalCompoundFee > 0) { - cApeShareBalance[address(this)] += vars.totalCompoundFee; - } } function _createCacheVars() diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 8be5aaba6..efaea365e 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -11,6 +11,7 @@ import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import {IPool} from "../../interfaces/IPool.sol"; +import "hardhat/console.sol"; /** * @title ApeStakingVaultLogic library @@ -44,14 +45,21 @@ library ApeStakingCommonLogic { uint256 latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); uint256 cApeDebtShare = poolState.cApeDebtShare; - uint128 currentTotalPosition = poolState.totalPosition; uint256 debtInterest = calculateCurrentPositionDebtInterest( cApeDebtShare, - currentTotalPosition, + poolState.stakingPosition, positionCap, cApeExchangeRate, latestBorrowIndex ); + console.log( + "calculateRepayAndCompound-----------vars.totalClaimedApe:", + vars.totalClaimedApe + ); + console.log( + "calculateRepayAndCompound-----------debtInterest:", + debtInterest + ); if (debtInterest >= vars.totalClaimedApe) { cApeDebtShare -= vars .totalClaimedApe @@ -64,20 +72,29 @@ library ApeStakingCommonLogic { cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( cApeExchangeRate ); + poolState.cApeDebtShare = cApeDebtShare; uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) .rayDiv(cApeExchangeRate); uint256 compoundFee = shareRewardAmount.percentMul( vars.compoundFee ); + console.log( + "calculateRepayAndCompound-----------vars.compoundFee:", + vars.compoundFee + ); + console.log( + "calculateRepayAndCompound-----------compoundFee:", + compoundFee + ); shareRewardAmount = shareRewardAmount - compoundFee; //update reward index + uint128 currentTotalPosition = poolState.totalPosition; if (currentTotalPosition != 0) { poolState.accumulatedRewardsPerNft += shareRewardAmount.toUint128() / currentTotalPosition; } - poolState.cApeDebtShare = cApeDebtShare; return (debtInterest, compoundFee); } } @@ -101,7 +118,7 @@ library ApeStakingCommonLogic { function calculateCurrentPositionDebtInterest( uint256 cApeDebtShare, - uint256 totalPosition, + uint256 currentStakingPosition, uint256 perPositionCap, uint256 cApeExchangeRate, uint256 latestBorrowIndex @@ -109,6 +126,6 @@ library ApeStakingCommonLogic { uint256 currentDebt = cApeDebtShare.rayMul(cApeExchangeRate).rayMul( latestBorrowIndex ); - return (currentDebt - perPositionCap * totalPosition); + return (currentDebt - perPositionCap * currentStakingPosition); } } diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 6a57bf9e7..8bbddbeac 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -133,7 +133,7 @@ library ApeStakingPairPoolLogic { emit PairNFTDeposited(isBAYC, apeTokenId, bakcTokenId); } - poolState.totalPosition += arrayLength.toUint128(); + poolState.totalPosition += arrayLength.toUint64(); } function stakingPairNFT( @@ -203,10 +203,13 @@ library ApeStakingPairPoolLogic { vars.apeCoinStaking.depositMAYC(_nfts); vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); } + + poolState.stakingPosition += arrayLength.toUint64(); } function withdrawPairNFT( IParaApeStaking.PoolState storage poolState, + mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, @@ -283,7 +286,8 @@ library ApeStakingPairPoolLogic { } //update state - poolState.totalPosition -= arrayLength.toUint128(); + poolState.totalPosition -= arrayLength.toUint64(); + poolState.stakingPosition -= vars.stakingPair; //withdraw from ApeCoinStaking and compound if (vars.stakingPair > 0) { @@ -334,6 +338,9 @@ library ApeStakingPairPoolLogic { address(this) ); } + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } //transfer ape and BAKC back to nToken @@ -375,6 +382,7 @@ library ApeStakingPairPoolLogic { function compoundPairNFT( IParaApeStaking.PoolState storage poolState, + mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, @@ -448,6 +456,9 @@ library ApeStakingPairPoolLogic { IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } function _claimPairNFT( diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 5fdfc937e..4c31d67c3 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -108,7 +108,7 @@ library ApeStakingSinglePoolLogic { emit NFTDeposited(nft, tokenId); } - poolState.totalPosition += arrayLength.toUint128(); + poolState.totalPosition += arrayLength.toUint64(); } function stakingApe( @@ -153,6 +153,8 @@ library ApeStakingSinglePoolLogic { } else { vars.apeCoinStaking.depositMAYC(_nfts); } + + poolState.stakingPosition += arrayLength.toUint64(); } function stakingBAKC( @@ -215,10 +217,13 @@ library ApeStakingSinglePoolLogic { } else { vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); } + + bakcPoolState.stakingPosition += arrayLength.toUint64(); } function compoundApe( IParaApeStaking.PoolState storage poolState, + mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds @@ -266,10 +271,14 @@ library ApeStakingSinglePoolLogic { IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } function compoundBAKC( IParaApeStaking.VaultStorage storage vaultStorage, + mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata apeTokenIds, @@ -347,6 +356,9 @@ library ApeStakingSinglePoolLogic { IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } function claimNFT( @@ -363,10 +375,12 @@ library ApeStakingSinglePoolLogic { function withdrawNFT( IParaApeStaking.VaultStorage storage vaultStorage, + mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds ) external { + console.log("withdrawNFT-----------------------"); uint256 arrayLength = tokenIds.length; require(arrayLength > 0, "wrong param"); @@ -387,12 +401,12 @@ library ApeStakingSinglePoolLogic { _claimNFT(curPoolState, vars, nft, tokenIds); //update state - curPoolState.totalPosition -= arrayLength.toUint128(); + curPoolState.totalPosition -= arrayLength.toUint64(); if (nft == vars.bayc || nft == vars.mayc) { - _unstakeApe(vaultStorage, vars, nft, tokenIds); + _unstakeApe(vaultStorage, cApeShareBalance, vars, nft, tokenIds); } else { - _unstakeBAKC(vaultStorage, vars, tokenIds); + _unstakeBAKC(vaultStorage, cApeShareBalance, vars, tokenIds); } //transfer nft back to nToken @@ -414,6 +428,7 @@ library ApeStakingSinglePoolLogic { function _unstakeApe( IParaApeStaking.VaultStorage storage vaultStorage, + mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds @@ -438,8 +453,8 @@ library ApeStakingSinglePoolLogic { vars._nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength ); - uint256 singleStakingCount; - uint256 pairStakingCount; + uint64 singleStakingCount; + uint64 pairStakingCount; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -476,6 +491,9 @@ library ApeStakingSinglePoolLogic { } } + apePoolState.stakingPosition -= singleStakingCount; + bakcPoolState.stakingPosition -= pairStakingCount; + if (singleStakingCount > 0) { ApeCoinStaking.SingleNft[] memory _nfts = vars._nfts; assembly { @@ -543,10 +561,14 @@ library ApeStakingSinglePoolLogic { IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } function _unstakeBAKC( IParaApeStaking.VaultStorage storage vaultStorage, + mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint32[] calldata tokenIds ) internal { @@ -559,8 +581,8 @@ library ApeStakingSinglePoolLogic { memory maycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength ); - uint256 baycPairCount; - uint256 maycPairCount; + uint64 baycPairCount; + uint64 maycPairCount; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -609,6 +631,10 @@ library ApeStakingSinglePoolLogic { ); if (baycPairCount > 0) { + IParaApeStaking.PoolState storage bakcPoolState = vaultStorage + .poolStates[BAKC_SINGLE_POOL_ID]; + bakcPoolState.stakingPosition -= baycPairCount; + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); vars.apeCoinStaking.withdrawBAKC(baycPair, _otherPairs); vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); @@ -624,11 +650,15 @@ library ApeStakingSinglePoolLogic { vars.totalCompoundFee ) = _calculateRepayAndCompoundBAKC( vaultStorage.poolStates[BAYC_SINGLE_POOL_ID], - vaultStorage.poolStates[BAKC_SINGLE_POOL_ID], + bakcPoolState, vars ); } if (maycPairCount > 0) { + IParaApeStaking.PoolState storage bakcPoolState = vaultStorage + .poolStates[BAKC_SINGLE_POOL_ID]; + bakcPoolState.stakingPosition -= maycPairCount; + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); vars.apeCoinStaking.withdrawBAKC(baycPair, maycPair); vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); @@ -644,7 +674,7 @@ library ApeStakingSinglePoolLogic { uint256 maycCompoundFee ) = _calculateRepayAndCompoundBAKC( vaultStorage.poolStates[MAYC_SINGLE_POOL_ID], - vaultStorage.poolStates[BAKC_SINGLE_POOL_ID], + bakcPoolState, vars ); vars.totalRepay += maycTotalRepay; @@ -655,6 +685,9 @@ library ApeStakingSinglePoolLogic { IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } function _claimNFT( @@ -714,12 +747,10 @@ library ApeStakingSinglePoolLogic { uint256 latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); uint256 cApeDebtShare = bakcPoolState.cApeDebtShare; - uint128 apeTotalPosition = apePoolState.totalPosition; - uint128 bakcTotalPosition = bakcPoolState.totalPosition; uint256 debtInterest = ApeStakingCommonLogic .calculateCurrentPositionDebtInterest( cApeDebtShare, - bakcTotalPosition, + bakcPoolState.stakingPosition, vars.bakcMatchedCap, cApeExchangeRate, latestBorrowIndex @@ -736,7 +767,16 @@ library ApeStakingSinglePoolLogic { cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( cApeExchangeRate ); + bakcPoolState.cApeDebtShare = cApeDebtShare; + console.log( + "_calculateRepayAndCompoundBAKC------------vars.totalClaimedApe:", + vars.totalClaimedApe + ); + console.log( + "_calculateRepayAndCompoundBAKC------------debtInterest:", + debtInterest + ); //update reward index uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) .rayDiv(cApeExchangeRate); @@ -748,17 +788,26 @@ library ApeStakingSinglePoolLogic { vars.apeRewardRatio ); + uint128 apeTotalPosition = apePoolState.totalPosition; if (apeTotalPosition != 0) { apePoolState.accumulatedRewardsPerNft += apeShareAmount.toUint128() / apeTotalPosition; } + uint128 bakcTotalPosition = bakcPoolState.totalPosition; if (bakcTotalPosition != 0) { bakcPoolState.accumulatedRewardsPerNft += (shareRewardAmount - apeShareAmount).toUint128() / bakcTotalPosition; } - bakcPoolState.cApeDebtShare = cApeDebtShare; + console.log( + "_calculateRepayAndCompoundBAKC------------vars.compoundFee:", + vars.compoundFee + ); + console.log( + "_calculateRepayAndCompoundBAKC------------compoundFee:", + compoundFee + ); return (debtInterest, compoundFee); } } diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index 208ef4a1c..e5743cb0e 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -19,7 +19,9 @@ interface IApeStakingVault { // accumulated cApe reward for per NFT position uint128 accumulatedRewardsPerNft; // total NFT position count - uint128 totalPosition; + uint64 totalPosition; + // total staking position + uint64 stakingPosition; //tokenId => reward debt position mapping(uint256 => TokenStatus) tokenStatus; //for pair pool, apeTokenId => PairingStatus diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index 949a6b675..c86dd30cd 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -34,7 +34,7 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { uint256 totalClaimedApe; ApeCoinStaking.SingleNft[] _nfts; ApeCoinStaking.PairNftWithdrawWithAmount[] _nftPairs; - uint128 stakingPair; + uint64 stakingPair; uint256 apeRewardRatio; uint256 totalRepay; uint256 totalCompoundFee; diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 53b622d6d..dd6e73ed0 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -127,12 +127,12 @@ describe("Para Ape Staking Test", () => { bakc, nBAYC, nBAKC, - poolAdmin, + poolAdmin, apeCoinStaking, } = await loadFixture(fixture); await waitForTx( - await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(1000) + await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(1000) ); await supplyAndValidate(bayc, "3", user1, true); @@ -198,9 +198,10 @@ describe("Para Ape Staking Test", () => { .connect(user4.signer) .compoundPairNFT(true, [0, 1, 2], [0, 1, 2]) ); - - const compoundFee = await paraApeStaking.pendingCApeReward(paraApeStaking.address); - expect(compoundFee).to.be.closeTo(parseEther("1080"), parseEther("10")); + let compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("10")); await waitForTx( await paraApeStaking @@ -214,6 +215,8 @@ describe("Para Ape Staking Test", () => { const user2Balance = await cApe.balanceOf(user2.address); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); + await advanceTimeAndBlock(parseInt("3600")); + await waitForTx( await paraApeStaking .connect(user1.signer) @@ -229,11 +232,20 @@ describe("Para Ape Staking Test", () => { expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("1440"), parseEther("20")); + await waitForTx( - await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) + await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) ); const compoundFeeBalance = await cApe.balanceOf(user4.address); - expect(compoundFeeBalance).to.be.equal(compoundFee); + expect(compoundFeeBalance).to.be.closeTo(compoundFee, parseEther("1")); + + expect( + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + ).to.be.equal(0); }); it("test MAYC + BAKC pool logic", async () => { @@ -244,8 +256,13 @@ describe("Para Ape Staking Test", () => { nMAYC, nBAKC, apeCoinStaking, + poolAdmin, } = await loadFixture(fixture); + await waitForTx( + await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(1000) + ); + await supplyAndValidate(mayc, "3", user1, true); await supplyAndValidate(bakc, "3", user1, true); @@ -309,6 +326,10 @@ describe("Para Ape Staking Test", () => { .connect(user4.signer) .compoundPairNFT(false, [0, 1, 2], [0, 1, 2]) ); + let compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("10")); await waitForTx( await paraApeStaking @@ -322,6 +343,8 @@ describe("Para Ape Staking Test", () => { const user2Balance = await cApe.balanceOf(user2.address); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); + await advanceTimeAndBlock(parseInt("3600")); + await waitForTx( await paraApeStaking .connect(user1.signer) @@ -338,6 +361,21 @@ describe("Para Ape Staking Test", () => { expect(await bakc.ownerOf(0)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + + compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("1440"), parseEther("20")); + + await waitForTx( + await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) + ); + const compoundFeeBalance = await cApe.balanceOf(user4.address); + expect(compoundFeeBalance).to.be.closeTo(compoundFee, parseEther("1")); + + expect( + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + ).to.be.equal(0); }); it("test single pool logic", async () => { @@ -350,8 +388,13 @@ describe("Para Ape Staking Test", () => { nMAYC, nBAKC, apeCoinStaking, + poolAdmin, } = await loadFixture(fixture); + await waitForTx( + await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(1000) + ); + await supplyAndValidate(bayc, "3", user1, true); await supplyAndValidate(mayc, "3", user2, true); await supplyAndValidate(bakc, "3", user3, true); @@ -454,6 +497,10 @@ describe("Para Ape Staking Test", () => { .connect(user4.signer) .compoundBAKC(mayc.address, [2], [2]) ); + let compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("1080"), parseEther("10")); await waitForTx( await paraApeStaking @@ -477,6 +524,8 @@ describe("Para Ape Staking Test", () => { expect(user1Balance).to.be.closeTo(user2Balance, parseEther("100")); expect(user1Balance).to.be.closeTo(user3Balance, parseEther("100")); + await advanceTimeAndBlock(parseInt("3600")); + await waitForTx( await paraApeStaking .connect(user1.signer) @@ -501,6 +550,21 @@ describe("Para Ape Staking Test", () => { expect(await bakc.ownerOf(0)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + + compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("2160"), parseEther("20")); + + await waitForTx( + await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) + ); + const compoundFeeBalance = await cApe.balanceOf(user4.address); + expect(compoundFeeBalance).to.be.closeTo(compoundFee, parseEther("1")); + + expect( + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + ).to.be.equal(0); }); it("stakingPairNFT cannot stake single pool nft", async () => {}); From dec78f613be5a5c51ae7cef797db6bd855bbcb15 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 10 Jul 2023 14:40:26 +0800 Subject: [PATCH 47/99] chore: refactor and add test case --- contracts/apestaking/ParaApeStaking.sol | 43 +- .../logic/ApeStakingCommonLogic.sol | 17 - .../logic/ApeStakingPairPoolLogic.sol | 132 ++-- .../logic/ApeStakingSinglePoolLogic.sol | 249 ++++---- contracts/interfaces/IParaApeStaking.sol | 6 +- .../protocol/libraries/helpers/Errors.sol | 10 + helpers/types.ts | 9 + test/para_ape_staking.spec.ts | 599 ++++++++++++++++-- 8 files changed, 791 insertions(+), 274 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index a2d94a237..a96f02c9d 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -17,7 +17,6 @@ import "./logic/ApeStakingPairPoolLogic.sol"; import "./logic/ApeStakingSinglePoolLogic.sol"; import "./logic/ApeStakingCommonLogic.sol"; import "../protocol/libraries/helpers/Errors.sol"; -import "hardhat/console.sol"; contract ParaApeStaking is Initializable, @@ -136,7 +135,7 @@ contract ParaApeStaking is } modifier onlyApeStakingBot() { - require(apeStakingBot == msg.sender, "not ape staking bot"); + require(apeStakingBot == msg.sender, Errors.NOT_APE_STAKING_BOT); _; } @@ -446,7 +445,10 @@ contract ParaApeStaking is external whenNotPaused { - require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); + require( + nft == bayc || nft == mayc || nft == bakc, + Errors.NFT_NOT_ALLOWED + ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID @@ -461,30 +463,29 @@ contract ParaApeStaking is ); } - function stakingApe(address nft, uint32[] calldata tokenIds) + function stakingApe(bool isBAYC, uint32[] calldata tokenIds) external whenNotPaused { - require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = (nft == bayc) + uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; ApeStakingSinglePoolLogic.stakingApe( vaultStorage.poolStates[poolId], vars, - nft, + isBAYC, tokenIds ); } function stakingBAKC( - address nft, + bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = (nft == bayc) + uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; ApeStakingSinglePoolLogic.stakingBAKC( @@ -493,45 +494,43 @@ contract ParaApeStaking is ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID ], vars, - nft, + isBAYC, apeTokenIds, bakcTokenIds ); } - function compoundApe(address nft, uint32[] calldata tokenIds) + function compoundApe(bool isBAYC, uint32[] calldata tokenIds) external whenNotPaused onlyApeStakingBot { - require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; - uint256 poolId = (nft == bayc) + uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; ApeStakingSinglePoolLogic.compoundApe( vaultStorage.poolStates[poolId], cApeShareBalance, vars, - nft, + isBAYC, tokenIds ); } function compoundBAKC( - address nft, + bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external whenNotPaused onlyApeStakingBot { - require(nft == bayc || nft == mayc, "wrong nft"); ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; ApeStakingSinglePoolLogic.compoundBAKC( vaultStorage, cApeShareBalance, vars, - nft, + isBAYC, apeTokenIds, bakcTokenIds ); @@ -541,7 +540,10 @@ contract ParaApeStaking is external whenNotPaused { - require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); + require( + nft == bayc || nft == mayc || nft == bakc, + Errors.NFT_NOT_ALLOWED + ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID @@ -560,7 +562,10 @@ contract ParaApeStaking is external whenNotPaused { - require(nft == bayc || nft == mayc || nft == bakc, "wrong nft"); + require( + nft == bayc || nft == mayc || nft == bakc, + Errors.NFT_NOT_ALLOWED + ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; ApeStakingSinglePoolLogic.withdrawNFT( diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index efaea365e..de62d22da 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -11,7 +11,6 @@ import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import {IPool} from "../../interfaces/IPool.sol"; -import "hardhat/console.sol"; /** * @title ApeStakingVaultLogic library @@ -52,14 +51,6 @@ library ApeStakingCommonLogic { cApeExchangeRate, latestBorrowIndex ); - console.log( - "calculateRepayAndCompound-----------vars.totalClaimedApe:", - vars.totalClaimedApe - ); - console.log( - "calculateRepayAndCompound-----------debtInterest:", - debtInterest - ); if (debtInterest >= vars.totalClaimedApe) { cApeDebtShare -= vars .totalClaimedApe @@ -79,14 +70,6 @@ library ApeStakingCommonLogic { uint256 compoundFee = shareRewardAmount.percentMul( vars.compoundFee ); - console.log( - "calculateRepayAndCompound-----------vars.compoundFee:", - vars.compoundFee - ); - console.log( - "calculateRepayAndCompound-----------compoundFee:", - compoundFee - ); shareRewardAmount = shareRewardAmount - compoundFee; //update reward index uint128 currentTotalPosition = poolState.totalPosition; diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 8bbddbeac..f99d0132c 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -8,10 +8,10 @@ import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; import "../../interfaces/IAutoCompoundApe.sol"; import "../../interfaces/ICApe.sol"; -import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import "./ApeStakingCommonLogic.sol"; +import "../../protocol/libraries/helpers/Errors.sol"; /** * @title ApeStakingPairPoolLogic library @@ -58,7 +58,7 @@ library ApeStakingPairPoolLogic { uint256 arrayLength = apeTokenIds.length; require( arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" + Errors.INVALID_PARAMETER ); if (isBAYC) { @@ -82,7 +82,7 @@ library ApeStakingPairPoolLogic { address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); require( msgSender == nApeOwner && msgSender == nBakcOwner, - "not owner" + Errors.NOT_THE_OWNER ); } @@ -92,17 +92,17 @@ library ApeStakingPairPoolLogic { vars.apeStakingPoolId, apeTokenId ); - require(stakedAmount == 0, "ape already staked"); + require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); (stakedAmount, ) = vars.apeCoinStaking.nftPosition( BAKC_POOL_ID, bakcTokenId ); - require(stakedAmount == 0, "bakc already staked"); + require(stakedAmount == 0, Errors.BAKC_POSITION_EXISTED); (, bool isPaired) = vars.apeCoinStaking.mainToBakc( vars.apeStakingPoolId, apeTokenId ); - require(!isPaired, "ape already pair staked"); + require(!isPaired, Errors.PAIR_POSITION_EXISTED); } //update pair status @@ -146,7 +146,7 @@ library ApeStakingPairPoolLogic { uint256 arrayLength = apeTokenIds.length; require( arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" + Errors.INVALID_PARAMETER ); ApeCoinStaking.SingleNft[] @@ -167,7 +167,7 @@ library ApeStakingPairPoolLogic { require( localPairStatus.tokenId == bakcTokenId && localPairStatus.isPaired, - "wrong pair status" + Errors.NOT_PAIRED_APE_AND_BAKC ); } @@ -218,7 +218,7 @@ library ApeStakingPairPoolLogic { uint256 arrayLength = apeTokenIds.length; require( arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" + Errors.INVALID_PARAMETER ); _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); @@ -234,19 +234,18 @@ library ApeStakingPairPoolLogic { vars.nApe = vars.nMayc; vars.positionCap = vars.maycMatchedCap; } - vars._nfts = new ApeCoinStaking.SingleNft[](arrayLength); - vars._nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - arrayLength - ); + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); + uint64 stakingPair = 0; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; - //check pair status - require( - poolState.pairStatus[apeTokenId].tokenId == bakcTokenId, - "wrong ape and bakc pair" - ); + // we don't need check pair status again here //check ntoken owner { @@ -255,7 +254,7 @@ library ApeStakingPairPoolLogic { address msgSender = msg.sender; require( msgSender == nApeOwner || msgSender == nBakcOwner, - "not owner" + Errors.NOT_THE_OWNER ); } @@ -264,38 +263,34 @@ library ApeStakingPairPoolLogic { delete poolState.tokenStatus[apeTokenId]; // we only need to check pair staking position - (, bool isPaired) = vars.apeCoinStaking.mainToBakc( + (, vars.isPaired) = vars.apeCoinStaking.mainToBakc( vars.apeStakingPoolId, apeTokenId ); - if (isPaired) { - vars._nfts[vars.stakingPair] = ApeCoinStaking.SingleNft({ + if (vars.isPaired) { + _nfts[stakingPair] = ApeCoinStaking.SingleNft({ tokenId: apeTokenId, amount: vars.positionCap.toUint224() }); - vars._nftPairs[vars.stakingPair] = ApeCoinStaking + _nftPairs[stakingPair] = ApeCoinStaking .PairNftWithdrawWithAmount({ mainTokenId: apeTokenId, bakcTokenId: bakcTokenId, amount: vars.bakcMatchedCap.toUint184(), isUncommit: true }); - vars.stakingPair++; + stakingPair++; } } //update state poolState.totalPosition -= arrayLength.toUint64(); - poolState.stakingPosition -= vars.stakingPair; + poolState.stakingPosition -= stakingPair; //withdraw from ApeCoinStaking and compound - if (vars.stakingPair > 0) { + if (stakingPair > 0) { { - ApeCoinStaking.SingleNft[] memory _nfts = vars._nfts; - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _nftPairs = vars._nftPairs; - uint256 stakingPair = vars.stakingPair; assembly { mstore(_nfts, stakingPair) } @@ -310,36 +305,38 @@ library ApeStakingPairPoolLogic { 0 ); if (isBAYC) { - vars.apeCoinStaking.withdrawBAYC(vars._nfts, address(this)); - vars.apeCoinStaking.withdrawBAKC(vars._nftPairs, _otherPairs); + vars.apeCoinStaking.withdrawBAYC(_nfts, address(this)); + vars.apeCoinStaking.withdrawBAKC(_nftPairs, _otherPairs); } else { - vars.apeCoinStaking.withdrawMAYC(vars._nfts, address(this)); - vars.apeCoinStaking.withdrawBAKC(_otherPairs, vars._nftPairs); + vars.apeCoinStaking.withdrawMAYC(_nfts, address(this)); + vars.apeCoinStaking.withdrawBAKC(_otherPairs, _nftPairs); } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); - - (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic - .calculateRepayAndCompound( - poolState, - vars, - vars.positionCap + vars.bakcMatchedCap + if (vars.totalClaimedApe > 0) { + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe ); - if (vars.totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); - IPool(vars.pool).repay( - vars.cApe, - vars.totalRepay, - address(this) - ); - } - if (vars.totalCompoundFee > 0) { - cApeShareBalance[address(this)] += vars.totalCompoundFee; + (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic + .calculateRepayAndCompound( + poolState, + vars, + vars.positionCap + vars.bakcMatchedCap + ); + + if (vars.totalRepay > 0) { + IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); + IPool(vars.pool).repay( + vars.cApe, + vars.totalRepay, + address(this) + ); + } + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } } @@ -374,7 +371,7 @@ library ApeStakingPairPoolLogic { uint256 arrayLength = apeTokenIds.length; require( arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" + Errors.INVALID_PARAMETER ); _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); @@ -391,7 +388,7 @@ library ApeStakingPairPoolLogic { uint256 arrayLength = apeTokenIds.length; require( arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" + Errors.INVALID_PARAMETER ); uint256[] memory _nfts = new uint256[](arrayLength); @@ -407,7 +404,7 @@ library ApeStakingPairPoolLogic { require( localPairStatus.tokenId == bakcTokenId && localPairStatus.isPaired, - "wrong pair status" + Errors.NOT_PAIRED_APE_AND_BAKC ); // construct staking data @@ -422,7 +419,6 @@ library ApeStakingPairPoolLogic { } vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - //claim from ApeCoinStaking { ApeCoinStaking.PairNft[] @@ -435,7 +431,6 @@ library ApeStakingPairPoolLogic { vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); } } - vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; IAutoCompoundApe(vars.cApe).deposit( @@ -472,26 +467,31 @@ library ApeStakingPairPoolLogic { address claimFor; uint256 arrayLength = apeTokenIds.length; uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + address nApe = isBAYC ? vars.nBayc : vars.nMayc; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; //just need to check ape ntoken owner { - address nApe = isBAYC ? vars.nBayc : vars.nMayc; address nApeOwner = IERC721(nApe).ownerOf(apeTokenId); if (claimFor == address(0)) { claimFor = nApeOwner; } else { - require(nApeOwner == claimFor, "claim not for same owner"); + require(nApeOwner == claimFor, Errors.NOT_THE_SAME_OWNER); } } - //check pair status - require( - poolState.pairStatus[apeTokenId].tokenId == bakcTokenId, - "wrong ape and bakc pair" - ); + // check pair status + { + IApeStakingVault.PairingStatus + memory localPairStatus = poolState.pairStatus[apeTokenId]; + require( + localPairStatus.tokenId == bakcTokenId && + localPairStatus.isPaired, + Errors.NOT_PAIRED_APE_AND_BAKC + ); + } //update reward, to save gas we don't claim pending reward in ApeCoinStaking. rewardShares += (accumulatedRewardsPerNft - diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 4c31d67c3..ddcee0c63 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -8,10 +8,10 @@ import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; import "../../interfaces/IAutoCompoundApe.sol"; import "../../interfaces/ICApe.sol"; -import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import "./ApeStakingCommonLogic.sol"; +import "../../protocol/libraries/helpers/Errors.sol"; /** * @title ApeStakingSinglePoolLogic library @@ -35,9 +35,10 @@ library ApeStakingSinglePoolLogic { uint256 constant BAKC_POOL_ID = 3; event NFTDeposited(address nft, uint256 tokenId); - event NFTStaked(address nft, uint256 tokenId); - event NFTPairStaked(address nft, uint256 apeTokenId, uint256 bakcTokenId); - event NFTCompounded(address nft, uint256 tokenId); + event ApeStaked(bool isBAYC, uint256 tokenId); + event BakcStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event ApeCompounded(bool isBAYC, uint256 tokenId); + event BakcCompounded(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); event NFTClaimed(address nft, uint256 tokenId); event NFTWithdrawn(address nft, uint256 tokenId); @@ -48,22 +49,22 @@ library ApeStakingSinglePoolLogic { uint32[] calldata tokenIds ) external { uint256 arrayLength = tokenIds.length; - require(arrayLength > 0, "wrong param"); + require(arrayLength > 0, Errors.INVALID_PARAMETER); address msgSender = msg.sender; - vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; if (nft == vars.bakc) { address nTokenOwner = IERC721(vars.nBakc).ownerOf(tokenId); - require(msgSender == nTokenOwner, "not owner"); + require(msgSender == nTokenOwner, Errors.NOT_THE_OWNER); (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( BAKC_POOL_ID, tokenId ); - require(stakedAmount == 0, "bakc already staked"); + require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); IERC721(nft).safeTransferFrom( vars.nBakc, @@ -71,25 +72,28 @@ library ApeStakingSinglePoolLogic { tokenId ); } else { - vars.nApe = (nft == vars.bayc) ? vars.nBayc : vars.nMayc; - vars.apeStakingPoolId = (nft == vars.bayc) - ? BAYC_POOL_ID - : MAYC_POOL_ID; + if (nft == vars.bayc) { + vars.nApe = vars.nBayc; + vars.apeStakingPoolId = BAYC_POOL_ID; + } else { + vars.nApe = vars.nMayc; + vars.apeStakingPoolId = MAYC_POOL_ID; + } address nApeOwner = IERC721(vars.nApe).ownerOf(tokenId); - require(msgSender == nApeOwner, "not ape owner"); + require(msgSender == nApeOwner, Errors.NOT_THE_OWNER); (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( vars.apeStakingPoolId, tokenId ); - require(stakedAmount == 0, "ape already staked"); + require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); (, bool isPaired) = vars.apeCoinStaking.mainToBakc( vars.apeStakingPoolId, tokenId ); - require(!isPaired, "ape already pair staked"); + require(!isPaired, Errors.PAIR_POSITION_EXISTED); IERC721(nft).safeTransferFrom( vars.nApe, @@ -100,7 +104,7 @@ library ApeStakingSinglePoolLogic { //update token status poolState.tokenStatus[tokenId] = IApeStakingVault.TokenStatus({ - rewardsDebt: vars.accumulatedRewardsPerNft, + rewardsDebt: accumulatedRewardsPerNft, isInPool: true }); @@ -114,23 +118,21 @@ library ApeStakingSinglePoolLogic { function stakingApe( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft, + bool isBAYC, uint32[] calldata tokenIds ) external { uint256 arrayLength = tokenIds.length; - require(arrayLength > 0, "wrong param"); + require(arrayLength > 0, Errors.INVALID_PARAMETER); ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); - vars.positionCap = (nft == vars.bayc) - ? vars.baycMatchedCap - : vars.maycMatchedCap; + vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; require( poolState.tokenStatus[tokenId].isInPool, - "ape not in single pool" + Errors.NFT_NOT_IN_SINGLE_POOL ); // construct staking data @@ -140,7 +142,7 @@ library ApeStakingSinglePoolLogic { }); //emit event - emit NFTStaked(nft, tokenId); + emit ApeStaked(isBAYC, tokenId); } // prepare Ape coin @@ -148,7 +150,7 @@ library ApeStakingSinglePoolLogic { ApeStakingCommonLogic.borrowCApeFromPool(poolState, vars, totalBorrow); //stake in ApeCoinStaking - if (nft == vars.bayc) { + if (isBAYC) { vars.apeCoinStaking.depositBAYC(_nfts); } else { vars.apeCoinStaking.depositMAYC(_nfts); @@ -161,14 +163,14 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage apePoolState, IParaApeStaking.PoolState storage bakcPoolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft, + bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external { uint256 arrayLength = apeTokenIds.length; require( arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" + Errors.INVALID_PARAMETER ); ApeCoinStaking.PairNftDepositWithAmount[] @@ -181,11 +183,11 @@ library ApeStakingSinglePoolLogic { require( apePoolState.tokenStatus[apeTokenId].isInPool, - "ape not in single pool" + Errors.NFT_NOT_IN_SINGLE_POOL ); require( bakcPoolState.tokenStatus[bakcTokenId].isInPool, - "ape not in single pool" + Errors.NFT_NOT_IN_SINGLE_POOL ); // construct staking data @@ -196,7 +198,7 @@ library ApeStakingSinglePoolLogic { }); //emit event - emit NFTPairStaked(nft, apeTokenId, bakcTokenId); + emit BakcStaked(isBAYC, apeTokenId, bakcTokenId); } // prepare Ape coin @@ -212,7 +214,7 @@ library ApeStakingSinglePoolLogic { memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( 0 ); - if (nft == vars.bayc) { + if (isBAYC) { vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); } else { vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); @@ -225,11 +227,11 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage poolState, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft, + bool isBAYC, uint32[] calldata tokenIds ) external { uint256 arrayLength = tokenIds.length; - require(arrayLength > 0, "wrong param"); + require(arrayLength > 0, Errors.INVALID_PARAMETER); uint256[] memory _nfts = new uint256[](arrayLength); for (uint256 index = 0; index < arrayLength; index++) { @@ -237,19 +239,19 @@ library ApeStakingSinglePoolLogic { require( poolState.tokenStatus[tokenId].isInPool, - "ape not in single pool" + Errors.NFT_NOT_IN_SINGLE_POOL ); // construct staking data _nfts[index] = tokenId; //emit event - emit NFTCompounded(nft, tokenId); + emit ApeCompounded(isBAYC, tokenId); } vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); //claim from ApeCoinStaking - if (nft == vars.bayc) { + if (isBAYC) { vars.apeCoinStaking.claimSelfBAYC(_nfts); vars.positionCap = vars.baycMatchedCap; } else { @@ -280,18 +282,18 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.VaultStorage storage vaultStorage, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft, + bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external { uint256 arrayLength = apeTokenIds.length; require( arrayLength == bakcTokenIds.length && arrayLength > 0, - "wrong param" + Errors.INVALID_PARAMETER ); IParaApeStaking.PoolState storage apePoolState; - if (nft == vars.bayc) { + if (isBAYC) { apePoolState = vaultStorage.poolStates[BAYC_SINGLE_POOL_ID]; vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; } else { @@ -310,11 +312,11 @@ library ApeStakingSinglePoolLogic { require( apePoolState.tokenStatus[apeTokenId].isInPool, - "ape not in single pool" + Errors.NFT_NOT_IN_SINGLE_POOL ); require( bakcPoolState.tokenStatus[bakcTokenId].isInPool, - "ape not in single pool" + Errors.NFT_NOT_IN_SINGLE_POOL ); // construct staking data @@ -324,19 +326,17 @@ library ApeStakingSinglePoolLogic { }); //emit event - emit NFTPairStaked(nft, apeTokenId, bakcTokenId); + emit BakcCompounded(isBAYC, apeTokenId, bakcTokenId); } vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); //claim from ApeCoinStaking - { - ApeCoinStaking.PairNft[] - memory _otherPairs = new ApeCoinStaking.PairNft[](0); - if (nft == vars.bayc) { - vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); - } else { - vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); - } + ApeCoinStaking.PairNft[] + memory _otherPairs = new ApeCoinStaking.PairNft[](0); + if (isBAYC) { + vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; @@ -368,7 +368,7 @@ library ApeStakingSinglePoolLogic { uint32[] calldata tokenIds ) external { uint256 arrayLength = tokenIds.length; - require(arrayLength > 0, "wrong param"); + require(arrayLength > 0, Errors.INVALID_PARAMETER); _claimNFT(poolState, vars, nft, tokenIds); } @@ -380,9 +380,8 @@ library ApeStakingSinglePoolLogic { address nft, uint32[] calldata tokenIds ) external { - console.log("withdrawNFT-----------------------"); uint256 arrayLength = tokenIds.length; - require(arrayLength > 0, "wrong param"); + require(arrayLength > 0, Errors.INVALID_PARAMETER); IParaApeStaking.PoolState storage curPoolState; address nToken; @@ -404,7 +403,13 @@ library ApeStakingSinglePoolLogic { curPoolState.totalPosition -= arrayLength.toUint64(); if (nft == vars.bayc || nft == vars.mayc) { - _unstakeApe(vaultStorage, cApeShareBalance, vars, nft, tokenIds); + _unstakeApe( + vaultStorage, + cApeShareBalance, + vars, + (nft == vars.bayc), + tokenIds + ); } else { _unstakeBAKC(vaultStorage, cApeShareBalance, vars, tokenIds); } @@ -415,7 +420,7 @@ library ApeStakingSinglePoolLogic { uint32 tokenId = tokenIds[index]; address nTokenOwner = IERC721(nToken).ownerOf(tokenId); - require(msgSender == nTokenOwner, "not owner"); + require(msgSender == nTokenOwner, Errors.NOT_THE_OWNER); delete curPoolState.tokenStatus[tokenId]; @@ -430,15 +435,11 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.VaultStorage storage vaultStorage, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft, + bool isBAYC, uint32[] calldata tokenIds ) internal { - uint256 arrayLength = tokenIds.length; - IParaApeStaking.PoolState storage apePoolState; - IParaApeStaking.PoolState storage bakcPoolState = vaultStorage - .poolStates[BAKC_SINGLE_POOL_ID]; - if (nft == vars.bayc) { + if (isBAYC) { vars.apeStakingPoolId = BAYC_POOL_ID; vars.positionCap = vars.baycMatchedCap; apePoolState = vaultStorage.poolStates[BAYC_SINGLE_POOL_ID]; @@ -449,13 +450,15 @@ library ApeStakingSinglePoolLogic { apePoolState = vaultStorage.poolStates[MAYC_SINGLE_POOL_ID]; vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; } - vars._nfts = new ApeCoinStaking.SingleNft[](arrayLength); - vars._nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - arrayLength - ); + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](tokenIds.length); + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + tokenIds.length + ); uint64 singleStakingCount; uint64 pairStakingCount; - for (uint256 index = 0; index < arrayLength; index++) { + for (uint256 index = 0; index < tokenIds.length; index++) { uint32 tokenId = tokenIds[index]; //check ape position @@ -465,7 +468,7 @@ library ApeStakingSinglePoolLogic { tokenId ); if (stakedAmount > 0) { - vars._nfts[singleStakingCount] = ApeCoinStaking.SingleNft({ + _nfts[singleStakingCount] = ApeCoinStaking.SingleNft({ tokenId: tokenId, amount: vars.positionCap.toUint224() }); @@ -479,7 +482,7 @@ library ApeStakingSinglePoolLogic { .apeCoinStaking .mainToBakc(vars.apeStakingPoolId, tokenId); if (isPaired) { - vars._nftPairs[pairStakingCount] = ApeCoinStaking + _nftPairs[pairStakingCount] = ApeCoinStaking .PairNftWithdrawWithAmount({ mainTokenId: tokenId, bakcTokenId: bakcTokenId.toUint32(), @@ -491,39 +494,40 @@ library ApeStakingSinglePoolLogic { } } + IParaApeStaking.PoolState storage bakcPoolState = vaultStorage + .poolStates[BAKC_SINGLE_POOL_ID]; apePoolState.stakingPosition -= singleStakingCount; bakcPoolState.stakingPosition -= pairStakingCount; if (singleStakingCount > 0) { - ApeCoinStaking.SingleNft[] memory _nfts = vars._nfts; assembly { mstore(_nfts, singleStakingCount) } vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - if (nft == vars.bayc) { - vars.apeCoinStaking.withdrawBAYC(vars._nfts, address(this)); + if (isBAYC) { + vars.apeCoinStaking.withdrawBAYC(_nfts, address(this)); } else { - vars.apeCoinStaking.withdrawMAYC(vars._nfts, address(this)); + vars.apeCoinStaking.withdrawMAYC(_nfts, address(this)); } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); - - (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic - .calculateRepayAndCompound( - apePoolState, - vars, - vars.positionCap + if (vars.totalClaimedApe > 0) { + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe ); + + (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic + .calculateRepayAndCompound( + apePoolState, + vars, + vars.positionCap + ); + } } if (pairStakingCount > 0) { - ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs = vars - ._nftPairs; assembly { mstore(_nftPairs, pairStakingCount) } @@ -533,28 +537,30 @@ library ApeStakingSinglePoolLogic { memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( 0 ); - if (nft == vars.bayc) { - vars.apeCoinStaking.withdrawBAKC(vars._nftPairs, _otherPairs); + if (isBAYC) { + vars.apeCoinStaking.withdrawBAKC(_nftPairs, _otherPairs); } else { - vars.apeCoinStaking.withdrawBAKC(_otherPairs, vars._nftPairs); + vars.apeCoinStaking.withdrawBAKC(_otherPairs, _nftPairs); } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); - - ( - uint256 bakcTotalRepay, - uint256 bakcCompoundFee - ) = _calculateRepayAndCompoundBAKC( - apePoolState, - bakcPoolState, - vars + if (vars.totalClaimedApe > 0) { + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe ); - vars.totalRepay += bakcTotalRepay; - vars.totalCompoundFee += bakcCompoundFee; + + ( + uint256 bakcTotalRepay, + uint256 bakcCompoundFee + ) = _calculateRepayAndCompoundBAKC( + apePoolState, + bakcPoolState, + vars + ); + vars.totalRepay += bakcTotalRepay; + vars.totalCompoundFee += bakcCompoundFee; + } } if (vars.totalRepay > 0) { @@ -696,36 +702,37 @@ library ApeStakingSinglePoolLogic { address nft, uint32[] calldata tokenIds ) internal { - vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; uint256 rewardShares; address claimFor; uint256 arrayLength = tokenIds.length; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; //just need to check ape ntoken owner { - address nToken = (nft == vars.bayc) - ? vars.nBayc - : (nft == vars.mayc) - ? vars.nMayc - : vars.nBakc; address nTokenOwner = IERC721(nToken).ownerOf(tokenId); if (claimFor == address(0)) { claimFor = nTokenOwner; } else { - require( - nTokenOwner == claimFor, - "claim not for same owner" - ); + require(nTokenOwner == claimFor, Errors.NOT_THE_SAME_OWNER); } } + require( + poolState.tokenStatus[tokenId].isInPool, + Errors.NFT_NOT_IN_SINGLE_POOL + ); + //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - rewardShares += (vars.accumulatedRewardsPerNft - + rewardShares += (accumulatedRewardsPerNft - poolState.tokenStatus[tokenId].rewardsDebt); - poolState.tokenStatus[tokenId].rewardsDebt = vars - .accumulatedRewardsPerNft; + poolState + .tokenStatus[tokenId] + .rewardsDebt = accumulatedRewardsPerNft; //emit event emit NFTClaimed(nft, tokenId); @@ -769,14 +776,6 @@ library ApeStakingSinglePoolLogic { ); bakcPoolState.cApeDebtShare = cApeDebtShare; - console.log( - "_calculateRepayAndCompoundBAKC------------vars.totalClaimedApe:", - vars.totalClaimedApe - ); - console.log( - "_calculateRepayAndCompoundBAKC------------debtInterest:", - debtInterest - ); //update reward index uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) .rayDiv(cApeExchangeRate); @@ -800,14 +799,6 @@ library ApeStakingSinglePoolLogic { (shareRewardAmount - apeShareAmount).toUint128() / bakcTotalPosition; } - console.log( - "_calculateRepayAndCompoundBAKC------------vars.compoundFee:", - vars.compoundFee - ); - console.log( - "_calculateRepayAndCompoundBAKC------------compoundFee:", - compoundFee - ); return (debtInterest, compoundFee); } } diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index c86dd30cd..cff53991c 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -22,8 +22,8 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { uint256 maycMatchedCap; uint256 bakcMatchedCap; //optional - uint256 compoundFee; bytes32 DOMAIN_SEPARATOR; + uint256 compoundFee; address apeToken; address nApe; uint256 apeStakingPoolId; @@ -32,12 +32,10 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { uint256 balanceBefore; uint256 balanceAfter; uint256 totalClaimedApe; - ApeCoinStaking.SingleNft[] _nfts; - ApeCoinStaking.PairNftWithdrawWithAmount[] _nftPairs; - uint64 stakingPair; uint256 apeRewardRatio; uint256 totalRepay; uint256 totalCompoundFee; + bool isPaired; } /** diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index fa57c774b..c98610c8d 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -134,4 +134,14 @@ library Errors { string public constant INVALID_FEE_VALUE = "139"; // invalid fee rate value string public constant TOKEN_NOT_ALLOW_RESCUE = "140"; // token is not allow rescue string public constant CALLER_NOT_ALLOWED = "141"; //The caller of the function is not allowed + + string public constant INVALID_PARAMETER = "170"; //invalid parameter + string public constant APE_POSITION_EXISTED = "171"; //ape staking position already existed + string public constant BAKC_POSITION_EXISTED = "172"; //bakc staking position already existed + string public constant PAIR_POSITION_EXISTED = "173"; //pair staking position already existed + string public constant NOT_PAIRED_APE_AND_BAKC = "174"; //not paired ape and bakc + string public constant NOT_APE_STAKING_BOT = "175"; //not ape staking bot + string public constant NOT_THE_SAME_OWNER = "176"; //not the same owner + string public constant NFT_NOT_ALLOWED = "177"; //nft now allowed + string public constant NFT_NOT_IN_SINGLE_POOL = "178"; //nft not in single pool } diff --git a/helpers/types.ts b/helpers/types.ts index b3560d94c..2df460111 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -416,6 +416,15 @@ export enum ProtocolErrors { INVALID_TOKEN_ID = "135", //invalid token id + INVALID_PARAMETER = "170", //invalid parameter + APE_POSITION_EXISTED = "171", //ape staking position already existed + BAKC_POSITION_EXISTED = "172", //bakc staking position already existed + PAIR_POSITION_EXISTED = "173", //pair staking position already existed + NOT_PAIRED_APE_AND_BAKC = "174", //not paired ape and bakc + NOT_APE_STAKING_BOT = "175", //not ape staking bot + NOT_THE_SAME_OWNER = "176", //not the same owner + NFT_NOT_IN_SINGLE_POOL = "178", //nft not in single pool + // SafeCast SAFECAST_UINT128_OVERFLOW = "SafeCast: value doesn't fit in 128 bits", diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index dd6e73ed0..354d1b59d 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -1,30 +1,19 @@ import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; import {expect} from "chai"; -import { - AutoCompoundApe, - P2PPairStaking, - ParaApeStaking, - VariableDebtToken, -} from "../types"; +import {AutoCompoundApe, ParaApeStaking, VariableDebtToken} from "../types"; import {TestEnv} from "./helpers/make-suite"; import {testEnvFixture} from "./helpers/setup-env"; import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; import { getAutoCompoundApe, getInitializableAdminUpgradeabilityProxy, - getP2PPairStaking, getParaApeStaking, getVariableDebtToken, } from "../helpers/contracts-getters"; import {MAX_UINT_AMOUNT} from "../helpers/constants"; import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; -import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; import {parseEther} from "ethers/lib/utils"; -import {almostEqual} from "./helpers/uniswapv3-helper"; -import { - deployP2PPairStakingImpl, - deployParaApeStakingImpl, -} from "../helpers/contracts-deployments"; +import {deployParaApeStakingImpl} from "../helpers/contracts-deployments"; import {GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; import {ProtocolErrors} from "../helpers/types"; @@ -425,24 +414,18 @@ describe("Para Ape Staking Test", () => { expect(await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingApe(bayc.address, [0, 1, 2]) + await paraApeStaking.connect(user4.signer).stakingApe(true, [0, 1, 2]) ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingApe(mayc.address, [0, 1, 2]) + await paraApeStaking.connect(user4.signer).stakingApe(false, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user4.signer) - .stakingBAKC(bayc.address, [0, 1], [0, 1]) + .stakingBAKC(true, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingBAKC(mayc.address, [2], [2]) + await paraApeStaking.connect(user4.signer).stakingBAKC(false, [2], [2]) ); expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq( parseEther("200000") @@ -478,24 +461,18 @@ describe("Para Ape Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundApe(bayc.address, [0, 1, 2]) + await paraApeStaking.connect(user4.signer).compoundApe(true, [0, 1, 2]) ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundApe(mayc.address, [0, 1, 2]) + await paraApeStaking.connect(user4.signer).compoundApe(false, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user4.signer) - .compoundBAKC(bayc.address, [0, 1], [0, 1]) + .compoundBAKC(true, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundBAKC(mayc.address, [2], [2]) + await paraApeStaking.connect(user4.signer).compoundBAKC(false, [2], [2]) ); let compoundFee = await paraApeStaking.pendingCApeReward( paraApeStaking.address @@ -567,15 +544,559 @@ describe("Para Ape Staking Test", () => { ).to.be.equal(0); }); - it("stakingPairNFT cannot stake single pool nft", async () => {}); + it("depositPairNFT revert test", async () => { + const { + users: [user1, user2], + ape, + bayc, + bakc, + apeCoinStaking, + } = await loadFixture(fixture); - it("stakingApe cannot stake pair pool ape", async () => {}); + await mintAndValidate(bayc, "3", user1); + await mintAndValidate(bakc, "3", user1); + await mintAndValidate(ape, "1000000", user1); - it("stakingBAKC cannot stake pair pool nft", async () => {}); + await waitForTx( + await ape + .connect(user1.signer) + .approve(apeCoinStaking.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await apeCoinStaking + .connect(user1.signer) + .depositBAYC([{tokenId: 0, amount: parseEther("10")}]) + ); + await waitForTx( + await apeCoinStaking + .connect(user1.signer) + .depositBAKC( + [{mainTokenId: 1, bakcTokenId: 1, amount: parseEther("10")}], + [] + ) + ); + + await supplyAndValidate(bayc, "3", user1, false); + await supplyAndValidate(bakc, "3", user1, false); + + await expect( + paraApeStaking.connect(user2.signer).depositPairNFT(true, [0, 1], [0, 1]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).depositPairNFT(true, [0], [0]) + ).to.be.revertedWith(ProtocolErrors.APE_POSITION_EXISTED); + + await expect( + paraApeStaking.connect(user1.signer).depositPairNFT(true, [2], [1]) + ).to.be.revertedWith(ProtocolErrors.BAKC_POSITION_EXISTED); + + await expect( + paraApeStaking.connect(user1.signer).depositPairNFT(true, [1], [0]) + ).to.be.revertedWith(ProtocolErrors.PAIR_POSITION_EXISTED); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositPairNFT(true, [2], [2]) + ); + }); - it("compoundPairNFT cannot stake single pool nft", async () => {}); + it("stakingPairNFT revert test", async () => { + const { + users: [user1, , , user4], + bayc, + bakc, + } = await loadFixture(fixture); - it("compoundNFT cannot compound pair pool nft", async () => {}); + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); - it("compoundBAKC cannot compound pair pool nft", async () => {}); + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositPairNFT(true, [0, 1, 2], [0, 1, 2]) + ); + + await expect( + paraApeStaking.connect(user4.signer).stakingPairNFT(true, [1], [0]) + ).to.be.revertedWith(ProtocolErrors.NOT_PAIRED_APE_AND_BAKC); + }); + + it("compoundPairNFT revert test", async () => { + const { + users: [user1, , , user4], + bayc, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositPairNFT(true, [0, 1, 2], [0, 1, 2]) + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .compoundPairNFT(true, [0, 1, 2], [0, 1, 2]) + ).to.be.revertedWith(ProtocolErrors.NOT_APE_STAKING_BOT); + + await expect( + paraApeStaking.connect(user4.signer).compoundPairNFT(true, [1], [0]) + ).to.be.revertedWith(ProtocolErrors.NOT_PAIRED_APE_AND_BAKC); + }); + + it("claimPairNFT revert test", async () => { + const { + users: [user1, user2, , user4], + bayc, + bakc, + nBAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositPairNFT(true, [0, 1, 2], [0, 1, 2]) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .stakingPairNFT(true, [0, 1, 2], [0, 1, 2]) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundPairNFT(true, [0, 1, 2], [0, 1, 2]) + ); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .claimPairNFT(true, [0, 1, 2], [0, 1, 2]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).claimPairNFT(true, [1], [0]) + ).to.be.revertedWith(ProtocolErrors.NOT_PAIRED_APE_AND_BAKC); + }); + + it("withdrawPairNFT revert test", async () => { + const { + users: [user1, user2], + bayc, + bakc, + nBAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositPairNFT(true, [0, 1, 2], [0, 1, 2]) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .stakingPairNFT(true, [0, 1], [0, 1]) + ); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + + await expect( + paraApeStaking.connect(user1.signer).withdrawPairNFT(true, [0, 1], [1, 0]) + ).to.be.revertedWith(ProtocolErrors.NOT_PAIRED_APE_AND_BAKC); + + await expect( + paraApeStaking + .connect(user1.signer) + .withdrawPairNFT(true, [0, 1, 2], [0, 1, 2]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .withdrawPairNFT(true, [0, 1], [0, 1]) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawPairNFT(true, [2], [2]) + ); + }); + + it("depositNFT revert test", async () => { + const { + users: [user1, user2], + ape, + bayc, + bakc, + apeCoinStaking, + } = await loadFixture(fixture); + + await mintAndValidate(bayc, "3", user1); + await mintAndValidate(bakc, "3", user1); + await mintAndValidate(ape, "1000000", user1); + + await waitForTx( + await ape + .connect(user1.signer) + .approve(apeCoinStaking.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await apeCoinStaking + .connect(user1.signer) + .depositBAYC([{tokenId: 0, amount: parseEther("10")}]) + ); + await waitForTx( + await apeCoinStaking + .connect(user1.signer) + .depositBAKC( + [{mainTokenId: 1, bakcTokenId: 1, amount: parseEther("10")}], + [] + ) + ); + + await supplyAndValidate(bayc, "3", user1, false); + await supplyAndValidate(bakc, "3", user1, false); + + await expect( + paraApeStaking.connect(user2.signer).depositNFT(bayc.address, [0]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).depositNFT(bayc.address, [0]) + ).to.be.revertedWith(ProtocolErrors.APE_POSITION_EXISTED); + + await expect( + paraApeStaking.connect(user1.signer).depositNFT(bayc.address, [1]) + ).to.be.revertedWith(ProtocolErrors.PAIR_POSITION_EXISTED); + + await expect( + paraApeStaking.connect(user2.signer).depositNFT(bakc.address, [0]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).depositNFT(bakc.address, [1]) + ).to.be.revertedWith(ProtocolErrors.APE_POSITION_EXISTED); + }); + + it("stakingApe revert test", async () => { + const { + users: [user1, , , user4], + bayc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1]) + ); + + await expect( + paraApeStaking.connect(user4.signer).stakingApe(true, [2]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + + await waitForTx( + await paraApeStaking.connect(user1.signer).stakingApe(true, [0, 1]) + ); + }); + + it("stakingBAKC revert test", async () => { + const { + users: [user1, , , user4], + bayc, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bakc.address, [0, 1]) + ); + + await expect( + paraApeStaking.connect(user4.signer).stakingBAKC(true, [2], [0]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + + await expect( + paraApeStaking.connect(user4.signer).stakingBAKC(true, [0], [2]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .stakingBAKC(true, [0, 1], [0, 1]) + ); + }); + + it("compoundApe revert test", async () => { + const { + users: [user1, , , user4], + bayc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1]) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).stakingApe(true, [0, 1]) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await expect( + paraApeStaking.connect(user4.signer).compoundApe(true, [2]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundApe(true, [0, 1]) + ); + }); + + it("compoundBAKC revert test", async () => { + const { + users: [user1, , , user4], + bayc, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bakc.address, [0, 1]) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .stakingBAKC(true, [0, 1], [0, 1]) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await expect( + paraApeStaking.connect(user4.signer).compoundBAKC(true, [2], [1]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + + await expect( + paraApeStaking.connect(user4.signer).compoundBAKC(true, [1], [2]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundBAKC(true, [0, 1], [0, 1]) + ); + }); + + it("claimNFT revert test", async () => { + const { + users: [user1, user2, , user4], + bayc, + bakc, + nBAYC, + nBAKC, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bakc.address, [0, 1]) + ); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).stakingApe(true, [0, 1]) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .stakingBAKC(true, [0, 1], [0, 1]) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundApe(true, [0, 1]) + ); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundBAKC(true, [0, 1], [0, 1]) + ); + + await expect( + paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [0, 1]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [2]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + + await expect( + paraApeStaking.connect(user1.signer).claimNFT(bakc.address, [0, 1]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).claimNFT(bakc.address, [2]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [0]) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimNFT(bakc.address, [0]) + ); + + await waitForTx( + await paraApeStaking.connect(user2.signer).claimNFT(bayc.address, [1]) + ); + + await waitForTx( + await paraApeStaking.connect(user2.signer).claimNFT(bakc.address, [1]) + ); + }); + + it("withdrawNFT revert test", async () => { + const { + users: [user1, user2], + bayc, + bakc, + nBAYC, + nBAKC, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bakc.address, [0, 1]) + ); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).stakingApe(true, [0, 1]) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .stakingBAKC(true, [0, 1], [0, 1]) + ); + + await expect( + paraApeStaking.connect(user1.signer).withdrawNFT(bayc.address, [0, 1]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).withdrawNFT(bayc.address, [1]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).withdrawNFT(bakc.address, [0, 1]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).withdrawNFT(bakc.address, [1]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawNFT(bayc.address, [0]) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawNFT(bakc.address, [0]) + ); + + await waitForTx( + await paraApeStaking.connect(user2.signer).withdrawNFT(bayc.address, [1]) + ); + + await waitForTx( + await paraApeStaking.connect(user2.signer).withdrawNFT(bakc.address, [1]) + ); + }); }); From a47977ceac24f70f40273339c07326c077e0e281 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 10 Jul 2023 20:58:56 +0800 Subject: [PATCH 48/99] chore: add pending reward interface --- contracts/apestaking/ParaApeStaking.sol | 47 ++++++ .../logic/ApeStakingCommonLogic.sol | 2 + .../logic/ApeStakingPairPoolLogic.sol | 64 ++++++-- .../logic/ApeStakingSinglePoolLogic.sol | 57 +++++-- helpers/contracts-deployments.ts | 2 - test/para_ape_staking.spec.ts | 141 ++++++++++++++++-- 6 files changed, 273 insertions(+), 40 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index a96f02c9d..7ff72c92c 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -402,6 +402,27 @@ contract ParaApeStaking is ); } + function pairNFTPendingReward( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external view returns (uint256) { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + (, uint256 pendingReward, ) = ApeStakingPairPoolLogic + .calculatePendingReward( + vaultStorage.poolStates[poolId], + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + + return pendingReward; + } + // to save gas we don't claim pending reward in ApeCoinStaking. function claimPairNFT( bool isBAYC, @@ -536,6 +557,32 @@ contract ParaApeStaking is ); } + function nftPendingReward(address nft, uint32[] calldata tokenIds) + external + view + returns (uint256) + { + require( + nft == bayc || nft == mayc || nft == bakc, + Errors.NFT_NOT_ALLOWED + ); + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = (nft == bayc) + ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID + : (nft == mayc) + ? ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID + : ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID; + (, uint256 pendingReward, ) = ApeStakingSinglePoolLogic + .calculatePendingReward( + vaultStorage.poolStates[poolId], + vars, + nft, + tokenIds + ); + + return pendingReward; + } + function claimNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index de62d22da..e05a0dc65 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -77,6 +77,8 @@ library ApeStakingCommonLogic { poolState.accumulatedRewardsPerNft += shareRewardAmount.toUint128() / currentTotalPosition; + } else { + compoundFee += shareRewardAmount; } return (debtInterest, compoundFee); } diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index f99d0132c..644fced8d 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -456,21 +456,27 @@ library ApeStakingPairPoolLogic { } } - function _claimPairNFT( + function calculatePendingReward( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) internal { + ) + public + view + returns ( + address claimFor, + uint256 pendingReward, + uint128 accumulatedRewardsPerNft + ) + { uint256 rewardShares; - address claimFor; uint256 arrayLength = apeTokenIds.length; - uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; address nApe = isBAYC ? vars.nBayc : vars.nMayc; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; //just need to check ape ntoken owner { @@ -487,7 +493,7 @@ library ApeStakingPairPoolLogic { IApeStakingVault.PairingStatus memory localPairStatus = poolState.pairStatus[apeTokenId]; require( - localPairStatus.tokenId == bakcTokenId && + localPairStatus.tokenId == bakcTokenIds[index] && localPairStatus.isPaired, Errors.NOT_PAIRED_APE_AND_BAKC ); @@ -496,16 +502,46 @@ library ApeStakingPairPoolLogic { //update reward, to save gas we don't claim pending reward in ApeCoinStaking. rewardShares += (accumulatedRewardsPerNft - poolState.tokenStatus[apeTokenId].rewardsDebt); - poolState - .tokenStatus[apeTokenId] - .rewardsDebt = accumulatedRewardsPerNft; - - //emit event - emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); } + pendingReward = ICApe(vars.cApe).getPooledApeByShares(rewardShares); + + return (claimFor, pendingReward, accumulatedRewardsPerNft); + } + + function _claimPairNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) internal { + ( + address owner, + uint256 pendingReward, + uint128 accumulatedRewardsPerNft + ) = calculatePendingReward( + poolState, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + + if (pendingReward > 0) { + uint256 arrayLength = apeTokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + poolState + .tokenStatus[apeTokenId] + .rewardsDebt = accumulatedRewardsPerNft; + + //emit event + emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); + } - if (rewardShares > 0) { - IERC20(vars.cApe).safeTransfer(claimFor, rewardShares); + IERC20(vars.cApe).safeTransfer(owner, pendingReward); } } } diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index ddcee0c63..2f57ad921 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -696,16 +696,23 @@ library ApeStakingSinglePoolLogic { } } - function _claimNFT( + function calculatePendingReward( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds - ) internal { + ) + public + view + returns ( + address claimFor, + uint256 pendingReward, + uint128 accumulatedRewardsPerNft + ) + { uint256 rewardShares; - address claimFor; uint256 arrayLength = tokenIds.length; - uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) ? vars.nMayc : vars.nBakc; @@ -730,16 +737,38 @@ library ApeStakingSinglePoolLogic { //update reward, to save gas we don't claim pending reward in ApeCoinStaking. rewardShares += (accumulatedRewardsPerNft - poolState.tokenStatus[tokenId].rewardsDebt); - poolState - .tokenStatus[tokenId] - .rewardsDebt = accumulatedRewardsPerNft; - - //emit event - emit NFTClaimed(nft, tokenId); } + pendingReward = ICApe(vars.cApe).getPooledApeByShares(rewardShares); + + return (claimFor, pendingReward, accumulatedRewardsPerNft); + } - if (rewardShares > 0) { - IERC20(vars.cApe).safeTransfer(claimFor, rewardShares); + function _claimNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) internal { + ( + address owner, + uint256 pendingReward, + uint128 accumulatedRewardsPerNft + ) = calculatePendingReward(poolState, vars, nft, tokenIds); + + if (pendingReward > 0) { + uint256 arrayLength = tokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + poolState + .tokenStatus[tokenId] + .rewardsDebt = accumulatedRewardsPerNft; + + //emit event + emit NFTClaimed(nft, tokenId); + } + + IERC20(vars.cApe).safeTransfer(owner, pendingReward); } } @@ -792,12 +821,16 @@ library ApeStakingSinglePoolLogic { apePoolState.accumulatedRewardsPerNft += apeShareAmount.toUint128() / apeTotalPosition; + } else { + compoundFee += apeShareAmount; } uint128 bakcTotalPosition = bakcPoolState.totalPosition; if (bakcTotalPosition != 0) { bakcPoolState.accumulatedRewardsPerNft += (shareRewardAmount - apeShareAmount).toUint128() / bakcTotalPosition; + } else { + compoundFee += (shareRewardAmount - apeShareAmount); } return (debtInterest, compoundFee); } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 44cd6ba3f..bbb7450b8 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -295,8 +295,6 @@ import { ParaApeStaking__factory, ApeStakingP2PLogic__factory, ApeStakingP2PLogic, - ApeStakingVaultLogic, - ApeStakingVaultLogic__factory, ApeStakingPairPoolLogic__factory, ApeStakingPairPoolLogic, ApeStakingSinglePoolLogic__factory, diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 354d1b59d..a7365f8bd 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -192,6 +192,25 @@ describe("Para Ape Staking Test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("10")); + const user1PendingReward = await paraApeStaking.pairNFTPendingReward( + true, + [0, 1], + [0, 1] + ); + const user2PendingReward = await paraApeStaking.pairNFTPendingReward( + true, + [2], + [2] + ); + expect(user1PendingReward).to.be.closeTo( + parseEther("4320"), + parseEther("50") + ); + expect(user2PendingReward).to.be.closeTo( + parseEther("2160"), + parseEther("50") + ); + await waitForTx( await paraApeStaking .connect(user1.signer) @@ -200,8 +219,10 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking.connect(user2.signer).claimPairNFT(true, [2], [2]) ); - const user1Balance = await cApe.balanceOf(user1.address); - const user2Balance = await cApe.balanceOf(user2.address); + let user1Balance = await cApe.balanceOf(user1.address); + let user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); + expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); await advanceTimeAndBlock(parseInt("3600")); @@ -221,20 +242,35 @@ describe("Para Ape Staking Test", () => { expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + //720 + 720 + 2160(user2's reward part) = 3600 compoundFee = await paraApeStaking.pendingCApeReward( paraApeStaking.address ); - expect(compoundFee).to.be.closeTo(parseEther("1440"), parseEther("20")); + expect(compoundFee).to.be.closeTo(parseEther("3600"), parseEther("50")); await waitForTx( await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) ); const compoundFeeBalance = await cApe.balanceOf(user4.address); expect(compoundFeeBalance).to.be.closeTo(compoundFee, parseEther("1")); + //withdraw cannot claim pending reward + user1Balance = await cApe.balanceOf(user1.address); + user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); + //user2 get user1's part + expect(user2Balance).to.be.closeTo( + user1PendingReward.add(user2PendingReward), + parseEther("20") + ); expect( await variableDebtCApeCoin.balanceOf(paraApeStaking.address) ).to.be.equal(0); + + expect(await cApe.balanceOf(paraApeStaking.address)).to.be.closeTo( + "0", + parseEther("10") + ); }); it("test MAYC + BAKC pool logic", async () => { @@ -320,6 +356,25 @@ describe("Para Ape Staking Test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("10")); + const user1PendingReward = await paraApeStaking.pairNFTPendingReward( + false, + [0, 1], + [0, 1] + ); + const user2PendingReward = await paraApeStaking.pairNFTPendingReward( + false, + [2], + [2] + ); + expect(user1PendingReward).to.be.closeTo( + parseEther("4320"), + parseEther("50") + ); + expect(user2PendingReward).to.be.closeTo( + parseEther("2160"), + parseEther("50") + ); + await waitForTx( await paraApeStaking .connect(user1.signer) @@ -328,8 +383,10 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking.connect(user2.signer).claimPairNFT(false, [2], [2]) ); - const user1Balance = await cApe.balanceOf(user1.address); - const user2Balance = await cApe.balanceOf(user2.address); + let user1Balance = await cApe.balanceOf(user1.address); + let user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); + expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); await advanceTimeAndBlock(parseInt("3600")); @@ -351,20 +408,35 @@ describe("Para Ape Staking Test", () => { expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + //720 + 720 + 2160(user2's reward part) = 3600 compoundFee = await paraApeStaking.pendingCApeReward( paraApeStaking.address ); - expect(compoundFee).to.be.closeTo(parseEther("1440"), parseEther("20")); + expect(compoundFee).to.be.closeTo(parseEther("3600"), parseEther("50")); await waitForTx( await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) ); const compoundFeeBalance = await cApe.balanceOf(user4.address); expect(compoundFeeBalance).to.be.closeTo(compoundFee, parseEther("1")); + //withdraw cannot claim pending reward + user1Balance = await cApe.balanceOf(user1.address); + user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); + //user2 get user1's part + expect(user2Balance).to.be.closeTo( + user1PendingReward.add(user2PendingReward), + parseEther("20") + ); expect( await variableDebtCApeCoin.balanceOf(paraApeStaking.address) ).to.be.equal(0); + + expect(await cApe.balanceOf(paraApeStaking.address)).to.be.closeTo( + "0", + parseEther("1") + ); }); it("test single pool logic", async () => { @@ -479,6 +551,31 @@ describe("Para Ape Staking Test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("1080"), parseEther("10")); + const user1PendingReward = await paraApeStaking.nftPendingReward( + bayc.address, + [0, 1, 2] + ); + const user2PendingReward = await paraApeStaking.nftPendingReward( + mayc.address, + [0, 1, 2] + ); + const user3PendingReward = await paraApeStaking.nftPendingReward( + bakc.address, + [0, 1, 2] + ); + expect(user1PendingReward).to.be.closeTo( + parseEther("3240"), + parseEther("50") + ); + expect(user2PendingReward).to.be.closeTo( + parseEther("3240"), + parseEther("50") + ); + expect(user3PendingReward).to.be.closeTo( + parseEther("3240"), + parseEther("50") + ); + await waitForTx( await paraApeStaking .connect(user1.signer) @@ -494,12 +591,15 @@ describe("Para Ape Staking Test", () => { .connect(user2.signer) .claimNFT(bakc.address, [0, 1, 2]) ); - const user1Balance = await cApe.balanceOf(user1.address); - const user2Balance = await cApe.balanceOf(user2.address); - const user3Balance = await cApe.balanceOf(user3.address); + let user1Balance = await cApe.balanceOf(user1.address); + let user2Balance = await cApe.balanceOf(user2.address); + let user3Balance = await cApe.balanceOf(user3.address); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); + expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); + expect(user3Balance).to.be.closeTo(user3PendingReward, parseEther("1")); //base on both baycPairStakingRewardRatio and maycPairStakingRewardRatio are 0 - expect(user1Balance).to.be.closeTo(user2Balance, parseEther("100")); - expect(user1Balance).to.be.closeTo(user3Balance, parseEther("100")); + expect(user1Balance).to.be.closeTo(user2Balance, parseEther("50")); + expect(user1Balance).to.be.closeTo(user3Balance, parseEther("50")); await advanceTimeAndBlock(parseInt("3600")); @@ -528,10 +628,11 @@ describe("Para Ape Staking Test", () => { expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + //1080 + 1080 + 3240(user1's reward part) + 3240 (user2's reward part) compoundFee = await paraApeStaking.pendingCApeReward( paraApeStaking.address ); - expect(compoundFee).to.be.closeTo(parseEther("2160"), parseEther("20")); + expect(compoundFee).to.be.closeTo(parseEther("8640"), parseEther("100")); await waitForTx( await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) @@ -539,9 +640,25 @@ describe("Para Ape Staking Test", () => { const compoundFeeBalance = await cApe.balanceOf(user4.address); expect(compoundFeeBalance).to.be.closeTo(compoundFee, parseEther("1")); + //withdraw cannot claim pending reward + user1Balance = await cApe.balanceOf(user1.address); + user2Balance = await cApe.balanceOf(user2.address); + user3Balance = await cApe.balanceOf(user3.address); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("10")); + expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("10")); + expect(user3Balance).to.be.closeTo( + user3PendingReward.mul(2), + parseEther("10") + ); + expect( await variableDebtCApeCoin.balanceOf(paraApeStaking.address) ).to.be.equal(0); + + expect(await cApe.balanceOf(paraApeStaking.address)).to.be.closeTo( + "0", + parseEther("1") + ); }); it("depositPairNFT revert test", async () => { From ac73a540fe6c2e576ed66c5c88959d11309d02a3 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 11 Jul 2023 12:57:17 +0800 Subject: [PATCH 49/99] chore: add multicall --- contracts/apestaking/ParaApeStaking.sol | 2 + .../logic/ApeStakingPairPoolLogic.sol | 3 +- .../logic/ApeStakingSinglePoolLogic.sol | 5 +- .../openzeppelin/contracts/Multicall.sol | 24 ++++ test/para_ape_staking.spec.ts | 127 ++++++++++++++++++ 5 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 contracts/dependencies/openzeppelin/contracts/Multicall.sol diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 7ff72c92c..29df3641c 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -7,6 +7,7 @@ import "../dependencies/openzeppelin/upgradeability/ReentrancyGuardUpgradeable.s import "../dependencies/openzeppelin/upgradeability/PausableUpgradeable.sol"; import {IERC20, SafeERC20} from "../dependencies/openzeppelin/contracts/SafeERC20.sol"; import "../dependencies/openzeppelin/contracts/SafeCast.sol"; +import "../dependencies/openzeppelin/contracts/Multicall.sol"; import "../dependencies/yoga-labs/ApeCoinStaking.sol"; import "../interfaces/IACLManager.sol"; import "../interfaces/ICApe.sol"; @@ -22,6 +23,7 @@ contract ParaApeStaking is Initializable, ReentrancyGuardUpgradeable, PausableUpgradeable, + Multicall, IParaApeStaking { using SafeERC20 for IERC20; diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 644fced8d..42c3a2a4c 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -286,10 +286,11 @@ library ApeStakingPairPoolLogic { //update state poolState.totalPosition -= arrayLength.toUint64(); - poolState.stakingPosition -= stakingPair; //withdraw from ApeCoinStaking and compound if (stakingPair > 0) { + poolState.stakingPosition -= stakingPair; + { assembly { mstore(_nfts, stakingPair) diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 2f57ad921..d29b92527 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -310,10 +310,7 @@ library ApeStakingSinglePoolLogic { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; - require( - apePoolState.tokenStatus[apeTokenId].isInPool, - Errors.NFT_NOT_IN_SINGLE_POOL - ); + // we just need to check bakc is in the pool require( bakcPoolState.tokenStatus[bakcTokenId].isInPool, Errors.NFT_NOT_IN_SINGLE_POOL diff --git a/contracts/dependencies/openzeppelin/contracts/Multicall.sol b/contracts/dependencies/openzeppelin/contracts/Multicall.sol new file mode 100644 index 000000000..bdb820139 --- /dev/null +++ b/contracts/dependencies/openzeppelin/contracts/Multicall.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (utils/Multicall.sol) + +pragma solidity ^0.8.0; + +import "./Address.sol"; + +/** + * @dev Provides a function to batch together multiple calls in a single external call. + * + * _Available since v4.1._ + */ +abstract contract Multicall { + /** + * @dev Receives and executes a batch of function calls on this contract. + */ + function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + results[i] = Address.functionDelegateCall(address(this), data[i]); + } + return results; + } +} diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index a7365f8bd..b371f492a 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -1216,4 +1216,131 @@ describe("Para Ape Staking Test", () => { await paraApeStaking.connect(user2.signer).withdrawNFT(bakc.address, [1]) ); }); + + it("multicall test", async () => { + const { + users: [user1, , , user4], + bayc, + mayc, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "4", user1, true); + await supplyAndValidate(mayc, "4", user1, true); + await supplyAndValidate(bakc, "4", user1, true); + + let tx0 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ + true, + [0, 1], + [0, 1] + ]); + let tx1 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ + false, + [0, 1], + [2, 3] + ]); + let tx2 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + bayc.address, + [2,3] + ]); + let tx3 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + mayc.address, + [2,3] + ]); + + await waitForTx(await paraApeStaking.connect(user1.signer).multicall([tx0, tx1, tx2, tx3])); + + tx0 = paraApeStaking.interface.encodeFunctionData("stakingPairNFT", [ + true, + [0, 1], + [0, 1] + ]); + tx1 = paraApeStaking.interface.encodeFunctionData("stakingPairNFT", [ + false, + [0, 1], + [2, 3] + ]); + tx2 = paraApeStaking.interface.encodeFunctionData("stakingApe", [ + true, + [2,3] + ]); + tx3 = paraApeStaking.interface.encodeFunctionData("stakingApe", [ + false, + [2,3] + ]); + + await waitForTx(await paraApeStaking.connect(user1.signer).multicall([tx0, tx1, tx2, tx3])); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundPairNFT(true, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundPairNFT(false, [0, 1], [2, 3]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundApe(true, [2, 3]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundApe(false, [2, 3]) + ); + // + // tx0 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ + // true, + // [0, 1], + // [0, 1] + // ]); + // tx1 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ + // false, + // [0, 1], + // [2, 3] + // ]); + // tx2 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ + // true, + // [2,3] + // ]); + // tx3 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ + // false, + // [2,3] + // ]); + // + // await waitForTx(await paraApeStaking.connect(user4.signer).multicall([tx0, tx1, tx2, tx3])); + + tx0 = paraApeStaking.interface.encodeFunctionData("claimPairNFT", [ + true, + [0, 1], + [0, 1] + ]); + tx1 = paraApeStaking.interface.encodeFunctionData("claimPairNFT", [ + false, + [0, 1], + [2, 3] + ]); + tx2 = paraApeStaking.interface.encodeFunctionData("claimNFT", [ + bayc.address, + [2,3] + ]); + tx3 = paraApeStaking.interface.encodeFunctionData("claimNFT", [ + mayc.address, + [2,3] + ]); + + await waitForTx(await paraApeStaking.connect(user1.signer).multicall([tx0, tx1, tx2, tx3])); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawPairNFT(true, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawPairNFT(false, [0, 1], [2, 3]) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawNFT(bayc.address, [2, 3]) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawNFT(mayc.address, [2, 3]) + ); + }); }); From e8404ca5a88aa1b38e1c7df412fae7ccea6b75e8 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 12 Jul 2023 09:09:46 +0800 Subject: [PATCH 50/99] chore: small optimization --- Makefile | 4 ++ helpers/contracts-deployments.ts | 10 ++--- helpers/contracts-getters.ts | 61 ++++++++++++++++++++++++++ scripts/upgrade/ntoken.ts | 5 ++- scripts/upgrade/para_ape_staking.ts | 35 +++++++++++++++ tasks/upgrade/index.ts | 13 ++++++ test/para_ape_staking.spec.ts | 66 ++++++++++++++++++----------- 7 files changed, 162 insertions(+), 32 deletions(-) create mode 100644 scripts/upgrade/para_ape_staking.ts diff --git a/Makefile b/Makefile index f3c770610..d3bc01d1e 100644 --- a/Makefile +++ b/Makefile @@ -640,6 +640,10 @@ upgrade-timelock: upgrade-p2p-pair-staking: make TASK_NAME=upgrade:p2p-pair-staking run-task +.PHONY: upgrade-para-ape-staking +upgrade-para-ape-staking: + make TASK_NAME=upgrade:para-ape-staking run-task + .PHONY: upgrade-ntoken upgrade-ntoken: make TASK_NAME=upgrade:ntoken run-task diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index bbb7450b8..c64c7dcab 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -2787,10 +2787,7 @@ export const deployFakeParaApeStakingImpl = async (verify?: boolean) => { ) as Promise; }; -export const deployParaApeStakingImpl = async ( - compoundFee: number, - verify?: boolean -) => { +export const deployParaApeStakingImpl = async (verify?: boolean) => { const poolProxy = await getPoolProxy(); const allTokens = await getAllTokens(); const protocolDataProvider = await getProtocolDataProvider(); @@ -2819,10 +2816,9 @@ export const deployParaApeStakingImpl = async ( allTokens.cAPE.address, apeCoinStaking, aclManager.address, - compoundFee, ]; - const libraries = await deployParaApeStakingLibraries(); + const libraries = await deployParaApeStakingLibraries(verify); return withSaveAndVerify( new ParaApeStaking__factory(libraries, await getFirstSigner()), @@ -2840,7 +2836,7 @@ export const deployParaApeStaking = async ( if (fakeImplementation) { stakingImplementation = await deployFakeParaApeStakingImpl(verify); } else { - stakingImplementation = await deployParaApeStakingImpl(0, verify); + stakingImplementation = await deployParaApeStakingImpl(verify); } const deployer = await getFirstSigner(); const deployerAddress = await deployer.getAddress(); diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index e3e03c5c2..cb03d56bd 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -98,6 +98,10 @@ import { MockLendPool__factory, NTokenChromieSquiggle__factory, ParaApeStaking__factory, + AuctionLogic__factory, + PoolCore__factory, + PoolParameters__factory, + PoolMarketplace__factory, } from "../types"; import { getEthersSigners, @@ -191,6 +195,17 @@ export const getBorrowLogic = async (address?: tEthereumAddress) => await getFirstSigner() ); +export const getAuctionLogic = async (address?: tEthereumAddress) => + await AuctionLogic__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.AuctionLogic}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); + export const getLiquidationLogic = async (address?: tEthereumAddress) => await LiquidationLogic__factory.connect( address || @@ -235,6 +250,39 @@ export const getPoolLogic = async (address?: tEthereumAddress) => await getFirstSigner() ); +export const getPoolCoreImpl = async (address?: tEthereumAddress) => + await PoolCore__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.PoolCoreImpl}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); + +export const getPoolParametersImpl = async (address?: tEthereumAddress) => + await PoolParameters__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.PoolParametersImpl}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); + +export const getPoolMarketplaceImpl = async (address?: tEthereumAddress) => + await PoolMarketplace__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.PoolMarketplaceImpl}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); + export const getPoolProxy = async (address?: tEthereumAddress) => { return await IPool__factory.connect( address || @@ -1007,6 +1055,19 @@ export const getParaApeStaking = async (address?: tEthereumAddress) => await getFirstSigner() ); +export const getParaApeStakingImplementation = async ( + address?: tEthereumAddress +) => + await ParaApeStaking__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.ParaApeStakingImpl}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); + export const getHelperContract = async (address?: tEthereumAddress) => await HelperContract__factory.connect( address || diff --git a/scripts/upgrade/ntoken.ts b/scripts/upgrade/ntoken.ts index ae76c097a..7c1342f37 100644 --- a/scripts/upgrade/ntoken.ts +++ b/scripts/upgrade/ntoken.ts @@ -240,7 +240,10 @@ export const upgradeNToken = async (verify = false) => { await (await getNToken(newImpl)).NTOKEN_REVISION() ).toNumber(); - if (oldRevision == newRevision) { + if (oldRevision >= newRevision) { + console.log( + `trying to upgrade ${token.symbol}'s version from v${oldRevision} to v${newRevision}, skip` + ); continue; } diff --git a/scripts/upgrade/para_ape_staking.ts b/scripts/upgrade/para_ape_staking.ts new file mode 100644 index 000000000..05a5ef7cf --- /dev/null +++ b/scripts/upgrade/para_ape_staking.ts @@ -0,0 +1,35 @@ +import {deployParaApeStakingImpl} from "../../helpers/contracts-deployments"; +import { + getInitializableAdminUpgradeabilityProxy, + getParaApeStaking, +} from "../../helpers/contracts-getters"; +import {dryRunEncodedData} from "../../helpers/contracts-helpers"; +import {DRY_RUN, GLOBAL_OVERRIDES} from "../../helpers/hardhat-constants"; +import {waitForTx} from "../../helpers/misc-utils"; + +export const upgradeParaApeStaking = async (verify = false) => { + console.time("deploy ParaApeStaking"); + const paraApeStakingImpl = await deployParaApeStakingImpl(verify); + const paraApeStaking = await getParaApeStaking(); + const paraApeStakingProxy = await getInitializableAdminUpgradeabilityProxy( + paraApeStaking.address + ); + console.timeEnd("deploy ParaApeStaking"); + + console.time("upgrade ParaApeStaking"); + if (DRY_RUN) { + const encodedData = paraApeStakingProxy.interface.encodeFunctionData( + "upgradeTo", + [paraApeStakingImpl.address] + ); + await dryRunEncodedData(paraApeStakingProxy.address, encodedData); + } else { + await waitForTx( + await paraApeStakingProxy.upgradeTo( + paraApeStakingImpl.address, + GLOBAL_OVERRIDES + ) + ); + } + console.timeEnd("upgrade ParaApeStaking"); +}; diff --git a/tasks/upgrade/index.ts b/tasks/upgrade/index.ts index 025c336aa..281c033a8 100644 --- a/tasks/upgrade/index.ts +++ b/tasks/upgrade/index.ts @@ -1,5 +1,6 @@ import {task} from "hardhat/config"; import {ETHERSCAN_VERIFICATION} from "../../helpers/hardhat-constants"; +import {upgradeParaApeStaking} from "../../scripts/upgrade/para_ape_staking"; task("upgrade:all", "upgrade all").setAction(async (_, DRE) => { const {upgradeAll} = await import("../../scripts/upgrade"); @@ -131,6 +132,18 @@ task("upgrade:p2p-pair-staking", "upgrade p2p pair staking") console.timeEnd("upgrade p2p pair staking"); }); +task("upgrade:para-ape-staking", "upgrade para ape staking").setAction( + async (_, DRE) => { + const {upgradeParaApeStaking} = await import( + "../../scripts/upgrade/para_ape_staking" + ); + await DRE.run("set-DRE"); + console.time("upgrade para ape staking"); + await upgradeParaApeStaking(ETHERSCAN_VERIFICATION); + console.timeEnd("upgrade para ape staking"); + } +); + task("upgrade:ptoken", "upgrade ptoken").setAction(async (_, DRE) => { const {upgradePToken} = await import("../../scripts/upgrade/ptoken"); await DRE.run("set-DRE"); diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index b371f492a..9aeac9dde 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -37,7 +37,7 @@ describe("Para Ape Staking Test", () => { } = testEnv; //upgrade to non-fake implementation - const paraApeStakingImpl = await deployParaApeStakingImpl(0); + const paraApeStakingImpl = await deployParaApeStakingImpl(false); paraApeStaking = await getParaApeStaking(); const paraApeStakingProxy = await getInitializableAdminUpgradeabilityProxy( paraApeStaking.address @@ -1232,58 +1232,66 @@ describe("Para Ape Staking Test", () => { let tx0 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ true, [0, 1], - [0, 1] + [0, 1], ]); let tx1 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ false, [0, 1], - [2, 3] + [2, 3], ]); let tx2 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ bayc.address, - [2,3] + [2, 3], ]); let tx3 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ mayc.address, - [2,3] + [2, 3], ]); - await waitForTx(await paraApeStaking.connect(user1.signer).multicall([tx0, tx1, tx2, tx3])); + await waitForTx( + await paraApeStaking.connect(user1.signer).multicall([tx0, tx1, tx2, tx3]) + ); tx0 = paraApeStaking.interface.encodeFunctionData("stakingPairNFT", [ true, [0, 1], - [0, 1] + [0, 1], ]); tx1 = paraApeStaking.interface.encodeFunctionData("stakingPairNFT", [ false, [0, 1], - [2, 3] + [2, 3], ]); tx2 = paraApeStaking.interface.encodeFunctionData("stakingApe", [ true, - [2,3] + [2, 3], ]); tx3 = paraApeStaking.interface.encodeFunctionData("stakingApe", [ false, - [2,3] + [2, 3], ]); - await waitForTx(await paraApeStaking.connect(user1.signer).multicall([tx0, tx1, tx2, tx3])); + await waitForTx( + await paraApeStaking.connect(user1.signer).multicall([tx0, tx1, tx2, tx3]) + ); await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking.connect(user4.signer).compoundPairNFT(true, [0, 1], [0, 1]) + await paraApeStaking + .connect(user4.signer) + .compoundPairNFT(true, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user4.signer).compoundPairNFT(false, [0, 1], [2, 3]) + await paraApeStaking + .connect(user4.signer) + .compoundPairNFT(false, [0, 1], [2, 3]) ); await waitForTx( - await paraApeStaking.connect(user4.signer).compoundApe(true, [2, 3]) + await paraApeStaking.connect(user4.signer).compoundApe(true, [2, 3]) ); await waitForTx( - await paraApeStaking.connect(user4.signer).compoundApe(false, [2, 3]) + await paraApeStaking.connect(user4.signer).compoundApe(false, [2, 3]) ); // // tx0 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ @@ -1310,37 +1318,47 @@ describe("Para Ape Staking Test", () => { tx0 = paraApeStaking.interface.encodeFunctionData("claimPairNFT", [ true, [0, 1], - [0, 1] + [0, 1], ]); tx1 = paraApeStaking.interface.encodeFunctionData("claimPairNFT", [ false, [0, 1], - [2, 3] + [2, 3], ]); tx2 = paraApeStaking.interface.encodeFunctionData("claimNFT", [ bayc.address, - [2,3] + [2, 3], ]); tx3 = paraApeStaking.interface.encodeFunctionData("claimNFT", [ mayc.address, - [2,3] + [2, 3], ]); - await waitForTx(await paraApeStaking.connect(user1.signer).multicall([tx0, tx1, tx2, tx3])); + await waitForTx( + await paraApeStaking.connect(user1.signer).multicall([tx0, tx1, tx2, tx3]) + ); await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking.connect(user1.signer).withdrawPairNFT(true, [0, 1], [0, 1]) + await paraApeStaking + .connect(user1.signer) + .withdrawPairNFT(true, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user1.signer).withdrawPairNFT(false, [0, 1], [2, 3]) + await paraApeStaking + .connect(user1.signer) + .withdrawPairNFT(false, [0, 1], [2, 3]) ); await waitForTx( - await paraApeStaking.connect(user1.signer).withdrawNFT(bayc.address, [2, 3]) + await paraApeStaking + .connect(user1.signer) + .withdrawNFT(bayc.address, [2, 3]) ); await waitForTx( - await paraApeStaking.connect(user1.signer).withdrawNFT(mayc.address, [2, 3]) + await paraApeStaking + .connect(user1.signer) + .withdrawNFT(mayc.address, [2, 3]) ); }); }); From dd4e2bb9b42b7a1392d577f74e7c2a3b287dba6c Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 12 Jul 2023 16:11:43 +0800 Subject: [PATCH 51/99] chore: fix bakc single pool issue --- contracts/apestaking/ParaApeStaking.sol | 80 ++--- .../logic/ApeStakingCommonLogic.sol | 35 +-- .../logic/ApeStakingPairPoolLogic.sol | 7 +- .../logic/ApeStakingSinglePoolLogic.sol | 275 +++++++++++------- contracts/interfaces/IApeStakingVault.sol | 16 + test/para_ape_staking.spec.ts | 4 +- 6 files changed, 242 insertions(+), 175 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 29df3641c..75f23258a 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -473,17 +473,7 @@ contract ParaApeStaking is Errors.NFT_NOT_ALLOWED ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = (nft == bayc) - ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID - : (nft == mayc) - ? ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID - : ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID; - ApeStakingSinglePoolLogic.depositNFT( - vaultStorage.poolStates[poolId], - vars, - nft, - tokenIds - ); + ApeStakingSinglePoolLogic.depositNFT(vaultStorage, vars, nft, tokenIds); } function stakingApe(bool isBAYC, uint32[] calldata tokenIds) @@ -513,9 +503,7 @@ contract ParaApeStaking is : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; ApeStakingSinglePoolLogic.stakingBAKC( vaultStorage.poolStates[poolId], - vaultStorage.poolStates[ - ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID - ], + vaultStorage.bakcPoolState, vars, isBAYC, apeTokenIds, @@ -569,18 +557,27 @@ contract ParaApeStaking is Errors.NFT_NOT_ALLOWED ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = (nft == bayc) - ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID - : (nft == mayc) - ? ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID - : ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID; - (, uint256 pendingReward, ) = ApeStakingSinglePoolLogic - .calculatePendingReward( - vaultStorage.poolStates[poolId], - vars, - nft, - tokenIds - ); + vars.accumulatedRewardsPerNft = (nft == bakc) + ? vaultStorage.bakcPoolState.accumulatedRewardsPerNft + : (nft == bayc) + ? vaultStorage + .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] + .accumulatedRewardsPerNft + : vaultStorage + .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] + .accumulatedRewardsPerNft; + mapping(uint256 => IParaApeStaking.TokenStatus) + storage tokenStatus = (nft == bakc) + ? vaultStorage.bakcPoolState.tokenStatus + : (nft == bayc) + ? vaultStorage + .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] + .tokenStatus + : vaultStorage + .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] + .tokenStatus; + (, uint256 pendingReward) = ApeStakingSinglePoolLogic + .calculatePendingReward(tokenStatus, vars, nft, tokenIds); return pendingReward; } @@ -594,17 +591,26 @@ contract ParaApeStaking is Errors.NFT_NOT_ALLOWED ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = (nft == bayc) - ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID - : (nft == mayc) - ? ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID - : ApeStakingPairPoolLogic.BAKC_SINGLE_POOL_ID; - ApeStakingSinglePoolLogic.claimNFT( - vaultStorage.poolStates[poolId], - vars, - nft, - tokenIds - ); + vars.accumulatedRewardsPerNft = (nft == bakc) + ? vaultStorage.bakcPoolState.accumulatedRewardsPerNft + : (nft == bayc) + ? vaultStorage + .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] + .accumulatedRewardsPerNft + : vaultStorage + .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] + .accumulatedRewardsPerNft; + mapping(uint256 => IParaApeStaking.TokenStatus) + storage tokenStatus = (nft == bakc) + ? vaultStorage.bakcPoolState.tokenStatus + : (nft == bayc) + ? vaultStorage + .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] + .tokenStatus + : vaultStorage + .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] + .tokenStatus; + ApeStakingSinglePoolLogic.claimNFT(tokenStatus, vars, nft, tokenIds); } function withdrawNFT(address nft, uint32[] calldata tokenIds) diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index e05a0dc65..624a789ce 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -51,23 +51,18 @@ library ApeStakingCommonLogic { cApeExchangeRate, latestBorrowIndex ); - if (debtInterest >= vars.totalClaimedApe) { - cApeDebtShare -= vars - .totalClaimedApe - .rayDiv(latestBorrowIndex) - .rayDiv(cApeExchangeRate); - poolState.cApeDebtShare = cApeDebtShare; - return (vars.totalClaimedApe, 0); - } else { - //repay debt - cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate - ); - poolState.cApeDebtShare = cApeDebtShare; - + uint256 repayAmount = (debtInterest >= vars.totalClaimedApe) + ? vars.totalClaimedApe + : debtInterest; + cApeDebtShare -= repayAmount.rayDiv(latestBorrowIndex).rayDiv( + cApeExchangeRate + ); + poolState.cApeDebtShare = cApeDebtShare; + uint256 compoundFee = 0; + if (vars.totalClaimedApe > debtInterest) { uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) .rayDiv(cApeExchangeRate); - uint256 compoundFee = shareRewardAmount.percentMul( + compoundFee = shareRewardAmount.percentMul( vars.compoundFee ); shareRewardAmount = shareRewardAmount - compoundFee; @@ -80,15 +75,15 @@ library ApeStakingCommonLogic { } else { compoundFee += shareRewardAmount; } - return (debtInterest, compoundFee); } + + return (repayAmount, compoundFee); } function borrowCApeFromPool( - IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint256 totalBorrow - ) internal { + ) internal returns (uint256) { uint256 latestBorrowIndex = IPool(vars.pool).borrowPoolCApe( totalBorrow ); @@ -96,9 +91,7 @@ library ApeStakingCommonLogic { uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( WadRayMath.RAY ); - poolState.cApeDebtShare += totalBorrow.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate - ); + return totalBorrow.rayDiv(latestBorrowIndex).rayDiv(cApeExchangeRate); } function calculateCurrentPositionDebtInterest( diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 42c3a2a4c..cbe50fa49 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -28,7 +28,6 @@ library ApeStakingPairPoolLogic { uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; uint256 public constant BAYC_SINGLE_POOL_ID = 3; uint256 public constant MAYC_SINGLE_POOL_ID = 4; - uint256 public constant BAKC_SINGLE_POOL_ID = 5; uint256 constant BAYC_POOL_ID = 1; uint256 constant MAYC_POOL_ID = 2; @@ -189,7 +188,11 @@ library ApeStakingPairPoolLogic { // prepare Ape coin uint256 totalBorrow = (vars.positionCap + vars.bakcMatchedCap) * arrayLength; - ApeStakingCommonLogic.borrowCApeFromPool(poolState, vars, totalBorrow); + uint256 cApeDebtShare = ApeStakingCommonLogic.borrowCApeFromPool( + vars, + totalBorrow + ); + poolState.cApeDebtShare += cApeDebtShare; //stake in ApeCoinStaking ApeCoinStaking.PairNftDepositWithAmount[] diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index d29b92527..6d4bec4e8 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -28,7 +28,6 @@ library ApeStakingSinglePoolLogic { uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; uint256 public constant BAYC_SINGLE_POOL_ID = 3; uint256 public constant MAYC_SINGLE_POOL_ID = 4; - uint256 public constant BAKC_SINGLE_POOL_ID = 5; uint256 constant BAYC_POOL_ID = 1; uint256 constant MAYC_POOL_ID = 2; @@ -43,7 +42,7 @@ library ApeStakingSinglePoolLogic { event NFTWithdrawn(address nft, uint256 tokenId); function depositNFT( - IParaApeStaking.PoolState storage poolState, + IParaApeStaking.VaultStorage storage vaultStorage, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds @@ -52,36 +51,55 @@ library ApeStakingSinglePoolLogic { require(arrayLength > 0, Errors.INVALID_PARAMETER); address msgSender = msg.sender; - uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + address nToken; + uint128 accumulatedRewardsPerNft; + if (nft == vars.bayc) { + nToken = vars.nBayc; + IParaApeStaking.PoolState storage poolState = vaultStorage + .poolStates[BAYC_SINGLE_POOL_ID]; + accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + poolState.totalPosition += arrayLength.toUint64(); + } else if (nft == vars.mayc) { + nToken = vars.nMayc; + IParaApeStaking.PoolState storage poolState = vaultStorage + .poolStates[MAYC_SINGLE_POOL_ID]; + accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + poolState.totalPosition += arrayLength.toUint64(); + } else { + nToken = vars.nBakc; + accumulatedRewardsPerNft = vaultStorage + .bakcPoolState + .accumulatedRewardsPerNft; + vaultStorage.bakcPoolState.totalPosition += arrayLength.toUint64(); + } + + mapping(uint256 => IParaApeStaking.TokenStatus) + storage tokenStatus = (nft == vars.bakc) + ? vaultStorage.bakcPoolState.tokenStatus + : (nft == vars.bayc) + ? vaultStorage + .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] + .tokenStatus + : vaultStorage + .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] + .tokenStatus; + for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; - if (nft == vars.bakc) { - address nTokenOwner = IERC721(vars.nBakc).ownerOf(tokenId); - require(msgSender == nTokenOwner, Errors.NOT_THE_OWNER); + address nTokenOwner = IERC721(nToken).ownerOf(tokenId); + require(msgSender == nTokenOwner, Errors.NOT_THE_OWNER); + if (nft == vars.bakc) { (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( BAKC_POOL_ID, tokenId ); require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); - - IERC721(nft).safeTransferFrom( - vars.nBakc, - address(this), - tokenId - ); } else { - if (nft == vars.bayc) { - vars.nApe = vars.nBayc; - vars.apeStakingPoolId = BAYC_POOL_ID; - } else { - vars.nApe = vars.nMayc; - vars.apeStakingPoolId = MAYC_POOL_ID; - } - - address nApeOwner = IERC721(vars.nApe).ownerOf(tokenId); - require(msgSender == nApeOwner, Errors.NOT_THE_OWNER); + vars.apeStakingPoolId = (nft == vars.bayc) + ? BAYC_POOL_ID + : MAYC_POOL_ID; (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( vars.apeStakingPoolId, @@ -94,16 +112,12 @@ library ApeStakingSinglePoolLogic { tokenId ); require(!isPaired, Errors.PAIR_POSITION_EXISTED); - - IERC721(nft).safeTransferFrom( - vars.nApe, - address(this), - tokenId - ); } + IERC721(nft).safeTransferFrom(nToken, address(this), tokenId); + //update token status - poolState.tokenStatus[tokenId] = IApeStakingVault.TokenStatus({ + tokenStatus[tokenId] = IApeStakingVault.TokenStatus({ rewardsDebt: accumulatedRewardsPerNft, isInPool: true }); @@ -111,8 +125,6 @@ library ApeStakingSinglePoolLogic { //emit event emit NFTDeposited(nft, tokenId); } - - poolState.totalPosition += arrayLength.toUint64(); } function stakingApe( @@ -147,7 +159,11 @@ library ApeStakingSinglePoolLogic { // prepare Ape coin uint256 totalBorrow = vars.positionCap * arrayLength; - ApeStakingCommonLogic.borrowCApeFromPool(poolState, vars, totalBorrow); + uint256 cApeDebtShare = ApeStakingCommonLogic.borrowCApeFromPool( + vars, + totalBorrow + ); + poolState.cApeDebtShare += cApeDebtShare; //stake in ApeCoinStaking if (isBAYC) { @@ -161,7 +177,7 @@ library ApeStakingSinglePoolLogic { function stakingBAKC( IParaApeStaking.PoolState storage apePoolState, - IParaApeStaking.PoolState storage bakcPoolState, + IParaApeStaking.BAKCPoolState storage bakcPoolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, @@ -203,8 +219,7 @@ library ApeStakingSinglePoolLogic { // prepare Ape coin uint256 totalBorrow = vars.bakcMatchedCap * arrayLength; - ApeStakingCommonLogic.borrowCApeFromPool( - bakcPoolState, + uint256 cApeDebtShare = ApeStakingCommonLogic.borrowCApeFromPool( vars, totalBorrow ); @@ -216,11 +231,13 @@ library ApeStakingSinglePoolLogic { ); if (isBAYC) { vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); + bakcPoolState.baycStakingPosition += arrayLength.toUint64(); + bakcPoolState.baycCApeDebtShare += cApeDebtShare; } else { vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); + bakcPoolState.maycStakingPosition += arrayLength.toUint64(); + bakcPoolState.maycCApeDebtShare += cApeDebtShare; } - - bakcPoolState.stakingPosition += arrayLength.toUint64(); } function compoundApe( @@ -300,8 +317,8 @@ library ApeStakingSinglePoolLogic { apePoolState = vaultStorage.poolStates[MAYC_SINGLE_POOL_ID]; vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; } - IParaApeStaking.PoolState storage bakcPoolState = vaultStorage - .poolStates[BAKC_SINGLE_POOL_ID]; + IParaApeStaking.BAKCPoolState storage bakcPoolState = vaultStorage + .bakcPoolState; { ApeCoinStaking.PairNft[] @@ -347,7 +364,12 @@ library ApeStakingSinglePoolLogic { ( vars.totalRepay, vars.totalCompoundFee - ) = _calculateRepayAndCompoundBAKC(apePoolState, bakcPoolState, vars); + ) = _calculateRepayAndCompoundBAKC( + apePoolState, + bakcPoolState, + vars, + isBAYC + ); if (vars.totalRepay > 0) { IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); @@ -359,7 +381,7 @@ library ApeStakingSinglePoolLogic { } function claimNFT( - IParaApeStaking.PoolState storage poolState, + mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds @@ -367,7 +389,7 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - _claimNFT(poolState, vars, nft, tokenIds); + _claimNFT(tokenStatus, vars, nft, tokenIds); } function withdrawNFT( @@ -380,24 +402,37 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - IParaApeStaking.PoolState storage curPoolState; address nToken; if (nft == vars.bayc) { - curPoolState = vaultStorage.poolStates[BAYC_SINGLE_POOL_ID]; + vars.accumulatedRewardsPerNft = vaultStorage + .poolStates[BAYC_SINGLE_POOL_ID] + .accumulatedRewardsPerNft; nToken = vars.nBayc; } else if (nft == vars.mayc) { - curPoolState = vaultStorage.poolStates[MAYC_SINGLE_POOL_ID]; + vars.accumulatedRewardsPerNft = vaultStorage + .poolStates[MAYC_SINGLE_POOL_ID] + .accumulatedRewardsPerNft; nToken = vars.nMayc; } else { - curPoolState = vaultStorage.poolStates[BAKC_SINGLE_POOL_ID]; + vars.accumulatedRewardsPerNft = vaultStorage + .bakcPoolState + .accumulatedRewardsPerNft; nToken = vars.nBakc; } - //claim pending reward - _claimNFT(curPoolState, vars, nft, tokenIds); + mapping(uint256 => IParaApeStaking.TokenStatus) + storage tokenStatus = (nft == vars.bakc) + ? vaultStorage.bakcPoolState.tokenStatus + : (nft == vars.bayc) + ? vaultStorage + .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] + .tokenStatus + : vaultStorage + .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] + .tokenStatus; - //update state - curPoolState.totalPosition -= arrayLength.toUint64(); + //claim pending reward + _claimNFT(tokenStatus, vars, nft, tokenIds); if (nft == vars.bayc || nft == vars.mayc) { _unstakeApe( @@ -419,7 +454,7 @@ library ApeStakingSinglePoolLogic { address nTokenOwner = IERC721(nToken).ownerOf(tokenId); require(msgSender == nTokenOwner, Errors.NOT_THE_OWNER); - delete curPoolState.tokenStatus[tokenId]; + delete tokenStatus[tokenId]; IERC721(nft).safeTransferFrom(address(this), nToken, tokenId); @@ -447,6 +482,9 @@ library ApeStakingSinglePoolLogic { apePoolState = vaultStorage.poolStates[MAYC_SINGLE_POOL_ID]; vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; } + + apePoolState.totalPosition -= tokenIds.length.toUint64(); + ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](tokenIds.length); ApeCoinStaking.PairNftWithdrawWithAmount[] @@ -491,10 +529,14 @@ library ApeStakingSinglePoolLogic { } } - IParaApeStaking.PoolState storage bakcPoolState = vaultStorage - .poolStates[BAKC_SINGLE_POOL_ID]; apePoolState.stakingPosition -= singleStakingCount; - bakcPoolState.stakingPosition -= pairStakingCount; + IParaApeStaking.BAKCPoolState storage bakcPoolState = vaultStorage + .bakcPoolState; + if (isBAYC) { + bakcPoolState.baycStakingPosition -= pairStakingCount; + } else { + bakcPoolState.maycStakingPosition -= pairStakingCount; + } if (singleStakingCount > 0) { assembly { @@ -553,7 +595,8 @@ library ApeStakingSinglePoolLogic { ) = _calculateRepayAndCompoundBAKC( apePoolState, bakcPoolState, - vars + vars, + isBAYC ); vars.totalRepay += bakcTotalRepay; vars.totalCompoundFee += bakcCompoundFee; @@ -576,6 +619,9 @@ library ApeStakingSinglePoolLogic { uint32[] calldata tokenIds ) internal { uint256 arrayLength = tokenIds.length; + IParaApeStaking.BAKCPoolState storage bakcPoolState = vaultStorage + .bakcPoolState; + vaultStorage.bakcPoolState.totalPosition -= arrayLength.toUint64(); ApeCoinStaking.PairNftWithdrawWithAmount[] memory baycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength @@ -634,9 +680,7 @@ library ApeStakingSinglePoolLogic { ); if (baycPairCount > 0) { - IParaApeStaking.PoolState storage bakcPoolState = vaultStorage - .poolStates[BAKC_SINGLE_POOL_ID]; - bakcPoolState.stakingPosition -= baycPairCount; + bakcPoolState.baycStakingPosition -= baycPairCount; vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); vars.apeCoinStaking.withdrawBAKC(baycPair, _otherPairs); @@ -654,13 +698,12 @@ library ApeStakingSinglePoolLogic { ) = _calculateRepayAndCompoundBAKC( vaultStorage.poolStates[BAYC_SINGLE_POOL_ID], bakcPoolState, - vars + vars, + true ); } if (maycPairCount > 0) { - IParaApeStaking.PoolState storage bakcPoolState = vaultStorage - .poolStates[BAKC_SINGLE_POOL_ID]; - bakcPoolState.stakingPosition -= maycPairCount; + bakcPoolState.maycStakingPosition -= maycPairCount; vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); vars.apeCoinStaking.withdrawBAKC(baycPair, maycPair); @@ -678,7 +721,8 @@ library ApeStakingSinglePoolLogic { ) = _calculateRepayAndCompoundBAKC( vaultStorage.poolStates[MAYC_SINGLE_POOL_ID], bakcPoolState, - vars + vars, + false ); vars.totalRepay += maycTotalRepay; vars.totalCompoundFee += maycCompoundFee; @@ -694,22 +738,13 @@ library ApeStakingSinglePoolLogic { } function calculatePendingReward( - IParaApeStaking.PoolState storage poolState, + mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds - ) - public - view - returns ( - address claimFor, - uint256 pendingReward, - uint128 accumulatedRewardsPerNft - ) - { + ) public view returns (address claimFor, uint256 pendingReward) { uint256 rewardShares; uint256 arrayLength = tokenIds.length; - accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) ? vars.nMayc : vars.nBakc; @@ -727,39 +762,39 @@ library ApeStakingSinglePoolLogic { } require( - poolState.tokenStatus[tokenId].isInPool, + tokenStatus[tokenId].isInPool, Errors.NFT_NOT_IN_SINGLE_POOL ); //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - rewardShares += (accumulatedRewardsPerNft - - poolState.tokenStatus[tokenId].rewardsDebt); + rewardShares += (vars.accumulatedRewardsPerNft - + tokenStatus[tokenId].rewardsDebt); } pendingReward = ICApe(vars.cApe).getPooledApeByShares(rewardShares); - return (claimFor, pendingReward, accumulatedRewardsPerNft); + return (claimFor, pendingReward); } function _claimNFT( - IParaApeStaking.PoolState storage poolState, + mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds ) internal { - ( - address owner, - uint256 pendingReward, - uint128 accumulatedRewardsPerNft - ) = calculatePendingReward(poolState, vars, nft, tokenIds); + (address owner, uint256 pendingReward) = calculatePendingReward( + tokenStatus, + vars, + nft, + tokenIds + ); if (pendingReward > 0) { uint256 arrayLength = tokenIds.length; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; - poolState - .tokenStatus[tokenId] - .rewardsDebt = accumulatedRewardsPerNft; + tokenStatus[tokenId].rewardsDebt = vars + .accumulatedRewardsPerNft; //emit event emit NFTClaimed(nft, tokenId); @@ -771,43 +806,56 @@ library ApeStakingSinglePoolLogic { function _calculateRepayAndCompoundBAKC( IParaApeStaking.PoolState storage apePoolState, - IParaApeStaking.PoolState storage bakcPoolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars + IParaApeStaking.BAKCPoolState storage bakcPoolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC ) internal returns (uint256, uint256) { uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( WadRayMath.RAY ); uint256 latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); - uint256 cApeDebtShare = bakcPoolState.cApeDebtShare; - uint256 debtInterest = ApeStakingCommonLogic - .calculateCurrentPositionDebtInterest( - cApeDebtShare, - bakcPoolState.stakingPosition, - vars.bakcMatchedCap, - cApeExchangeRate, - latestBorrowIndex - ); - if (debtInterest >= vars.totalClaimedApe) { - cApeDebtShare -= vars - .totalClaimedApe - .rayDiv(latestBorrowIndex) - .rayDiv(cApeExchangeRate); - bakcPoolState.cApeDebtShare = cApeDebtShare; - return (vars.totalClaimedApe, 0); - } else { - //repay debt - cApeDebtShare -= debtInterest.rayDiv(latestBorrowIndex).rayDiv( + uint256 repayAmount = 0; + uint256 debtInterest = 0; + //calculate repay + { + uint256 cApeDebtShare; + uint256 stakingPosition; + if (isBAYC) { + cApeDebtShare = bakcPoolState.baycCApeDebtShare; + stakingPosition = bakcPoolState.baycStakingPosition; + } else { + cApeDebtShare = bakcPoolState.maycCApeDebtShare; + stakingPosition = bakcPoolState.maycStakingPosition; + } + debtInterest = ApeStakingCommonLogic + .calculateCurrentPositionDebtInterest( + cApeDebtShare, + stakingPosition, + vars.bakcMatchedCap, + cApeExchangeRate, + latestBorrowIndex + ); + repayAmount = (debtInterest >= vars.totalClaimedApe) + ? vars.totalClaimedApe + : debtInterest; + cApeDebtShare -= repayAmount.rayDiv(latestBorrowIndex).rayDiv( cApeExchangeRate ); - bakcPoolState.cApeDebtShare = cApeDebtShare; + if (isBAYC) { + bakcPoolState.baycCApeDebtShare = cApeDebtShare; + } else { + bakcPoolState.maycCApeDebtShare = cApeDebtShare; + } + } + //calculate compound fee + uint256 compoundFee = 0; + if (vars.totalClaimedApe > debtInterest) { //update reward index uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) .rayDiv(cApeExchangeRate); - uint256 compoundFee = shareRewardAmount.percentMul( - vars.compoundFee - ); + compoundFee = shareRewardAmount.percentMul(vars.compoundFee); shareRewardAmount = shareRewardAmount - compoundFee; uint256 apeShareAmount = shareRewardAmount.percentMul( vars.apeRewardRatio @@ -829,7 +877,8 @@ library ApeStakingSinglePoolLogic { } else { compoundFee += (shareRewardAmount - apeShareAmount); } - return (debtInterest, compoundFee); } + + return (repayAmount, compoundFee); } } diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index e5743cb0e..4a70e20c5 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -30,8 +30,24 @@ interface IApeStakingVault { uint256 cApeDebtShare; } + struct BAKCPoolState { + // accumulated cApe reward for per NFT position + uint128 accumulatedRewardsPerNft; + // total NFT position count + uint64 totalPosition; + // total staking position + uint64 baycStakingPosition; + uint64 maycStakingPosition; + //pool cape debt token share + uint256 baycCApeDebtShare; + uint256 maycCApeDebtShare; + //tokenId => reward debt position + mapping(uint256 => TokenStatus) tokenStatus; + } + struct VaultStorage { mapping(uint256 => PoolState) poolStates; + BAKCPoolState bakcPoolState; uint128 baycPairStakingRewardRatio; uint128 maycPairStakingRewardRatio; } diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 9aeac9dde..4ae689662 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -653,7 +653,7 @@ describe("Para Ape Staking Test", () => { expect( await variableDebtCApeCoin.balanceOf(paraApeStaking.address) - ).to.be.equal(0); + ).to.be.closeTo("0", "10"); expect(await cApe.balanceOf(paraApeStaking.address)).to.be.closeTo( "0", @@ -1038,7 +1038,7 @@ describe("Para Ape Staking Test", () => { await expect( paraApeStaking.connect(user4.signer).compoundBAKC(true, [2], [1]) - ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + ).to.be.reverted; await expect( paraApeStaking.connect(user4.signer).compoundBAKC(true, [1], [2]) From ee5b36be153819d91d386ec9542125c8232065be Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 12 Jul 2023 18:56:51 +0800 Subject: [PATCH 52/99] chore: gas optimization --- contracts/apestaking/ParaApeStaking.sol | 44 +---- .../logic/ApeStakingCommonLogic.sol | 10 +- .../logic/ApeStakingPairPoolLogic.sol | 10 +- .../logic/ApeStakingSinglePoolLogic.sol | 152 +++++++++++------- contracts/interfaces/IApeStakingVault.sol | 30 ++-- 5 files changed, 126 insertions(+), 120 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 75f23258a..cbca26d35 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -557,27 +557,8 @@ contract ParaApeStaking is Errors.NFT_NOT_ALLOWED ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - vars.accumulatedRewardsPerNft = (nft == bakc) - ? vaultStorage.bakcPoolState.accumulatedRewardsPerNft - : (nft == bayc) - ? vaultStorage - .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] - .accumulatedRewardsPerNft - : vaultStorage - .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] - .accumulatedRewardsPerNft; - mapping(uint256 => IParaApeStaking.TokenStatus) - storage tokenStatus = (nft == bakc) - ? vaultStorage.bakcPoolState.tokenStatus - : (nft == bayc) - ? vaultStorage - .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] - .tokenStatus - : vaultStorage - .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] - .tokenStatus; - (, uint256 pendingReward) = ApeStakingSinglePoolLogic - .calculatePendingReward(tokenStatus, vars, nft, tokenIds); + uint256 pendingReward = ApeStakingSinglePoolLogic + .calculatePendingReward(vaultStorage, vars, nft, tokenIds); return pendingReward; } @@ -591,26 +572,7 @@ contract ParaApeStaking is Errors.NFT_NOT_ALLOWED ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - vars.accumulatedRewardsPerNft = (nft == bakc) - ? vaultStorage.bakcPoolState.accumulatedRewardsPerNft - : (nft == bayc) - ? vaultStorage - .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] - .accumulatedRewardsPerNft - : vaultStorage - .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] - .accumulatedRewardsPerNft; - mapping(uint256 => IParaApeStaking.TokenStatus) - storage tokenStatus = (nft == bakc) - ? vaultStorage.bakcPoolState.tokenStatus - : (nft == bayc) - ? vaultStorage - .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] - .tokenStatus - : vaultStorage - .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] - .tokenStatus; - ApeStakingSinglePoolLogic.claimNFT(tokenStatus, vars, nft, tokenIds); + ApeStakingSinglePoolLogic.claimNFT(vaultStorage, vars, nft, tokenIds); } function withdrawNFT(address nft, uint32[] calldata tokenIds) diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 624a789ce..684f377d1 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -52,19 +52,17 @@ library ApeStakingCommonLogic { latestBorrowIndex ); uint256 repayAmount = (debtInterest >= vars.totalClaimedApe) - ? vars.totalClaimedApe - : debtInterest; + ? vars.totalClaimedApe + : debtInterest; cApeDebtShare -= repayAmount.rayDiv(latestBorrowIndex).rayDiv( cApeExchangeRate ); - poolState.cApeDebtShare = cApeDebtShare; + poolState.cApeDebtShare = cApeDebtShare.toUint128(); uint256 compoundFee = 0; if (vars.totalClaimedApe > debtInterest) { uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) .rayDiv(cApeExchangeRate); - compoundFee = shareRewardAmount.percentMul( - vars.compoundFee - ); + compoundFee = shareRewardAmount.percentMul(vars.compoundFee); shareRewardAmount = shareRewardAmount - compoundFee; //update reward index uint128 currentTotalPosition = poolState.totalPosition; diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index cbe50fa49..23f52eb66 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -132,7 +132,7 @@ library ApeStakingPairPoolLogic { emit PairNFTDeposited(isBAYC, apeTokenId, bakcTokenId); } - poolState.totalPosition += arrayLength.toUint64(); + poolState.totalPosition += arrayLength.toUint32(); } function stakingPairNFT( @@ -192,7 +192,7 @@ library ApeStakingPairPoolLogic { vars, totalBorrow ); - poolState.cApeDebtShare += cApeDebtShare; + poolState.cApeDebtShare += cApeDebtShare.toUint128(); //stake in ApeCoinStaking ApeCoinStaking.PairNftDepositWithAmount[] @@ -207,7 +207,7 @@ library ApeStakingPairPoolLogic { vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); } - poolState.stakingPosition += arrayLength.toUint64(); + poolState.stakingPosition += arrayLength.toUint32(); } function withdrawPairNFT( @@ -243,7 +243,7 @@ library ApeStakingPairPoolLogic { memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength ); - uint64 stakingPair = 0; + uint32 stakingPair = 0; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -288,7 +288,7 @@ library ApeStakingPairPoolLogic { } //update state - poolState.totalPosition -= arrayLength.toUint64(); + poolState.totalPosition -= arrayLength.toUint32(); //withdraw from ApeCoinStaking and compound if (stakingPair > 0) { diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 6d4bec4e8..bc2b9c558 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -58,19 +58,19 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage poolState = vaultStorage .poolStates[BAYC_SINGLE_POOL_ID]; accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - poolState.totalPosition += arrayLength.toUint64(); + poolState.totalPosition += arrayLength.toUint32(); } else if (nft == vars.mayc) { nToken = vars.nMayc; IParaApeStaking.PoolState storage poolState = vaultStorage .poolStates[MAYC_SINGLE_POOL_ID]; accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - poolState.totalPosition += arrayLength.toUint64(); + poolState.totalPosition += arrayLength.toUint32(); } else { nToken = vars.nBakc; accumulatedRewardsPerNft = vaultStorage .bakcPoolState .accumulatedRewardsPerNft; - vaultStorage.bakcPoolState.totalPosition += arrayLength.toUint64(); + vaultStorage.bakcPoolState.totalPosition += arrayLength.toUint32(); } mapping(uint256 => IParaApeStaking.TokenStatus) @@ -163,7 +163,7 @@ library ApeStakingSinglePoolLogic { vars, totalBorrow ); - poolState.cApeDebtShare += cApeDebtShare; + poolState.cApeDebtShare += cApeDebtShare.toUint128(); //stake in ApeCoinStaking if (isBAYC) { @@ -172,7 +172,7 @@ library ApeStakingSinglePoolLogic { vars.apeCoinStaking.depositMAYC(_nfts); } - poolState.stakingPosition += arrayLength.toUint64(); + poolState.stakingPosition += arrayLength.toUint32(); } function stakingBAKC( @@ -231,12 +231,12 @@ library ApeStakingSinglePoolLogic { ); if (isBAYC) { vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); - bakcPoolState.baycStakingPosition += arrayLength.toUint64(); - bakcPoolState.baycCApeDebtShare += cApeDebtShare; + bakcPoolState.baycStakingPosition += arrayLength.toUint32(); + bakcPoolState.baycCApeDebtShare += cApeDebtShare.toUint128(); } else { vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); - bakcPoolState.maycStakingPosition += arrayLength.toUint64(); - bakcPoolState.maycCApeDebtShare += cApeDebtShare; + bakcPoolState.maycStakingPosition += arrayLength.toUint32(); + bakcPoolState.maycCApeDebtShare += cApeDebtShare.toUint128(); } } @@ -381,7 +381,7 @@ library ApeStakingSinglePoolLogic { } function claimNFT( - mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus, + IParaApeStaking.VaultStorage storage vaultStorage, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds @@ -389,6 +389,14 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); + vars.accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( + vaultStorage, + vars, + nft + ); + mapping(uint256 => IParaApeStaking.TokenStatus) + storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); + _claimNFT(tokenStatus, vars, nft, tokenIds); } @@ -402,48 +410,27 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - address nToken; - if (nft == vars.bayc) { - vars.accumulatedRewardsPerNft = vaultStorage - .poolStates[BAYC_SINGLE_POOL_ID] - .accumulatedRewardsPerNft; - nToken = vars.nBayc; - } else if (nft == vars.mayc) { - vars.accumulatedRewardsPerNft = vaultStorage - .poolStates[MAYC_SINGLE_POOL_ID] - .accumulatedRewardsPerNft; - nToken = vars.nMayc; - } else { - vars.accumulatedRewardsPerNft = vaultStorage - .bakcPoolState - .accumulatedRewardsPerNft; - nToken = vars.nBakc; - } - + vars.accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( + vaultStorage, + vars, + nft + ); mapping(uint256 => IParaApeStaking.TokenStatus) - storage tokenStatus = (nft == vars.bakc) - ? vaultStorage.bakcPoolState.tokenStatus - : (nft == vars.bayc) - ? vaultStorage - .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] - .tokenStatus - : vaultStorage - .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] - .tokenStatus; + storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); //claim pending reward _claimNFT(tokenStatus, vars, nft, tokenIds); - if (nft == vars.bayc || nft == vars.mayc) { - _unstakeApe( - vaultStorage, - cApeShareBalance, - vars, - (nft == vars.bayc), - tokenIds - ); + address nToken; + if (nft == vars.bayc) { + _unstakeApe(vaultStorage, cApeShareBalance, vars, true, tokenIds); + nToken = vars.nBayc; + } else if (nft == vars.mayc) { + _unstakeApe(vaultStorage, cApeShareBalance, vars, false, tokenIds); + nToken = vars.nMayc; } else { _unstakeBAKC(vaultStorage, cApeShareBalance, vars, tokenIds); + nToken = vars.nBakc; } //transfer nft back to nToken @@ -483,7 +470,7 @@ library ApeStakingSinglePoolLogic { vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; } - apePoolState.totalPosition -= tokenIds.length.toUint64(); + apePoolState.totalPosition -= tokenIds.length.toUint32(); ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](tokenIds.length); @@ -491,8 +478,8 @@ library ApeStakingSinglePoolLogic { memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( tokenIds.length ); - uint64 singleStakingCount; - uint64 pairStakingCount; + uint32 singleStakingCount; + uint32 pairStakingCount; for (uint256 index = 0; index < tokenIds.length; index++) { uint32 tokenId = tokenIds[index]; @@ -621,7 +608,7 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; IParaApeStaking.BAKCPoolState storage bakcPoolState = vaultStorage .bakcPoolState; - vaultStorage.bakcPoolState.totalPosition -= arrayLength.toUint64(); + vaultStorage.bakcPoolState.totalPosition -= arrayLength.toUint32(); ApeCoinStaking.PairNftWithdrawWithAmount[] memory baycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength @@ -630,8 +617,8 @@ library ApeStakingSinglePoolLogic { memory maycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength ); - uint64 baycPairCount; - uint64 maycPairCount; + uint32 baycPairCount; + uint32 maycPairCount; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -738,6 +725,29 @@ library ApeStakingSinglePoolLogic { } function calculatePendingReward( + IParaApeStaking.VaultStorage storage vaultStorage, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) external view returns (uint256) { + vars.accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( + vaultStorage, + vars, + nft + ); + mapping(uint256 => IParaApeStaking.TokenStatus) + storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); + + (, uint256 pendingReward) = _calculatePendingReward( + tokenStatus, + vars, + nft, + tokenIds + ); + return pendingReward; + } + + function _calculatePendingReward( mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, @@ -781,7 +791,7 @@ library ApeStakingSinglePoolLogic { address nft, uint32[] calldata tokenIds ) internal { - (address owner, uint256 pendingReward) = calculatePendingReward( + (address owner, uint256 pendingReward) = _calculatePendingReward( tokenStatus, vars, nft, @@ -843,9 +853,9 @@ library ApeStakingSinglePoolLogic { cApeExchangeRate ); if (isBAYC) { - bakcPoolState.baycCApeDebtShare = cApeDebtShare; + bakcPoolState.baycCApeDebtShare = cApeDebtShare.toUint128(); } else { - bakcPoolState.maycCApeDebtShare = cApeDebtShare; + bakcPoolState.maycCApeDebtShare = cApeDebtShare.toUint128(); } } @@ -881,4 +891,38 @@ library ApeStakingSinglePoolLogic { return (repayAmount, compoundFee); } + + function _getPoolAccumulatedRewardsPerNft( + IParaApeStaking.VaultStorage storage vaultStorage, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft + ) internal view returns (uint128) { + return + (nft == vars.bakc) + ? vaultStorage.bakcPoolState.accumulatedRewardsPerNft + : (nft == vars.bayc) + ? vaultStorage + .poolStates[BAYC_SINGLE_POOL_ID] + .accumulatedRewardsPerNft + : vaultStorage + .poolStates[MAYC_SINGLE_POOL_ID] + .accumulatedRewardsPerNft; + } + + function _getPoolTokenStatus( + IParaApeStaking.VaultStorage storage vaultStorage, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft + ) + internal + view + returns (mapping(uint256 => IParaApeStaking.TokenStatus) storage) + { + return + (nft == vars.bakc) + ? vaultStorage.bakcPoolState.tokenStatus + : (nft == vars.bayc) + ? vaultStorage.poolStates[BAYC_SINGLE_POOL_ID].tokenStatus + : vaultStorage.poolStates[MAYC_SINGLE_POOL_ID].tokenStatus; + } } diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index 4a70e20c5..9bc8f21b4 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -16,31 +16,33 @@ interface IApeStakingVault { bool isInPool; } struct PoolState { - // accumulated cApe reward for per NFT position - uint128 accumulatedRewardsPerNft; + //pool cape debt token share + uint128 cApeDebtShare; // total NFT position count - uint64 totalPosition; + uint32 totalPosition; // total staking position - uint64 stakingPosition; + uint32 stakingPosition; + // accumulated cApe reward for per NFT position + uint128 accumulatedRewardsPerNft; //tokenId => reward debt position mapping(uint256 => TokenStatus) tokenStatus; //for pair pool, apeTokenId => PairingStatus mapping(uint256 => PairingStatus) pairStatus; - //pool cape debt token share - uint256 cApeDebtShare; } struct BAKCPoolState { + // total NFT position count + uint32 totalPosition; + //bayc pair cape debt token share + uint128 baycCApeDebtShare; + //bayc pair staking position + uint32 baycStakingPosition; + //mayc pair cape debt token share + uint128 maycCApeDebtShare; + //mayc pair staking position + uint32 maycStakingPosition; // accumulated cApe reward for per NFT position uint128 accumulatedRewardsPerNft; - // total NFT position count - uint64 totalPosition; - // total staking position - uint64 baycStakingPosition; - uint64 maycStakingPosition; - //pool cape debt token share - uint256 baycCApeDebtShare; - uint256 maycCApeDebtShare; //tokenId => reward debt position mapping(uint256 => TokenStatus) tokenStatus; } From 610e8ed55a3778a699ecf0781ca8a3a34392f608 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 13 Jul 2023 14:13:11 +0800 Subject: [PATCH 53/99] chore: refactor and gas optimization --- contracts/apestaking/ParaApeStaking.sol | 19 +- .../logic/ApeStakingCommonLogic.sol | 19 +- .../logic/ApeStakingPairPoolLogic.sol | 22 +- .../logic/ApeStakingSinglePoolLogic.sol | 165 ++++----- contracts/interfaces/IApeStakingP2P.sol | 3 - contracts/interfaces/IApeStakingVault.sol | 3 - contracts/interfaces/IParaApeStaking.sol | 3 +- test/para_ape_staking.spec.ts | 320 ++++++++++++++++-- 8 files changed, 419 insertions(+), 135 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index cbca26d35..2bc142f2d 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -352,7 +352,7 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused { + ) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID @@ -370,7 +370,7 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused { + ) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID @@ -388,7 +388,7 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused onlyApeStakingBot { + ) external onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC @@ -430,7 +430,7 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused { + ) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID @@ -448,7 +448,7 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused { + ) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC @@ -467,6 +467,7 @@ contract ParaApeStaking is function depositNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused + nonReentrant { require( nft == bayc || nft == mayc || nft == bakc, @@ -479,6 +480,7 @@ contract ParaApeStaking is function stakingApe(bool isBAYC, uint32[] calldata tokenIds) external whenNotPaused + nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC @@ -496,7 +498,7 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused { + ) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID @@ -513,7 +515,6 @@ contract ParaApeStaking is function compoundApe(bool isBAYC, uint32[] calldata tokenIds) external - whenNotPaused onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); @@ -534,7 +535,7 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused onlyApeStakingBot { + ) external onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; ApeStakingSinglePoolLogic.compoundBAKC( @@ -566,6 +567,7 @@ contract ParaApeStaking is function claimNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused + nonReentrant { require( nft == bayc || nft == mayc || nft == bakc, @@ -578,6 +580,7 @@ contract ParaApeStaking is function withdrawNFT(address nft, uint32[] calldata tokenIds) external whenNotPaused + nonReentrant { require( nft == bayc || nft == mayc || nft == bakc, diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 684f377d1..6f70c8ed0 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -2,12 +2,9 @@ pragma solidity 0.8.10; import "../../interfaces/IParaApeStaking.sol"; -import {IERC20, SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; -import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; import "../../interfaces/IAutoCompoundApe.sol"; import "../../interfaces/ICApe.sol"; -import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import {IPool} from "../../interfaces/IPool.sol"; @@ -20,7 +17,6 @@ import {IPool} from "../../interfaces/IPool.sol"; library ApeStakingCommonLogic { using PercentageMath for uint256; using SafeCast for uint256; - using SafeERC20 for IERC20; using WadRayMath for uint256; function depositCApeShareForUser( @@ -38,30 +34,25 @@ library ApeStakingCommonLogic { IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint256 positionCap ) internal returns (uint256, uint256) { - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); - uint256 latestBorrowIndex = IPool(vars.pool) - .getReserveNormalizedVariableDebt(vars.cApe); uint256 cApeDebtShare = poolState.cApeDebtShare; uint256 debtInterest = calculateCurrentPositionDebtInterest( cApeDebtShare, poolState.stakingPosition, positionCap, - cApeExchangeRate, - latestBorrowIndex + vars.cApeExchangeRate, + vars.latestBorrowIndex ); uint256 repayAmount = (debtInterest >= vars.totalClaimedApe) ? vars.totalClaimedApe : debtInterest; - cApeDebtShare -= repayAmount.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate + cApeDebtShare -= repayAmount.rayDiv(vars.latestBorrowIndex).rayDiv( + vars.cApeExchangeRate ); poolState.cApeDebtShare = cApeDebtShare.toUint128(); uint256 compoundFee = 0; if (vars.totalClaimedApe > debtInterest) { uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) - .rayDiv(cApeExchangeRate); + .rayDiv(vars.cApeExchangeRate); compoundFee = shareRewardAmount.percentMul(vars.compoundFee); shareRewardAmount = shareRewardAmount - compoundFee; //update reward index diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 23f52eb66..fd433c9e6 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -294,13 +294,11 @@ library ApeStakingPairPoolLogic { if (stakingPair > 0) { poolState.stakingPosition -= stakingPair; - { - assembly { - mstore(_nfts, stakingPair) - } - assembly { - mstore(_nftPairs, stakingPair) - } + assembly { + mstore(_nfts, stakingPair) + } + assembly { + mstore(_nftPairs, stakingPair) } vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); @@ -323,6 +321,11 @@ library ApeStakingPairPoolLogic { vars.totalClaimedApe ); + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + vars.latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic .calculateRepayAndCompound( poolState, @@ -444,6 +447,11 @@ library ApeStakingPairPoolLogic { //repay and compound vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + vars.latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic .calculateRepayAndCompound( poolState, diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index bc2b9c558..edf176f55 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -51,38 +51,16 @@ library ApeStakingSinglePoolLogic { require(arrayLength > 0, Errors.INVALID_PARAMETER); address msgSender = msg.sender; - address nToken; - uint128 accumulatedRewardsPerNft; - if (nft == vars.bayc) { - nToken = vars.nBayc; - IParaApeStaking.PoolState storage poolState = vaultStorage - .poolStates[BAYC_SINGLE_POOL_ID]; - accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - poolState.totalPosition += arrayLength.toUint32(); - } else if (nft == vars.mayc) { - nToken = vars.nMayc; - IParaApeStaking.PoolState storage poolState = vaultStorage - .poolStates[MAYC_SINGLE_POOL_ID]; - accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - poolState.totalPosition += arrayLength.toUint32(); - } else { - nToken = vars.nBakc; - accumulatedRewardsPerNft = vaultStorage - .bakcPoolState - .accumulatedRewardsPerNft; - vaultStorage.bakcPoolState.totalPosition += arrayLength.toUint32(); - } - + address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; + uint128 accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( + vaultStorage, + vars, + nft + ); mapping(uint256 => IParaApeStaking.TokenStatus) - storage tokenStatus = (nft == vars.bakc) - ? vaultStorage.bakcPoolState.tokenStatus - : (nft == vars.bayc) - ? vaultStorage - .poolStates[ApeStakingSinglePoolLogic.BAYC_SINGLE_POOL_ID] - .tokenStatus - : vaultStorage - .poolStates[ApeStakingSinglePoolLogic.MAYC_SINGLE_POOL_ID] - .tokenStatus; + storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -125,6 +103,18 @@ library ApeStakingSinglePoolLogic { //emit event emit NFTDeposited(nft, tokenId); } + + if (nft == vars.bayc) { + vaultStorage + .poolStates[BAYC_SINGLE_POOL_ID] + .totalPosition += arrayLength.toUint32(); + } else if (nft == vars.mayc) { + vaultStorage + .poolStates[MAYC_SINGLE_POOL_ID] + .totalPosition += arrayLength.toUint32(); + } else { + vaultStorage.bakcPoolState.totalPosition += arrayLength.toUint32(); + } } function stakingApe( @@ -138,7 +128,9 @@ library ApeStakingSinglePoolLogic { ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); - vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; + uint256 positionCap = isBAYC + ? vars.baycMatchedCap + : vars.maycMatchedCap; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -150,7 +142,7 @@ library ApeStakingSinglePoolLogic { // construct staking data _nfts[index] = ApeCoinStaking.SingleNft({ tokenId: tokenId, - amount: vars.positionCap.toUint224() + amount: positionCap.toUint224() }); //emit event @@ -158,7 +150,7 @@ library ApeStakingSinglePoolLogic { } // prepare Ape coin - uint256 totalBorrow = vars.positionCap * arrayLength; + uint256 totalBorrow = positionCap * arrayLength; uint256 cApeDebtShare = ApeStakingCommonLogic.borrowCApeFromPool( vars, totalBorrow @@ -283,6 +275,11 @@ library ApeStakingSinglePoolLogic { ); //repay and compound + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + vars.latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic .calculateRepayAndCompound(poolState, vars, vars.positionCap); @@ -361,6 +358,11 @@ library ApeStakingSinglePoolLogic { } //repay and compound + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + vars.latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); ( vars.totalRepay, vars.totalCompoundFee @@ -525,6 +527,11 @@ library ApeStakingSinglePoolLogic { bakcPoolState.maycStakingPosition -= pairStakingCount; } + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + vars.latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); if (singleStakingCount > 0) { assembly { mstore(_nfts, singleStakingCount) @@ -665,7 +672,11 @@ library ApeStakingSinglePoolLogic { memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( 0 ); - + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + vars.latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); if (baycPairCount > 0) { bakcPoolState.baycStakingPosition -= baycPairCount; @@ -693,7 +704,7 @@ library ApeStakingSinglePoolLogic { bakcPoolState.maycStakingPosition -= maycPairCount; vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - vars.apeCoinStaking.withdrawBAKC(baycPair, maycPair); + vars.apeCoinStaking.withdrawBAKC(_otherPairs, maycPair); vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; IAutoCompoundApe(vars.cApe).deposit( @@ -738,10 +749,14 @@ library ApeStakingSinglePoolLogic { mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); + address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; + (, uint256 pendingReward) = _calculatePendingReward( tokenStatus, vars, - nft, + nToken, tokenIds ); return pendingReward; @@ -750,14 +765,12 @@ library ApeStakingSinglePoolLogic { function _calculatePendingReward( mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft, + address nToken, uint32[] calldata tokenIds - ) public view returns (address claimFor, uint256 pendingReward) { + ) internal view returns (address claimFor, uint256 pendingReward) { uint256 rewardShares; uint256 arrayLength = tokenIds.length; - address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) - ? vars.nMayc - : vars.nBakc; + uint128 accumulatedRewardsPerNft = vars.accumulatedRewardsPerNft; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -777,7 +790,7 @@ library ApeStakingSinglePoolLogic { ); //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - rewardShares += (vars.accumulatedRewardsPerNft - + rewardShares += (accumulatedRewardsPerNft - tokenStatus[tokenId].rewardsDebt); } pendingReward = ICApe(vars.cApe).getPooledApeByShares(rewardShares); @@ -791,10 +804,13 @@ library ApeStakingSinglePoolLogic { address nft, uint32[] calldata tokenIds ) internal { + address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; (address owner, uint256 pendingReward) = _calculatePendingReward( tokenStatus, vars, - nft, + nToken, tokenIds ); @@ -820,43 +836,36 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC ) internal returns (uint256, uint256) { - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); - uint256 latestBorrowIndex = IPool(vars.pool) - .getReserveNormalizedVariableDebt(vars.cApe); uint256 repayAmount = 0; uint256 debtInterest = 0; //calculate repay - { - uint256 cApeDebtShare; - uint256 stakingPosition; - if (isBAYC) { - cApeDebtShare = bakcPoolState.baycCApeDebtShare; - stakingPosition = bakcPoolState.baycStakingPosition; - } else { - cApeDebtShare = bakcPoolState.maycCApeDebtShare; - stakingPosition = bakcPoolState.maycStakingPosition; - } - debtInterest = ApeStakingCommonLogic - .calculateCurrentPositionDebtInterest( - cApeDebtShare, - stakingPosition, - vars.bakcMatchedCap, - cApeExchangeRate, - latestBorrowIndex - ); - repayAmount = (debtInterest >= vars.totalClaimedApe) - ? vars.totalClaimedApe - : debtInterest; - cApeDebtShare -= repayAmount.rayDiv(latestBorrowIndex).rayDiv( - cApeExchangeRate + uint256 cApeDebtShare; + uint256 stakingPosition; + if (isBAYC) { + cApeDebtShare = bakcPoolState.baycCApeDebtShare; + stakingPosition = bakcPoolState.baycStakingPosition; + } else { + cApeDebtShare = bakcPoolState.maycCApeDebtShare; + stakingPosition = bakcPoolState.maycStakingPosition; + } + debtInterest = ApeStakingCommonLogic + .calculateCurrentPositionDebtInterest( + cApeDebtShare, + stakingPosition, + vars.bakcMatchedCap, + vars.cApeExchangeRate, + vars.latestBorrowIndex ); - if (isBAYC) { - bakcPoolState.baycCApeDebtShare = cApeDebtShare.toUint128(); - } else { - bakcPoolState.maycCApeDebtShare = cApeDebtShare.toUint128(); - } + repayAmount = (debtInterest >= vars.totalClaimedApe) + ? vars.totalClaimedApe + : debtInterest; + cApeDebtShare -= repayAmount.rayDiv(vars.latestBorrowIndex).rayDiv( + vars.cApeExchangeRate + ); + if (isBAYC) { + bakcPoolState.baycCApeDebtShare = cApeDebtShare.toUint128(); + } else { + bakcPoolState.maycCApeDebtShare = cApeDebtShare.toUint128(); } //calculate compound fee @@ -864,7 +873,7 @@ library ApeStakingSinglePoolLogic { if (vars.totalClaimedApe > debtInterest) { //update reward index uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) - .rayDiv(cApeExchangeRate); + .rayDiv(vars.cApeExchangeRate); compoundFee = shareRewardAmount.percentMul(vars.compoundFee); shareRewardAmount = shareRewardAmount - compoundFee; uint256 apeShareAmount = shareRewardAmount.percentMul( diff --git a/contracts/interfaces/IApeStakingP2P.sol b/contracts/interfaces/IApeStakingP2P.sol index a7cf8cfb0..d610676cd 100644 --- a/contracts/interfaces/IApeStakingP2P.sol +++ b/contracts/interfaces/IApeStakingP2P.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.10; -import "../dependencies/openzeppelin/contracts/IERC20.sol"; -import "../dependencies/yoga-labs/ApeCoinStaking.sol"; - interface IApeStakingP2P { enum StakingType { BAYCStaking, diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index 9bc8f21b4..120130596 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -1,9 +1,6 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.10; -import "../dependencies/openzeppelin/contracts/IERC20.sol"; -import "../dependencies/yoga-labs/ApeCoinStaking.sol"; - interface IApeStakingVault { struct PairingStatus { uint248 tokenId; diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index cff53991c..2df2a18c6 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.10; -import "../dependencies/openzeppelin/contracts/IERC20.sol"; import "../dependencies/yoga-labs/ApeCoinStaking.sol"; import "./IApeStakingVault.sol"; import "./IApeStakingP2P.sol"; @@ -35,6 +34,8 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { uint256 apeRewardRatio; uint256 totalRepay; uint256 totalCompoundFee; + uint256 cApeExchangeRate; + uint256 latestBorrowIndex; bool isPaired; } diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 4ae689662..957f68385 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -1293,27 +1293,6 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking.connect(user4.signer).compoundApe(false, [2, 3]) ); - // - // tx0 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ - // true, - // [0, 1], - // [0, 1] - // ]); - // tx1 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ - // false, - // [0, 1], - // [2, 3] - // ]); - // tx2 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ - // true, - // [2,3] - // ]); - // tx3 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ - // false, - // [2,3] - // ]); - // - // await waitForTx(await paraApeStaking.connect(user4.signer).multicall([tx0, tx1, tx2, tx3])); tx0 = paraApeStaking.interface.encodeFunctionData("claimPairNFT", [ true, @@ -1361,4 +1340,303 @@ describe("Para Ape Staking Test", () => { .withdrawNFT(mayc.address, [2, 3]) ); }); + + it("ape pair staking reward ratio test", async () => { + const { + users: [user1, user2, user3, user4], + bayc, + mayc, + bakc, + poolAdmin, + } = await loadFixture(fixture); + + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .setSinglePoolApeRewardRatio(6000, 5000) + ); + + await supplyAndValidate(bayc, "2", user1, true); + await supplyAndValidate(mayc, "2", user2, true); + await supplyAndValidate(bakc, "4", user3, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositNFT(mayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .depositNFT(bakc.address, [0, 1, 2, 3]) + ); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingBAKC(true, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingBAKC(false, [0, 1], [2, 3]) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundBAKC(true, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundBAKC(false, [0, 1], [2, 3]) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).claimNFT(mayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .claimNFT(bakc.address, [0, 1, 2, 3]) + ); + + //user1: 900 * 0.5 * 2 + expect(await cApe.balanceOf(user1.address)).to.be.closeTo( + parseEther("1080"), + parseEther("50") + ); + //user2: 900 * 0.6 * 2 + expect(await cApe.balanceOf(user2.address)).to.be.closeTo( + parseEther("900"), + parseEther("50") + ); + //user3: 900 * 0.5 * 2 + 900 * 0.4 * 2 + expect(await cApe.balanceOf(user3.address)).to.be.closeTo( + parseEther("1620"), + parseEther("50") + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .withdrawNFT(bayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .withdrawNFT(mayc.address, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .withdrawNFT(bakc.address, [0, 1, 2, 3]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) + ); + + //user1: 1080 + expect(await cApe.balanceOf(user1.address)).to.be.closeTo( + parseEther("1080"), + parseEther("50") + ); + //user2: 900 + expect(await cApe.balanceOf(user2.address)).to.be.closeTo( + parseEther("900"), + parseEther("50") + ); + //user3: 1620 * 2 + expect(await cApe.balanceOf(user3.address)).to.be.closeTo( + parseEther("3240"), + parseEther("50") + ); + expect(await cApe.balanceOf(user4.address)).to.be.closeTo( + parseEther("1980"), + parseEther("50") + ); + }); + + it("test bakc single pool logic0", async () => { + const { + users: [user1, user2, user3, user4], + bayc, + mayc, + bakc, + poolAdmin, + } = await loadFixture(fixture); + + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .setSinglePoolApeRewardRatio(5000, 5000) + ); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(mayc, "3", user2, true); + await supplyAndValidate(bakc, "4", user3, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositNFT(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .depositNFT(bakc.address, [0, 1, 2, 3]) + ); + + await waitForTx( + await paraApeStaking.connect(user4.signer).stakingApe(true, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).stakingApe(false, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingBAKC(true, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingBAKC(false, [0, 1], [2, 3]) + ); + expect( + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + ).to.be.closeTo(parseEther("1100000"), parseEther("10")); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundBAKC(true, [0, 1], [0, 1]) + ); + const user3PendingReward0 = await paraApeStaking.nftPendingReward( + bakc.address, + [0, 1, 2, 3] + ); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundBAKC(false, [0, 1], [2, 3]) + ); + const user3PendingReward1 = await paraApeStaking.nftPendingReward( + bakc.address, + [0, 1, 2, 3] + ); + const user1PendingReward = await paraApeStaking.nftPendingReward( + bayc.address, + [0, 1] + ); + const user2PendingReward = await paraApeStaking.nftPendingReward( + mayc.address, + [0, 1] + ); + expect(user3PendingReward0.mul(2)).to.be.closeTo( + user3PendingReward1, + parseEther("1") + ); + expect(user1PendingReward).to.be.closeTo( + user2PendingReward, + parseEther("1") + ); + }); + + it("test bakc single pool logic1", async () => { + const { + users: [user1, user2, user3, user4], + bayc, + mayc, + bakc, + poolAdmin, + } = await loadFixture(fixture); + + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .setSinglePoolApeRewardRatio(5000, 5000) + ); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(mayc, "3", user2, true); + await supplyAndValidate(bakc, "4", user3, true); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositNFT(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositNFT(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .depositNFT(bakc.address, [0, 1, 2, 3]) + ); + + await waitForTx( + await paraApeStaking.connect(user4.signer).stakingApe(true, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).stakingApe(false, [0, 1, 2]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingBAKC(true, [0, 1], [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .stakingBAKC(false, [0, 1], [2, 3]) + ); + expect( + await variableDebtCApeCoin.balanceOf(paraApeStaking.address) + ).to.be.closeTo(parseEther("1100000"), parseEther("10")); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user3.signer) + .withdrawNFT(bakc.address, [0, 1, 2, 3]) + ); + const user1PendingReward = await paraApeStaking.nftPendingReward( + bayc.address, + [0, 1] + ); + const user2PendingReward = await paraApeStaking.nftPendingReward( + mayc.address, + [0, 1] + ); + expect(user1PendingReward).to.be.closeTo( + user2PendingReward, + parseEther("1") + ); + }); }); From 73a73eca06149bc3834b36d0f2d10e9a4294d2cc Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 13 Jul 2023 17:06:47 +0800 Subject: [PATCH 54/99] chore: fix p2p logic --- contracts/apestaking/ParaApeStaking.sol | 8 +- .../apestaking/logic/ApeStakingP2PLogic.sol | 61 ++- contracts/interfaces/IApeStakingP2P.sol | 3 +- .../protocol/libraries/helpers/Errors.sol | 11 + helpers/types.ts | 11 + test/helpers/p2ppairstaking-helper.ts | 3 +- test/p2p_pair_staking.spec.ts | 384 +++++++++--------- test/para_ape_staking.spec.ts | 2 +- 8 files changed, 250 insertions(+), 233 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 2bc142f2d..65fdf03bc 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -207,13 +207,13 @@ contract ParaApeStaking is external nonReentrant { - require(msg.sender == listingOrder.offerer, "not order offerer"); + require(msg.sender == listingOrder.offerer, Errors.NOT_ORDER_OFFERER); bytes32 orderHash = ApeStakingP2PLogic.getListingOrderHash( listingOrder ); require( listingOrderStatus[orderHash] != ListingOrderStatus.Cancelled, - "order already cancelled" + Errors.ORDER_ALREADY_CANCELLED ); listingOrderStatus[orderHash] = ListingOrderStatus.Cancelled; @@ -247,7 +247,7 @@ contract ParaApeStaking is ) external nonReentrant whenNotPaused returns (bytes32 orderHash) { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.DOMAIN_SEPARATOR = DOMAIN_SEPARATOR; - orderHash = ApeStakingP2PLogic.matchPairStakingList( + orderHash = ApeStakingP2PLogic.matchBAKCPairStakingList( apeOrder, bakcOrder, apeCoinOrder, @@ -268,6 +268,7 @@ contract ParaApeStaking is whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; ApeStakingP2PLogic.breakUpMatchedOrder( listingOrderStatus, matchedOrders, @@ -287,6 +288,7 @@ contract ParaApeStaking is whenNotPaused { ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; ApeStakingP2PLogic.claimForMatchedOrdersAndCompound( matchedOrders, cApeShareBalance, diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index 0f5ea0724..709cec945 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -12,6 +12,7 @@ import {SignatureChecker} from "../../dependencies/looksrare/contracts/libraries import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import "./ApeStakingCommonLogic.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; +import "../../protocol/libraries/helpers/Errors.sol"; /** * @title ApeStakingVaultLogic library @@ -57,16 +58,16 @@ library ApeStakingP2PLogic { require( apeOrder.stakingType == IApeStakingP2P.StakingType.MAYCStaking || apeOrder.stakingType == IApeStakingP2P.StakingType.BAYCStaking, - "invalid stake type" + Errors.INVALID_STAKING_TYPE ); require( apeOrder.stakingType == apeCoinOrder.stakingType, - "orders type match failed" + Errors.ORDER_TYPE_MATCH_FAILED ); require( apeOrder.share + apeCoinOrder.share == PercentageMath.PERCENTAGE_FACTOR, - "orders share match failed" + Errors.ORDER_SHARE_MATCH_FAILED ); //3 transfer token @@ -110,7 +111,7 @@ library ApeStakingP2PLogic { return orderHash; } - function matchPairStakingList( + function matchBAKCPairStakingList( IApeStakingP2P.ListingOrder calldata apeOrder, IApeStakingP2P.ListingOrder calldata bakcOrder, IApeStakingP2P.ListingOrder calldata apeCoinOrder, @@ -131,21 +132,18 @@ library ApeStakingP2PLogic { //2 check if orders can match require( - apeOrder.stakingType == - IApeStakingP2P.StakingType.BAYCPairStaking || - apeOrder.stakingType == - IApeStakingP2P.StakingType.MAYCPairStaking, - "invalid stake type" + apeOrder.stakingType == IApeStakingP2P.StakingType.BAKCPairStaking, + Errors.INVALID_STAKING_TYPE ); require( apeOrder.stakingType == bakcOrder.stakingType && apeOrder.stakingType == apeCoinOrder.stakingType, - "orders type match failed" + Errors.ORDER_TYPE_MATCH_FAILED ); require( apeOrder.share + bakcOrder.share + apeCoinOrder.share == PercentageMath.PERCENTAGE_FACTOR, - "share match failed" + Errors.ORDER_SHARE_MATCH_FAILED ); //3 transfer token @@ -220,11 +218,9 @@ library ApeStakingP2PLogic { msg.sender == apeNTokenOwner || msg.sender == order.apeCoinOfferer || (msg.sender == nBakcOwner && - (order.stakingType == - IApeStakingP2P.StakingType.BAYCPairStaking || - order.stakingType == - IApeStakingP2P.StakingType.MAYCPairStaking)), - "no permission to break up" + order.stakingType == + IApeStakingP2P.StakingType.BAKCPairStaking), + Errors.NO_BREAK_UP_PERMISSION ); //2 claim pending reward and compound @@ -290,10 +286,7 @@ library ApeStakingP2PLogic { order.apeCoinOfferer, order.apePrincipleAmount ); - if ( - order.stakingType == IApeStakingP2P.StakingType.BAYCPairStaking || - order.stakingType == IApeStakingP2P.StakingType.MAYCPairStaking - ) { + if (order.stakingType == IApeStakingP2P.StakingType.BAKCPairStaking) { IERC721(vars.bakc).safeTransferFrom( address(this), vars.nBakc, @@ -417,10 +410,7 @@ library ApeStakingP2PLogic { order.apeCoinOfferer, rewardShare.percentMul(order.apeCoinShare) ); - if ( - order.stakingType == IApeStakingP2P.StakingType.BAYCPairStaking || - order.stakingType == IApeStakingP2P.StakingType.MAYCPairStaking - ) { + if (order.stakingType == IApeStakingP2P.StakingType.BAKCPairStaking) { ApeStakingCommonLogic.depositCApeShareForUser( cApeShareBalance, IERC721(vars.nBakc).ownerOf(order.bakcTokenId), @@ -441,15 +431,15 @@ library ApeStakingP2PLogic { ) internal view returns (bytes32 orderHash) { require( listingOrder.startTime <= block.timestamp, - "ape order not start" + Errors.ORDER_NOT_STARTED ); - require(listingOrder.endTime >= block.timestamp, "ape offer expired"); + require(listingOrder.endTime >= block.timestamp, Errors.ORDER_EXPIRED); orderHash = getListingOrderHash(listingOrder); require( listingOrderStatus[orderHash] != IApeStakingP2P.ListingOrderStatus.Cancelled, - "order already cancelled" + Errors.ORDER_ALREADY_CANCELLED ); if (msg.sender != listingOrder.offerer) { @@ -462,7 +452,7 @@ library ApeStakingP2PLogic { listingOrder.r, listingOrder.s ), - "invalid signature" + Errors.INVALID_SIGNATURE ); } } @@ -482,7 +472,7 @@ library ApeStakingP2PLogic { address nToken = _getApeNTokenAddress(vars, apeOrder.token); require( IERC721(nToken).ownerOf(apeOrder.tokenId) == apeOrder.offerer, - "ape order invalid NToken owner" + Errors.NOT_THE_OWNER ); } @@ -497,14 +487,11 @@ library ApeStakingP2PLogic { listingOrderStatus, apeCoinOrder ); - require( - apeCoinOrder.token == vars.cApe, - "ape coin order invalid token" - ); + require(apeCoinOrder.token == vars.cApe, Errors.INVALID_TOKEN); require( listingOrderStatus[orderHash] != IApeStakingP2P.ListingOrderStatus.Matched, - "ape coin order already matched" + Errors.ORDER_ALREADY_MATCHED ); } @@ -520,10 +507,10 @@ library ApeStakingP2PLogic { bakcOrder ); - require(bakcOrder.token == vars.bakc, "bakc order invalid token"); + require(bakcOrder.token == vars.bakc, Errors.INVALID_TOKEN); require( IERC721(vars.nBakc).ownerOf(bakcOrder.tokenId) == bakcOrder.offerer, - "bakc order invalid NToken owner" + Errors.NOT_THE_OWNER ); } @@ -568,7 +555,7 @@ library ApeStakingP2PLogic { } else if (apeToken == vars.mayc) { return vars.nMayc; } else { - revert("unsupported ape token"); + revert(Errors.INVALID_TOKEN); } } diff --git a/contracts/interfaces/IApeStakingP2P.sol b/contracts/interfaces/IApeStakingP2P.sol index d610676cd..9497a6f1a 100644 --- a/contracts/interfaces/IApeStakingP2P.sol +++ b/contracts/interfaces/IApeStakingP2P.sol @@ -5,8 +5,7 @@ interface IApeStakingP2P { enum StakingType { BAYCStaking, MAYCStaking, - BAYCPairStaking, - MAYCPairStaking + BAKCPairStaking } enum ListingOrderStatus { diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index c98610c8d..b20bbba60 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -144,4 +144,15 @@ library Errors { string public constant NOT_THE_SAME_OWNER = "176"; //not the same owner string public constant NFT_NOT_ALLOWED = "177"; //nft now allowed string public constant NFT_NOT_IN_SINGLE_POOL = "178"; //nft not in single pool + + string public constant NOT_ORDER_OFFERER = "180"; //not order offerer + string public constant ORDER_ALREADY_CANCELLED = "181"; //order already cancelled + string public constant ORDER_NOT_STARTED = "182"; //order not started + string public constant ORDER_EXPIRED = "183"; //order expired + string public constant INVALID_TOKEN = "184"; //invalid token + string public constant ORDER_ALREADY_MATCHED = "185"; //order matched + string public constant INVALID_STAKING_TYPE = "186"; //invalid stake type + string public constant ORDER_TYPE_MATCH_FAILED = "187"; //orders type match failed + string public constant ORDER_SHARE_MATCH_FAILED = "188"; //orders share match failed + string public constant NO_BREAK_UP_PERMISSION = "189"; //no permission to break up } diff --git a/helpers/types.ts b/helpers/types.ts index 2df460111..1efb5403f 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -425,6 +425,17 @@ export enum ProtocolErrors { NOT_THE_SAME_OWNER = "176", //not the same owner NFT_NOT_IN_SINGLE_POOL = "178", //nft not in single pool + NOT_ORDER_OFFERER = "180", //not order offerer + ORDER_ALREADY_CANCELLED = "181", //order already cancelled + ORDER_NOT_STARTED = "182", //order not started + ORDER_EXPIRED = "183", //order expired + INVALID_TOKEN = "184", //invalid token + ORDER_ALREADY_MATCHED = "185", //order matched + INVALID_STAKING_TYPE = "186", //invalid stake type + ORDER_TYPE_MATCH_FAILED = "187", //orders type match failed + ORDER_SHARE_MATCH_FAILED = "188", //orders share match failed + NO_BREAK_UP_PERMISSION = "189", //no permission to break up + // SafeCast SAFECAST_UINT128_OVERFLOW = "SafeCast: value doesn't fit in 128 bits", diff --git a/test/helpers/p2ppairstaking-helper.ts b/test/helpers/p2ppairstaking-helper.ts index 41bb169cf..14f06f7ef 100644 --- a/test/helpers/p2ppairstaking-helper.ts +++ b/test/helpers/p2ppairstaking-helper.ts @@ -4,6 +4,7 @@ import { MintableERC20, MintableERC721, P2PPairStaking, + ParaApeStaking, } from "../../types"; import {SignerWithAddress} from "./make-suite"; import {convertSignatureToEIP2098} from "../../helpers/seaport-helpers/encoding"; @@ -23,7 +24,7 @@ export type ListingOrder = { }; export async function getSignedListingOrder( - p2pPairStaking: P2PPairStaking, + p2pPairStaking: ParaApeStaking, stakingType: number, listingToken: MintableERC721 | MintableERC20 | AutoCompoundApe, tokenId: number, diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index 846727781..5d0812520 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -1,38 +1,54 @@ import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; import {expect} from "chai"; -import {AutoCompoundApe, P2PPairStaking} from "../types"; +import {AutoCompoundApe, ParaApeStaking} from "../types"; import {TestEnv} from "./helpers/make-suite"; import {testEnvFixture} from "./helpers/setup-env"; import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; import { getAutoCompoundApe, getInitializableAdminUpgradeabilityProxy, - getP2PPairStaking, + getParaApeStaking, } from "../helpers/contracts-getters"; import {MAX_UINT_AMOUNT} from "../helpers/constants"; import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; import {parseEther} from "ethers/lib/utils"; import {almostEqual} from "./helpers/uniswapv3-helper"; -import {deployP2PPairStakingImpl} from "../helpers/contracts-deployments"; +import {deployParaApeStakingImpl} from "../helpers/contracts-deployments"; import {GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; import {ProtocolErrors} from "../helpers/types"; describe("P2P Pair Staking Test", () => { let testEnv: TestEnv; - let p2pPairStaking: P2PPairStaking; + let paraApeStaking: ParaApeStaking; let cApe: AutoCompoundApe; let MINIMUM_LIQUIDITY; const fixture = async () => { testEnv = await loadFixture(testEnvFixture); - const {ape, users, apeCoinStaking, poolAdmin} = testEnv; - - const user1 = users[0]; - const user2 = users[1]; - const user4 = users[5]; + const { + ape, + users: [user1, user2, , user4, user5, user6], + apeCoinStaking, + poolAdmin, + } = testEnv; - p2pPairStaking = await getP2PPairStaking(); + //upgrade to non-fake implementation + const paraApeStakingImpl = await deployParaApeStakingImpl(false); + paraApeStaking = await getParaApeStaking(); + const paraApeStakingProxy = await getInitializableAdminUpgradeabilityProxy( + paraApeStaking.address + ); + await waitForTx( + await paraApeStakingProxy + .connect(user5.signer) + .upgradeTo(paraApeStakingImpl.address, GLOBAL_OVERRIDES) + ); + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .setApeStakingBot(user4.address) + ); cApe = await getAutoCompoundApe(); MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); @@ -48,12 +64,12 @@ describe("P2P Pair Staking Test", () => { ); // user4 deposit MINIMUM_LIQUIDITY to make test case easy - await mintAndValidate(ape, "1", user4); + await mintAndValidate(ape, "1", user6); await waitForTx( - await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) + await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) ); await waitForTx( - await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) + await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) ); await waitForTx( @@ -62,12 +78,7 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await cApe .connect(user2.signer) - .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await p2pPairStaking - .connect(poolAdmin.signer) - .setCompoundBot(user1.address) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) ); return testEnv; @@ -84,13 +95,13 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await mintAndValidate(ape, "1000000", user2); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, bayc, 0, @@ -98,7 +109,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, cApe, 0, @@ -107,7 +118,7 @@ describe("P2P Pair Staking Test", () => { ); const txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ); @@ -117,24 +128,24 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), + await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), + await paraApeStaking.pendingCApeReward(user2.address), parseEther("2880") ); await waitForTx( - await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) ); await waitForTx( - await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) + await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) ); almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); @@ -148,17 +159,17 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) ); expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); almostEqual(await cApe.balanceOf(user2.address), apeAmount); almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), + await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), + await paraApeStaking.pendingCApeReward(user2.address), parseEther("2880") ); }); @@ -177,16 +188,16 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await mayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(1); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(1); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 1, mayc, 0, @@ -194,7 +205,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 1, cApe, 0, @@ -203,7 +214,7 @@ describe("P2P Pair Staking Test", () => { ); const txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ); @@ -213,24 +224,24 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), + await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), + await paraApeStaking.pendingCApeReward(user2.address), parseEther("2880") ); await waitForTx( - await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) ); await waitForTx( - await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) + await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) ); almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); @@ -244,17 +255,17 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) ); expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); almostEqual(await cApe.balanceOf(user2.address), apeAmount); almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), + await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), + await paraApeStaking.pendingCApeReward(user2.address), parseEther("2880") ); }); @@ -276,21 +287,21 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await bakc .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bayc, 0, @@ -298,7 +309,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bakc, 0, @@ -306,7 +317,7 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, cApe, 0, @@ -315,7 +326,7 @@ describe("P2P Pair Staking Test", () => { ); const txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, @@ -330,31 +341,31 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), + await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), + await paraApeStaking.pendingCApeReward(user3.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), + await paraApeStaking.pendingCApeReward(user2.address), parseEther("2160") ); await waitForTx( - await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) ); await waitForTx( - await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) + await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) ); await waitForTx( - await p2pPairStaking.connect(user3.signer).claimCApeReward(user3.address) + await paraApeStaking.connect(user3.signer).claimCApeReward(user3.address) ); almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); @@ -369,7 +380,7 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) ); expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); @@ -377,15 +388,15 @@ describe("P2P Pair Staking Test", () => { almostEqual(await cApe.balanceOf(user2.address), apeAmount); almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), + await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), + await paraApeStaking.pendingCApeReward(user3.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), + await paraApeStaking.pendingCApeReward(user2.address), parseEther("2160") ); }); @@ -404,13 +415,13 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bakc, "1", user3, true); await mintAndValidate(ape, "1000000", user2); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, mayc, 0, @@ -418,7 +429,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bakc, 0, @@ -426,7 +437,7 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, cApe, 0, @@ -435,7 +446,7 @@ describe("P2P Pair Staking Test", () => { ); const txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, @@ -450,31 +461,31 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), + await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), + await paraApeStaking.pendingCApeReward(user3.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), + await paraApeStaking.pendingCApeReward(user2.address), parseEther("2160") ); await waitForTx( - await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) ); await waitForTx( - await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) + await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) ); await waitForTx( - await p2pPairStaking.connect(user3.signer).claimCApeReward(user3.address) + await paraApeStaking.connect(user3.signer).claimCApeReward(user3.address) ); almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); @@ -489,22 +500,22 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) ); expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(1); almostEqual(await cApe.balanceOf(user2.address), apeAmount); almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), + await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), + await paraApeStaking.pendingCApeReward(user3.address), parseEther("720") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), + await paraApeStaking.pendingCApeReward(user2.address), parseEther("2160") ); }); @@ -524,12 +535,12 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await bakc .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await cApe @@ -540,7 +551,7 @@ describe("P2P Pair Staking Test", () => { const txArray: string[] = []; for (let i = 0; i < 10; i++) { const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bayc, i, @@ -548,7 +559,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bakc, i, @@ -556,7 +567,7 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, cApe, 0, @@ -565,7 +576,7 @@ describe("P2P Pair Staking Test", () => { ); const txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, @@ -583,7 +594,7 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .claimForMatchedOrderAndCompound(txArray) ); @@ -603,16 +614,16 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await ape .connect(user2.signer) - .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, bayc, 0, @@ -620,7 +631,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, ape, 0, @@ -629,14 +640,14 @@ describe("P2P Pair Staking Test", () => { ); await waitForTx( - await p2pPairStaking.connect(user2.signer).cancelListing(user2SignedOrder) + await paraApeStaking.connect(user2.signer).cancelListing(user2SignedOrder) ); await expect( - p2pPairStaking + paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith("order already cancelled"); + ).to.be.revertedWith(ProtocolErrors.ORDER_ALREADY_CANCELLED); }); it("match failed when order was canceled 1", async () => { @@ -654,21 +665,21 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await bakc .connect(user2.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await ape .connect(user3.signer) - .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bayc, 0, @@ -676,7 +687,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bakc, 0, @@ -684,7 +695,7 @@ describe("P2P Pair Staking Test", () => { user2 ); const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, ape, 0, @@ -693,18 +704,18 @@ describe("P2P Pair Staking Test", () => { ); await waitForTx( - await p2pPairStaking.connect(user3.signer).cancelListing(user3SignedOrder) + await paraApeStaking.connect(user3.signer).cancelListing(user3SignedOrder) ); await expect( - p2pPairStaking + paraApeStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, user2SignedOrder, user3SignedOrder ) - ).to.be.revertedWith("order already cancelled"); + ).to.be.revertedWith(ProtocolErrors.ORDER_ALREADY_CANCELLED); }); it("match failed when orders type match failed 0", async () => { @@ -720,15 +731,15 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, bayc, 0, @@ -736,7 +747,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 1, cApe, 0, @@ -745,10 +756,10 @@ describe("P2P Pair Staking Test", () => { ); await expect( - p2pPairStaking + paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith("orders type match failed"); + ).to.be.revertedWith(ProtocolErrors.ORDER_TYPE_MATCH_FAILED); }); it("match failed when orders type match failed 1", async () => { @@ -766,21 +777,21 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await bakc .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bayc, 0, @@ -788,7 +799,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 1, bakc, 0, @@ -796,7 +807,7 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, cApe, 0, @@ -805,14 +816,14 @@ describe("P2P Pair Staking Test", () => { ); await expect( - p2pPairStaking + paraApeStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, user3SignedOrder, user2SignedOrder ) - ).to.be.revertedWith("orders type match failed"); + ).to.be.revertedWith(ProtocolErrors.ORDER_TYPE_MATCH_FAILED); }); it("match failed when share match failed 0", async () => { @@ -828,15 +839,15 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, bayc, 0, @@ -844,7 +855,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, cApe, 0, @@ -853,10 +864,10 @@ describe("P2P Pair Staking Test", () => { ); await expect( - p2pPairStaking + paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith("share match failed"); + ).to.be.revertedWith(ProtocolErrors.ORDER_SHARE_MATCH_FAILED); }); it("match failed when share match failed 1", async () => { @@ -874,21 +885,21 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await bakc .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bayc, 0, @@ -896,7 +907,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bakc, 0, @@ -904,7 +915,7 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, cApe, 0, @@ -913,14 +924,14 @@ describe("P2P Pair Staking Test", () => { ); await expect( - p2pPairStaking + paraApeStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, user3SignedOrder, user2SignedOrder ) - ).to.be.revertedWith("share match failed"); + ).to.be.revertedWith(ProtocolErrors.ORDER_SHARE_MATCH_FAILED); }); it("listing order can only be canceled by offerer", async () => { @@ -930,7 +941,7 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, bayc, 0, @@ -939,31 +950,24 @@ describe("P2P Pair Staking Test", () => { ); await expect( - p2pPairStaking.connect(user2.signer).cancelListing(user1SignedOrder) - ).to.be.revertedWith("not order offerer"); + paraApeStaking.connect(user2.signer).cancelListing(user1SignedOrder) + ).to.be.revertedWith(ProtocolErrors.NOT_ORDER_OFFERER); await waitForTx( - await p2pPairStaking.connect(user1.signer).cancelListing(user1SignedOrder) + await paraApeStaking.connect(user1.signer).cancelListing(user1SignedOrder) ); }); it("compound fee work as expected", async () => { const { - users: [user1, user2, user3, , user5], - poolAdmin, + users: [user1, user2, user3, user4], bayc, ape, + poolAdmin, } = await loadFixture(fixture); - const p2pPairStakingImpl = await deployP2PPairStakingImpl(50); - const p2pPairStaking = await getP2PPairStaking(); - const p2pPairStakingProxy = await getInitializableAdminUpgradeabilityProxy( - p2pPairStaking.address - ); await waitForTx( - await p2pPairStakingProxy - .connect(user5.signer) - .upgradeTo(p2pPairStakingImpl.address, GLOBAL_OVERRIDES) + await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(50) ); await supplyAndValidate(bayc, "1", user3, true); @@ -982,15 +986,15 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, bayc, 0, @@ -998,7 +1002,7 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, cApe, 0, @@ -1007,7 +1011,7 @@ describe("P2P Pair Staking Test", () => { ); const txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ); @@ -1017,22 +1021,24 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); - almostEqual(await ape.balanceOf(user1.address), parseEther("18")); almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), + await paraApeStaking.pendingCApeReward(user3.address), parseEther("716.4") ); almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), + await paraApeStaking.pendingCApeReward(user2.address), parseEther("2865.6") ); - almostEqual(await ape.balanceOf(user1.address), parseEther("18")); + almostEqual( + await paraApeStaking.pendingCApeReward(paraApeStaking.address), + parseEther("18") + ); }); it("check ape token can be matched twice", async () => { @@ -1050,12 +1056,12 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await bakc .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await cApe @@ -1065,7 +1071,7 @@ describe("P2P Pair Staking Test", () => { //match bayc + ApeCoin let user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, bayc, 0, @@ -1073,7 +1079,7 @@ describe("P2P Pair Staking Test", () => { user1 ); let user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, cApe, 0, @@ -1082,7 +1088,7 @@ describe("P2P Pair Staking Test", () => { ); let txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ); @@ -1091,7 +1097,7 @@ describe("P2P Pair Staking Test", () => { //match bayc + bakc + ApeCoin user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bayc, 0, @@ -1099,7 +1105,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, bakc, 0, @@ -1107,7 +1113,7 @@ describe("P2P Pair Staking Test", () => { user3 ); user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 2, cApe, 0, @@ -1116,7 +1122,7 @@ describe("P2P Pair Staking Test", () => { ); txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, @@ -1128,11 +1134,11 @@ describe("P2P Pair Staking Test", () => { const orderHash1 = txReceipt.logs[logLength - 1].data; await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) ); await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash1) + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash1) ); }); @@ -1149,7 +1155,7 @@ describe("P2P Pair Staking Test", () => { await waitForTx( await bayc .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) + .setApprovalForAll(paraApeStaking.address, true) ); await waitForTx( await cApe @@ -1158,7 +1164,7 @@ describe("P2P Pair Staking Test", () => { ); const user1SignedOrder0 = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, bayc, 0, @@ -1166,7 +1172,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user1SignedOrder1 = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, bayc, 1, @@ -1174,7 +1180,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 0, cApe, 0, @@ -1183,7 +1189,7 @@ describe("P2P Pair Staking Test", () => { ); const txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder0, user2SignedOrder) ); @@ -1191,17 +1197,17 @@ describe("P2P Pair Staking Test", () => { const orderHash0 = txReceipt.logs[logLength - 1].data; await expect( - p2pPairStaking + paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder1, user2SignedOrder) - ).to.be.revertedWith("ape coin order already matched"); + ).to.be.revertedWith(ProtocolErrors.ORDER_ALREADY_MATCHED); await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) ); await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder1, user2SignedOrder) ); @@ -1218,13 +1224,13 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(mayc, "1", user1, true); await mintAndValidate(ape, "1000000", user2); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(1); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(1); await waitForTx( await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 1, mayc, 0, @@ -1232,7 +1238,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, + paraApeStaking, 1, cApe, 0, @@ -1241,21 +1247,21 @@ describe("P2P Pair Staking Test", () => { ); await expect( - p2pPairStaking.connect(user1.signer).pause() + paraApeStaking.connect(user1.signer).pause() ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN); - await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).pause()); + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).pause()); await expect( - p2pPairStaking + paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ).to.be.revertedWith("paused"); - await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).unpause()); + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).unpause()); const txReceipt = await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ); @@ -1264,32 +1270,32 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); - await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).pause()); + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).pause()); await expect( - p2pPairStaking + paraApeStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ).to.be.revertedWith("paused"); - await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).unpause()); + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).unpause()); await waitForTx( - await p2pPairStaking + await paraApeStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); - await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).pause()); + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).pause()); await expect( - p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) + paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) ).to.be.revertedWith("paused"); - await waitForTx(await p2pPairStaking.connect(poolAdmin.signer).unpause()); + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).unpause()); await waitForTx( - await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) ); }); }); diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 957f68385..40641f86b 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -73,7 +73,7 @@ describe("Para Ape Staking Test", () => { ) ); - // user4 deposit MINIMUM_LIQUIDITY to make test case easy + // user6 deposit MINIMUM_LIQUIDITY to make test case easy await mintAndValidate(ape, "1", user6); await waitForTx( await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) From 91cd6f0707c7fb7e08a06c1733a65517f348b4f5 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 14 Jul 2023 11:47:54 +0800 Subject: [PATCH 55/99] chore: use one-time approve --- contracts/apestaking/ParaApeStaking.sol | 2 + .../logic/ApeStakingPairPoolLogic.sol | 2 - .../logic/ApeStakingSinglePoolLogic.sol | 4 - helpers/contracts-deployments.ts | 66 +++----------- .../deployments/steps/20_p2pPairStaking.ts | 85 +++++++------------ test/para_ape_staking.spec.ts | 10 --- 6 files changed, 45 insertions(+), 124 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 65fdf03bc..455de1328 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -118,6 +118,8 @@ contract ParaApeStaking is IERC20(apeCoin).safeApprove(address(apeCoinStaking), type(uint256).max); //approve ApeCoin for cApe IERC20(apeCoin).safeApprove(cApe, type(uint256).max); + //approve cApe for pool + IERC20(cApe).safeApprove(pool, type(uint256).max); } /** diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index fd433c9e6..df56fe84e 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -334,7 +334,6 @@ library ApeStakingPairPoolLogic { ); if (vars.totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay( vars.cApe, vars.totalRepay, @@ -460,7 +459,6 @@ library ApeStakingPairPoolLogic { ); if (vars.totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } if (vars.totalCompoundFee > 0) { diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index edf176f55..41bf9b6c2 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -284,7 +284,6 @@ library ApeStakingSinglePoolLogic { .calculateRepayAndCompound(poolState, vars, vars.positionCap); if (vars.totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } if (vars.totalCompoundFee > 0) { @@ -374,7 +373,6 @@ library ApeStakingSinglePoolLogic { ); if (vars.totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } if (vars.totalCompoundFee > 0) { @@ -598,7 +596,6 @@ library ApeStakingSinglePoolLogic { } if (vars.totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } if (vars.totalCompoundFee > 0) { @@ -727,7 +724,6 @@ library ApeStakingSinglePoolLogic { } if (vars.totalRepay > 0) { - IERC20(vars.cApe).safeApprove(vars.pool, vars.totalRepay); IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } if (vars.totalCompoundFee > 0) { diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index c64c7dcab..c89d92c37 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -2756,37 +2756,6 @@ export const deployParaApeStakingLibraries = async ( }; }; -export const deployFakeParaApeStakingImpl = async (verify?: boolean) => { - const allTokens = await getAllTokens(); - const apeCoinStaking = - (await getContractAddressInDb(eContractid.ApeCoinStaking)) || - (await deployApeCoinStaking(verify)).address; - - const args = [ - zeroAddress(), - zeroAddress(), - zeroAddress(), - zeroAddress(), - zeroAddress(), - zeroAddress(), - zeroAddress(), - allTokens.APE.address, - allTokens.cAPE.address, - apeCoinStaking, - zeroAddress(), - 0, - ]; - - const libraries = await deployParaApeStakingLibraries(); - - return withSaveAndVerify( - new ParaApeStaking__factory(libraries, await getFirstSigner()), - eContractid.ParaApeStakingImpl, - [...args], - verify - ) as Promise; -}; - export const deployParaApeStakingImpl = async (verify?: boolean) => { const poolProxy = await getPoolProxy(); const allTokens = await getAllTokens(); @@ -2832,33 +2801,26 @@ export const deployParaApeStaking = async ( fakeImplementation: boolean, verify?: boolean ) => { - let stakingImplementation; - if (fakeImplementation) { - stakingImplementation = await deployFakeParaApeStakingImpl(verify); - } else { - stakingImplementation = await deployParaApeStakingImpl(verify); - } - const deployer = await getFirstSigner(); - const deployerAddress = await deployer.getAddress(); - const initData = - stakingImplementation.interface.encodeFunctionData("initialize"); - const proxyInstance = await withSaveAndVerify( new InitializableAdminUpgradeabilityProxy__factory(await getFirstSigner()), eContractid.ParaApeStaking, [], verify ); - await waitForTx( - await (proxyInstance as InitializableAdminUpgradeabilityProxy)[ - "initialize(address,address,bytes)" - ]( - stakingImplementation.address, - deployerAddress, - initData, - GLOBAL_OVERRIDES - ) - ); + if (!fakeImplementation) { + const paraApeStakingImpl = await deployParaApeStakingImpl(verify); + + const deployer = await getFirstSigner(); + const deployerAddress = await deployer.getAddress(); + const initData = + paraApeStakingImpl.interface.encodeFunctionData("initialize"); + + await waitForTx( + await (proxyInstance as InitializableAdminUpgradeabilityProxy)[ + "initialize(address,address,bytes)" + ](paraApeStakingImpl.address, deployerAddress, initData, GLOBAL_OVERRIDES) + ); + } return await getParaApeStaking(proxyInstance.address); }; diff --git a/scripts/deployments/steps/20_p2pPairStaking.ts b/scripts/deployments/steps/20_p2pPairStaking.ts index 00b140457..5398da6cc 100644 --- a/scripts/deployments/steps/20_p2pPairStaking.ts +++ b/scripts/deployments/steps/20_p2pPairStaking.ts @@ -1,16 +1,16 @@ -import {deployP2PPairStaking} from "../../../helpers/contracts-deployments"; import { - getAllTokens, - getNTokenBAKC, - getNTokenBAYC, - getNTokenMAYC, - getPoolProxy, + deployParaApeStaking, + deployParaApeStakingImpl, +} from "../../../helpers/contracts-deployments"; +import { + getFirstSigner, + getInitializableAdminUpgradeabilityProxy, + getParaApeStaking, } from "../../../helpers/contracts-getters"; import {getParaSpaceConfig, waitForTx} from "../../../helpers/misc-utils"; -import { - ERC20TokenContractId, - ERC721TokenContractId, -} from "../../../helpers/types"; +import {ERC20TokenContractId} from "../../../helpers/types"; +import {GLOBAL_OVERRIDES} from "../../../helpers/hardhat-constants"; +import {InitializableAdminUpgradeabilityProxy} from "../../../types"; export const step_20 = async (verify = false) => { const paraSpaceConfig = getParaSpaceConfig(); @@ -18,58 +18,31 @@ export const step_20 = async (verify = false) => { if (!paraSpaceConfig.ReservesConfig[ERC20TokenContractId.APE]) { return; } - // deploy P2PPairStaking - const p2pPairStaking = await deployP2PPairStaking(verify); - const allTokens = await getAllTokens(); - const pool = await getPoolProxy(); - const bayc = allTokens[ERC721TokenContractId.BAYC]; - const mayc = allTokens[ERC721TokenContractId.MAYC]; - const bakc = allTokens[ERC721TokenContractId.BAKC]; - - if (bayc) { - const nBAYC = await getNTokenBAYC( - ( - await pool.getReserveData(bayc.address) - ).xTokenAddress - ); - await waitForTx( - await nBAYC.setApprovalForAllTo( - bayc.address, - p2pPairStaking.address, - true - ) - ); - } + const paraApeStaking = await getParaApeStaking(); + //upgrade to non-fake implementation + if (paraApeStaking) { + const paraApeStakingImpl = await deployParaApeStakingImpl(verify); + const paraApeStakingProxy = + await getInitializableAdminUpgradeabilityProxy(paraApeStaking.address); - if (mayc) { - const nMAYC = await getNTokenMAYC( - ( - await pool.getReserveData(mayc.address) - ).xTokenAddress - ); - await waitForTx( - await nMAYC.setApprovalForAllTo( - mayc.address, - p2pPairStaking.address, - true - ) - ); - } + const deployer = await getFirstSigner(); + const deployerAddress = await deployer.getAddress(); + const initData = + paraApeStakingImpl.interface.encodeFunctionData("initialize"); - if (bakc) { - const nBAKC = await getNTokenBAKC( - ( - await pool.getReserveData(bakc.address) - ).xTokenAddress - ); await waitForTx( - await nBAKC.setApprovalForAllTo( - bakc.address, - p2pPairStaking.address, - true + await (paraApeStakingProxy as InitializableAdminUpgradeabilityProxy)[ + "initialize(address,address,bytes)" + ]( + paraApeStakingImpl.address, + deployerAddress, + initData, + GLOBAL_OVERRIDES ) ); + } else { + await deployParaApeStaking(false, verify); } } catch (error) { console.error(error); diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 40641f86b..05a5ea3ad 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -36,17 +36,7 @@ describe("Para Ape Staking Test", () => { poolAdmin, } = testEnv; - //upgrade to non-fake implementation - const paraApeStakingImpl = await deployParaApeStakingImpl(false); paraApeStaking = await getParaApeStaking(); - const paraApeStakingProxy = await getInitializableAdminUpgradeabilityProxy( - paraApeStaking.address - ); - await waitForTx( - await paraApeStakingProxy - .connect(user5.signer) - .upgradeTo(paraApeStakingImpl.address, GLOBAL_OVERRIDES) - ); await waitForTx( await paraApeStaking From 78cbb45de71fa78ed89596814e8933957f475dec Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 14 Jul 2023 12:41:06 +0800 Subject: [PATCH 56/99] chore: rename script --- Makefile | 12 ++++++------ helpers/contracts-deployments.ts | 1 - ..._p2pPairStaking.ts => 20_paraApeStaking.ts} | 0 scripts/deployments/steps/index.ts | 2 +- ..._p2pPairStaking.ts => 20_paraApeStaking.ts} | 4 ++-- tasks/upgrade/index.ts | 1 - test/helpers/p2ppairstaking-helper.ts | 1 - test/p2p_pair_staking.spec.ts | 18 +++--------------- test/para_ape_staking.spec.ts | 5 +---- 9 files changed, 13 insertions(+), 31 deletions(-) rename scripts/deployments/steps/{20_p2pPairStaking.ts => 20_paraApeStaking.ts} (100%) rename tasks/deployments/{20_p2pPairStaking.ts => 20_paraApeStaking.ts} (67%) diff --git a/Makefile b/Makefile index d3bc01d1e..50b9be4ee 100644 --- a/Makefile +++ b/Makefile @@ -264,9 +264,9 @@ test-ape-staking: test-auto-compound-ape: make TEST_TARGET=auto_compound_ape.spec.ts test -.PHONY: test-p2p-pair-staking -test-p2p-pair-staking: - make TEST_TARGET=p2p_pair_staking.spec.ts test +.PHONY: test-para-ape-staking +test-para-aper-staking: + make TEST_TARGET=para_ape_staking.spec.ts test .PHONY: test-sape-operation test-sape-operation: @@ -384,9 +384,9 @@ deploy-blur-exchange: deploy-flashClaimRegistry: make TASK_NAME=deploy:flash-claim-registry run-task -.PHONY: deploy-p2p-pair-staking -deploy-p2p-pair-staking: - make TASK_NAME=deploy:P2PPairStaking run-task +.PHONY: deploy-para-ape-staking +deploy-para-ape-staking: + make TASK_NAME=deploy:ParaApeStaking run-task .PHONY: deploy-timelock deploy-timelock: diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index c89d92c37..07ff04abc 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -346,7 +346,6 @@ import {pick, upperFirst} from "lodash"; import {ZERO_ADDRESS} from "./constants"; import {GLOBAL_OVERRIDES} from "./hardhat-constants"; import {parseEther} from "ethers/lib/utils"; -import {zeroAddress} from "ethereumjs-util"; import {ParaApeStakingLibraryAddresses} from "../types/factories/contracts/apestaking/ParaApeStaking__factory"; export const deployPoolAddressesProvider = async ( diff --git a/scripts/deployments/steps/20_p2pPairStaking.ts b/scripts/deployments/steps/20_paraApeStaking.ts similarity index 100% rename from scripts/deployments/steps/20_p2pPairStaking.ts rename to scripts/deployments/steps/20_paraApeStaking.ts diff --git a/scripts/deployments/steps/index.ts b/scripts/deployments/steps/index.ts index a75d3afec..187db9886 100644 --- a/scripts/deployments/steps/index.ts +++ b/scripts/deployments/steps/index.ts @@ -19,7 +19,7 @@ export const getAllSteps = async () => { const {step_17} = await import("./17_x2y2"); const {step_18} = await import("./18_blur"); const {step_19} = await import("./19_flashClaimRegistry"); - const {step_20} = await import("./20_p2pPairStaking"); + const {step_20} = await import("./20_paraApeStaking"); const {step_21} = await import("./21_helperContract"); const {step_22} = await import("./22_timelock"); const {step_23} = await import("./23_renounceOwnership"); diff --git a/tasks/deployments/20_p2pPairStaking.ts b/tasks/deployments/20_paraApeStaking.ts similarity index 67% rename from tasks/deployments/20_p2pPairStaking.ts rename to tasks/deployments/20_paraApeStaking.ts index 2ab569c77..9a60b2421 100644 --- a/tasks/deployments/20_p2pPairStaking.ts +++ b/tasks/deployments/20_paraApeStaking.ts @@ -1,11 +1,11 @@ import {task} from "hardhat/config"; import {ETHERSCAN_VERIFICATION} from "../../helpers/hardhat-constants"; -task("deploy:P2PPairStaking", "Deploy P2PPairStaking").setAction( +task("deploy:ParaApeStaking", "Deploy ParaApeStaking").setAction( async (_, DRE) => { await DRE.run("set-DRE"); const {step_20} = await import( - "../../scripts/deployments/steps/20_p2pPairStaking" + "../../scripts/deployments/steps/20_paraApeStaking" ); await step_20(ETHERSCAN_VERIFICATION); } diff --git a/tasks/upgrade/index.ts b/tasks/upgrade/index.ts index 281c033a8..b8f8b125e 100644 --- a/tasks/upgrade/index.ts +++ b/tasks/upgrade/index.ts @@ -1,6 +1,5 @@ import {task} from "hardhat/config"; import {ETHERSCAN_VERIFICATION} from "../../helpers/hardhat-constants"; -import {upgradeParaApeStaking} from "../../scripts/upgrade/para_ape_staking"; task("upgrade:all", "upgrade all").setAction(async (_, DRE) => { const {upgradeAll} = await import("../../scripts/upgrade"); diff --git a/test/helpers/p2ppairstaking-helper.ts b/test/helpers/p2ppairstaking-helper.ts index 14f06f7ef..c0cc0e30e 100644 --- a/test/helpers/p2ppairstaking-helper.ts +++ b/test/helpers/p2ppairstaking-helper.ts @@ -3,7 +3,6 @@ import { AutoCompoundApe, MintableERC20, MintableERC721, - P2PPairStaking, ParaApeStaking, } from "../../types"; import {SignerWithAddress} from "./make-suite"; diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index 5d0812520..c9659aafa 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -6,7 +6,6 @@ import {testEnvFixture} from "./helpers/setup-env"; import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; import { getAutoCompoundApe, - getInitializableAdminUpgradeabilityProxy, getParaApeStaking, } from "../helpers/contracts-getters"; import {MAX_UINT_AMOUNT} from "../helpers/constants"; @@ -14,8 +13,6 @@ import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; import {parseEther} from "ethers/lib/utils"; import {almostEqual} from "./helpers/uniswapv3-helper"; -import {deployParaApeStakingImpl} from "../helpers/contracts-deployments"; -import {GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; import {ProtocolErrors} from "../helpers/types"; describe("P2P Pair Staking Test", () => { @@ -28,22 +25,13 @@ describe("P2P Pair Staking Test", () => { testEnv = await loadFixture(testEnvFixture); const { ape, - users: [user1, user2, , user4, user5, user6], + users: [user1, user2, , user4, , user6], apeCoinStaking, poolAdmin, } = testEnv; - //upgrade to non-fake implementation - const paraApeStakingImpl = await deployParaApeStakingImpl(false); paraApeStaking = await getParaApeStaking(); - const paraApeStakingProxy = await getInitializableAdminUpgradeabilityProxy( - paraApeStaking.address - ); - await waitForTx( - await paraApeStakingProxy - .connect(user5.signer) - .upgradeTo(paraApeStakingImpl.address, GLOBAL_OVERRIDES) - ); + await waitForTx( await paraApeStaking .connect(poolAdmin.signer) @@ -960,7 +948,7 @@ describe("P2P Pair Staking Test", () => { it("compound fee work as expected", async () => { const { - users: [user1, user2, user3, user4], + users: [user1, user2, user3], bayc, ape, poolAdmin, diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 05a5ea3ad..31ba0e243 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -6,15 +6,12 @@ import {testEnvFixture} from "./helpers/setup-env"; import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; import { getAutoCompoundApe, - getInitializableAdminUpgradeabilityProxy, getParaApeStaking, getVariableDebtToken, } from "../helpers/contracts-getters"; import {MAX_UINT_AMOUNT} from "../helpers/constants"; import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {parseEther} from "ethers/lib/utils"; -import {deployParaApeStakingImpl} from "../helpers/contracts-deployments"; -import {GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; import {ProtocolErrors} from "../helpers/types"; describe("Para Ape Staking Test", () => { @@ -28,7 +25,7 @@ describe("Para Ape Staking Test", () => { testEnv = await loadFixture(testEnvFixture); const { ape, - users: [user1, , , user4, user5, user6], + users: [user1, , , user4, , user6], apeCoinStaking, pool, protocolDataProvider, From ee177cb5bc98a427d9af418525beb3a7dad95bb6 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 14 Jul 2023 16:00:22 +0800 Subject: [PATCH 57/99] chore: add comment and small optimization --- contracts/apestaking/ParaApeStaking.sol | 67 ++++++-- .../logic/ApeStakingCommonLogic.sol | 2 +- .../logic/ApeStakingPairPoolLogic.sol | 52 +++---- .../logic/ApeStakingSinglePoolLogic.sol | 93 +++++------ contracts/interfaces/IApeStakingP2P.sol | 6 - contracts/interfaces/IApeStakingVault.sol | 147 ++++++++++++++++++ 6 files changed, 276 insertions(+), 91 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 455de1328..492020e4b 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -192,6 +192,12 @@ contract ParaApeStaking is _unpause(); } + /** + * @notice Rescue erc20 from this contract address. Only pool admin can call this function + * @param token The token address to be rescued, _yieldToken cannot be rescued. + * @param to The account address to receive token + * @param amount The amount to be rescued + **/ function rescueERC20( address token, address to, @@ -205,8 +211,10 @@ contract ParaApeStaking is * P2P Pair Staking Logic */ + /// @inheritdoc IApeStakingP2P function cancelListing(ListingOrder calldata listingOrder) external + override nonReentrant { require(msg.sender == listingOrder.offerer, Errors.NOT_ORDER_OFFERER); @@ -222,10 +230,11 @@ contract ParaApeStaking is emit OrderCancelled(orderHash, listingOrder.offerer); } + /// @inheritdoc IApeStakingP2P function matchPairStakingList( ListingOrder calldata apeOrder, ListingOrder calldata apeCoinOrder - ) external nonReentrant whenNotPaused returns (bytes32 orderHash) { + ) external override nonReentrant whenNotPaused returns (bytes32 orderHash) { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.DOMAIN_SEPARATOR = DOMAIN_SEPARATOR; orderHash = ApeStakingP2PLogic.matchPairStakingList( @@ -242,11 +251,12 @@ contract ParaApeStaking is return orderHash; } + /// @inheritdoc IApeStakingP2P function matchBAKCPairStakingList( ListingOrder calldata apeOrder, ListingOrder calldata bakcOrder, ListingOrder calldata apeCoinOrder - ) external nonReentrant whenNotPaused returns (bytes32 orderHash) { + ) external override nonReentrant whenNotPaused returns (bytes32 orderHash) { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.DOMAIN_SEPARATOR = DOMAIN_SEPARATOR; orderHash = ApeStakingP2PLogic.matchBAKCPairStakingList( @@ -264,8 +274,10 @@ contract ParaApeStaking is return orderHash; } + /// @inheritdoc IApeStakingP2P function breakUpMatchedOrder(bytes32 orderHash) external + override nonReentrant whenNotPaused { @@ -284,8 +296,10 @@ contract ParaApeStaking is emit PairStakingBreakUp(orderHash); } + /// @inheritdoc IApeStakingP2P function claimForMatchedOrderAndCompound(bytes32[] calldata orderHashes) external + override nonReentrant whenNotPaused { @@ -299,8 +313,10 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingP2P function claimCApeReward(address receiver) external + override nonReentrant whenNotPaused { @@ -312,7 +328,13 @@ contract ParaApeStaking is } } - function pendingCApeReward(address user) public view returns (uint256) { + /// @inheritdoc IApeStakingP2P + function pendingCApeReward(address user) + public + view + override + returns (uint256) + { uint256 amount = 0; uint256 shareBalance = cApeShareBalance[user]; if (shareBalance > 0) { @@ -321,9 +343,11 @@ contract ParaApeStaking is return amount; } + /// @inheritdoc IApeStakingP2P function getApeCoinStakingCap(StakingType stakingType) public view + override returns (uint256) { ApeStakingVaultCacheVars memory vars = _createCacheVars(); @@ -352,11 +376,12 @@ contract ParaApeStaking is } } + /// @inheritdoc IApeStakingVault function depositPairNFT( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant { + ) external override whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID @@ -370,11 +395,12 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingVault function stakingPairNFT( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant { + ) external override whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID @@ -388,11 +414,12 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingVault function compoundPairNFT( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external onlyApeStakingBot { + ) external override onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC @@ -408,11 +435,12 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingVault function pairNFTPendingReward( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external view returns (uint256) { + ) external view override returns (uint256) { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID @@ -429,12 +457,12 @@ contract ParaApeStaking is return pendingReward; } - // to save gas we don't claim pending reward in ApeCoinStaking. + /// @inheritdoc IApeStakingVault function claimPairNFT( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant { + ) external override whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID @@ -448,11 +476,12 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingVault function withdrawPairNFT( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant { + ) external override whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC @@ -468,8 +497,10 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingVault function depositNFT(address nft, uint32[] calldata tokenIds) external + override whenNotPaused nonReentrant { @@ -481,8 +512,10 @@ contract ParaApeStaking is ApeStakingSinglePoolLogic.depositNFT(vaultStorage, vars, nft, tokenIds); } + /// @inheritdoc IApeStakingVault function stakingApe(bool isBAYC, uint32[] calldata tokenIds) external + override whenNotPaused nonReentrant { @@ -498,11 +531,12 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingVault function stakingBAKC( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant { + ) external override whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID @@ -517,8 +551,10 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingVault function compoundApe(bool isBAYC, uint32[] calldata tokenIds) external + override onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); @@ -535,11 +571,12 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingVault function compoundBAKC( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external onlyApeStakingBot { + ) external override onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; ApeStakingSinglePoolLogic.compoundBAKC( @@ -552,9 +589,11 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeStakingVault function nftPendingReward(address nft, uint32[] calldata tokenIds) external view + override returns (uint256) { require( @@ -568,8 +607,10 @@ contract ParaApeStaking is return pendingReward; } + /// @inheritdoc IApeStakingVault function claimNFT(address nft, uint32[] calldata tokenIds) external + override whenNotPaused nonReentrant { @@ -581,8 +622,10 @@ contract ParaApeStaking is ApeStakingSinglePoolLogic.claimNFT(vaultStorage, vars, nft, tokenIds); } + /// @inheritdoc IApeStakingVault function withdrawNFT(address nft, uint32[] calldata tokenIds) external + override whenNotPaused nonReentrant { diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 6f70c8ed0..12e247243 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -54,7 +54,7 @@ library ApeStakingCommonLogic { uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) .rayDiv(vars.cApeExchangeRate); compoundFee = shareRewardAmount.percentMul(vars.compoundFee); - shareRewardAmount = shareRewardAmount - compoundFee; + shareRewardAmount -= compoundFee; //update reward index uint128 currentTotalPosition = poolState.totalPosition; if (currentTotalPosition != 0) { diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index df56fe84e..6e2a738ff 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -307,42 +307,40 @@ library ApeStakingPairPoolLogic { 0 ); if (isBAYC) { - vars.apeCoinStaking.withdrawBAYC(_nfts, address(this)); + vars.apeCoinStaking.withdrawSelfBAYC(_nfts); vars.apeCoinStaking.withdrawBAKC(_nftPairs, _otherPairs); } else { - vars.apeCoinStaking.withdrawMAYC(_nfts, address(this)); + vars.apeCoinStaking.withdrawSelfMAYC(_nfts); vars.apeCoinStaking.withdrawBAKC(_otherPairs, _nftPairs); } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - if (vars.totalClaimedApe > 0) { - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); + + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + vars.latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); + (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic + .calculateRepayAndCompound( + poolState, + vars, + vars.positionCap + vars.bakcMatchedCap ); - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY + if (vars.totalRepay > 0) { + IPool(vars.pool).repay( + vars.cApe, + vars.totalRepay, + address(this) ); - vars.latestBorrowIndex = IPool(vars.pool) - .getReserveNormalizedVariableDebt(vars.cApe); - (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic - .calculateRepayAndCompound( - poolState, - vars, - vars.positionCap + vars.bakcMatchedCap - ); - - if (vars.totalRepay > 0) { - IPool(vars.pool).repay( - vars.cApe, - vars.totalRepay, - address(this) - ); - } - if (vars.totalCompoundFee > 0) { - cApeShareBalance[address(this)] += vars.totalCompoundFee; - } + } + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; } } diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 41bf9b6c2..b549c7d52 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -50,10 +50,6 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - address msgSender = msg.sender; - address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) - ? vars.nMayc - : vars.nBakc; uint128 accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( vaultStorage, vars, @@ -62,11 +58,26 @@ library ApeStakingSinglePoolLogic { mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); + address nToken; + uint256 apeStakingPoolId; + if (nft == vars.bayc) { + nToken = vars.nBayc; + apeStakingPoolId = BAYC_POOL_ID; + } else if (nft == vars.mayc) { + nToken = vars.nMayc; + apeStakingPoolId = MAYC_POOL_ID; + } else { + nToken = vars.nBakc; + apeStakingPoolId = 0; + } + address msgSender = msg.sender; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; - address nTokenOwner = IERC721(nToken).ownerOf(tokenId); - require(msgSender == nTokenOwner, Errors.NOT_THE_OWNER); + require( + msgSender == IERC721(nToken).ownerOf(tokenId), + Errors.NOT_THE_OWNER + ); if (nft == vars.bakc) { (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( @@ -75,18 +86,14 @@ library ApeStakingSinglePoolLogic { ); require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); } else { - vars.apeStakingPoolId = (nft == vars.bayc) - ? BAYC_POOL_ID - : MAYC_POOL_ID; - (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( - vars.apeStakingPoolId, + apeStakingPoolId, tokenId ); require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); (, bool isPaired) = vars.apeCoinStaking.mainToBakc( - vars.apeStakingPoolId, + apeStakingPoolId, tokenId ); require(!isPaired, Errors.PAIR_POSITION_EXISTED); @@ -543,19 +550,17 @@ library ApeStakingSinglePoolLogic { } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - if (vars.totalClaimedApe > 0) { - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); - (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic - .calculateRepayAndCompound( - apePoolState, - vars, - vars.positionCap - ); - } + (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic + .calculateRepayAndCompound( + apePoolState, + vars, + vars.positionCap + ); } if (pairStakingCount > 0) { @@ -575,24 +580,22 @@ library ApeStakingSinglePoolLogic { } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - if (vars.totalClaimedApe > 0) { - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); - ( - uint256 bakcTotalRepay, - uint256 bakcCompoundFee - ) = _calculateRepayAndCompoundBAKC( - apePoolState, - bakcPoolState, - vars, - isBAYC - ); - vars.totalRepay += bakcTotalRepay; - vars.totalCompoundFee += bakcCompoundFee; - } + ( + uint256 bakcTotalRepay, + uint256 bakcCompoundFee + ) = _calculateRepayAndCompoundBAKC( + apePoolState, + bakcPoolState, + vars, + isBAYC + ); + vars.totalRepay += bakcTotalRepay; + vars.totalCompoundFee += bakcCompoundFee; } if (vars.totalRepay > 0) { @@ -834,7 +837,6 @@ library ApeStakingSinglePoolLogic { ) internal returns (uint256, uint256) { uint256 repayAmount = 0; uint256 debtInterest = 0; - //calculate repay uint256 cApeDebtShare; uint256 stakingPosition; if (isBAYC) { @@ -871,7 +873,7 @@ library ApeStakingSinglePoolLogic { uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) .rayDiv(vars.cApeExchangeRate); compoundFee = shareRewardAmount.percentMul(vars.compoundFee); - shareRewardAmount = shareRewardAmount - compoundFee; + shareRewardAmount -= compoundFee; uint256 apeShareAmount = shareRewardAmount.percentMul( vars.apeRewardRatio ); @@ -885,12 +887,13 @@ library ApeStakingSinglePoolLogic { compoundFee += apeShareAmount; } uint128 bakcTotalPosition = bakcPoolState.totalPosition; + shareRewardAmount -= apeShareAmount; if (bakcTotalPosition != 0) { bakcPoolState.accumulatedRewardsPerNft += - (shareRewardAmount - apeShareAmount).toUint128() / + shareRewardAmount.toUint128() / bakcTotalPosition; } else { - compoundFee += (shareRewardAmount - apeShareAmount); + compoundFee += shareRewardAmount; } } diff --git a/contracts/interfaces/IApeStakingP2P.sol b/contracts/interfaces/IApeStakingP2P.sol index 9497a6f1a..b2d000bf9 100644 --- a/contracts/interfaces/IApeStakingP2P.sol +++ b/contracts/interfaces/IApeStakingP2P.sol @@ -40,12 +40,6 @@ interface IApeStakingP2P { bytes32 apeCoinListingOrderHash; } - struct VaultPosition { - StakingType stakingType; - uint32 mainTokenId; - uint32 bakcTokenId; - } - /** * @dev Emit an event whenever an listing order is successfully cancelled. * @param orderHash The hash of the cancelled order. diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index 120130596..452f1feb6 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -64,4 +64,151 @@ interface IApeStakingVault { * @param newRatio The value of the new maycPairStakingRewardRatio **/ event MaycPairStakingRewardRatioUpdated(uint128 oldRatio, uint128 newRatio); + + /** + * @notice deposit Ape and BAKC pair into the pool + * @param isBAYC if Ape is BAYC + * @param apeTokenIds Ape token ids + * @param bakcTokenIds BAKC token ids + */ + function depositPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external; + + /** + * @notice stake pool's Ape and BAKC pair into ApeCoinStaking + * @param isBAYC if Ape is BAYC + * @param apeTokenIds Ape token ids + * @param bakcTokenIds BAKC token ids + */ + function stakingPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external; + + /** + * @notice claim Ape and BAKC pair staking reward from ApeCoinStaking and compound as cApe for user + * only ape staking bot can call this function + * @param isBAYC if Ape is BAYC + * @param apeTokenIds Ape token ids + * @param bakcTokenIds BAKC token ids + */ + function compoundPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external; + + /** + * @notice get Ape and BAKC pair staking unclaimed cApe reward + * @param isBAYC if Ape is BAYC + * @param apeTokenIds Ape token ids + * @param bakcTokenIds BAKC token ids + */ + function pairNFTPendingReward( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external returns (uint256); + + /** + * @notice claim Ape and BAKC pair staking unclaimed cApe reward + * to save gas we don't claim pending reward in ApeCoinStaking. + * @param isBAYC if Ape is BAYC + * @param apeTokenIds Ape token ids + * @param bakcTokenIds BAKC token ids + */ + function claimPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external; + + /** + * @notice withdraw Ape and BAKC pair from pool + * if the pair is staking it ApeCoinStaking, it will unstake from ApeCoinStaking first. + * @param isBAYC if Ape is BAYC + * @param apeTokenIds Ape token ids + * @param bakcTokenIds BAKC token ids + */ + function withdrawPairNFT( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external; + + /** + * @notice deposit Ape or BAKC into the single pool + * @param nft Ape or BAKC token address + * @param tokenIds nft token ids + */ + function depositNFT(address nft, uint32[] calldata tokenIds) external; + + /** + * @notice stake pool's Ape into ApeCoinStaking + * @param isBAYC if Ape is BAYC + * @param tokenIds Ape token ids + */ + function stakingApe(bool isBAYC, uint32[] calldata tokenIds) external; + + /** + * @notice stake pool's Ape and BAKC into ApeCoinStaking pair staking pool + * @param isBAYC if Ape is BAYC + * @param apeTokenIds Ape token ids + * @param bakcTokenIds BAKC token ids + */ + function stakingBAKC( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external; + + /** + * @notice claim Ape staking reward from ApeCoinStaking and compound as cApe for user + * only ape staking bot can call this function + * @param isBAYC if Ape is BAYC + * @param tokenIds Ape token ids + */ + function compoundApe(bool isBAYC, uint32[] calldata tokenIds) external; + + /** + * @notice claim single pool's Ape and BAKC pair staking reward from ApeCoinStaking and compound as cApe for user + * only ape staking bot can call this function + * @param isBAYC if Ape is BAYC + * @param apeTokenIds Ape token ids + * @param bakcTokenIds BAKC token ids + */ + function compoundBAKC( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external; + + /** + * @notice get single pool nft unclaimed cApe reward + * @param nft Ape or BAKC token address + * @param tokenIds nft token ids + */ + function nftPendingReward(address nft, uint32[] calldata tokenIds) + external + returns (uint256); + + /** + * @notice claim single pool nft unclaimed cApe reward + * to save gas we don't claim pending reward in ApeCoinStaking. + * @param nft Ape or BAKC token address + * @param tokenIds nft token ids + */ + function claimNFT(address nft, uint32[] calldata tokenIds) external; + + /** + * @notice withdraw nft from single pool + * if the nft is staking it ApeCoinStaking, it will unstake from ApeCoinStaking first. + * @param nft Ape or BAKC token address + * @param tokenIds nft token ids + */ + function withdrawNFT(address nft, uint32[] calldata tokenIds) external; } From 8d0c5cb41757c11c4bbcf6256006752ae27777ff Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 17 Jul 2023 09:34:45 +0800 Subject: [PATCH 58/99] chore: remove unused storage --- contracts/apestaking/ParaApeStaking.sol | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 492020e4b..7a8d50794 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -50,14 +50,8 @@ contract ParaApeStaking is uint256 private immutable bakcMatchedCap; IACLManager private immutable aclManager; - bytes32 internal DOMAIN_SEPARATOR; - mapping(bytes32 => ListingOrderStatus) public listingOrderStatus; - mapping(bytes32 => MatchedOrder) public matchedOrders; mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; - mapping(StakingType => mapping(uint32 => uint256)) - private positionCApeShareDebt; mapping(address => uint256) private cApeShareBalance; - address public apeStakingBot; uint64 public compoundFee; @@ -211,6 +205,10 @@ contract ParaApeStaking is * P2P Pair Staking Logic */ + bytes32 internal DOMAIN_SEPARATOR; + mapping(bytes32 => ListingOrderStatus) public listingOrderStatus; + mapping(bytes32 => MatchedOrder) public matchedOrders; + /// @inheritdoc IApeStakingP2P function cancelListing(ListingOrder calldata listingOrder) external From 2d382b06b4215cec651f375a560e97b414b09004 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 18 Jul 2023 15:51:57 +0800 Subject: [PATCH 59/99] chore: vault optimization and fix --- contracts/apestaking/ParaApeStaking.sol | 43 ++ .../apestaking/logic/ApeCoinPoolLogic.sol | 547 ++++++++++++++++++ .../logic/ApeStakingCommonLogic.sol | 20 +- .../apestaking/logic/ApeStakingP2PLogic.sol | 12 +- .../logic/ApeStakingPairPoolLogic.sol | 14 +- .../logic/ApeStakingSinglePoolLogic.sol | 45 +- .../openzeppelin/contracts/SafeCast.sol | 34 ++ contracts/interfaces/IApeCoinPool.sol | 20 + contracts/interfaces/IApeStakingVault.sol | 28 +- contracts/interfaces/IParaApeStaking.sol | 7 + 10 files changed, 717 insertions(+), 53 deletions(-) create mode 100644 contracts/apestaking/logic/ApeCoinPoolLogic.sol create mode 100644 contracts/interfaces/IApeCoinPool.sol diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 7a8d50794..b7bcb431f 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -50,8 +50,11 @@ contract ParaApeStaking is uint256 private immutable bakcMatchedCap; IACLManager private immutable aclManager; + //record Ape in P2P and ApeCoin pool + mapping(address => IApeCoinPool.ApeCoinPoolState) private apeCoinPoolStates; mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; mapping(address => uint256) private cApeShareBalance; + mapping(address => uint256) private sApeBalance; address public apeStakingBot; uint64 public compoundFee; @@ -201,6 +204,46 @@ contract ParaApeStaking is emit RescueERC20(token, to, amount); } + /* + *Ape Coin Staking Pool Logic + */ + + function depositApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) + external + whenNotPaused + nonReentrant + {} + + function depositApeCoinPairPool( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external whenNotPaused nonReentrant {} + + function CompoundApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) + external + whenNotPaused + nonReentrant + {} + + function CompoundApeCoinPairPool( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external whenNotPaused nonReentrant {} + + function WithdrawApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) + external + whenNotPaused + nonReentrant + {} + + function WithdrawApeCoinPairPool( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external whenNotPaused nonReentrant {} + /* * P2P Pair Staking Logic */ diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol new file mode 100644 index 000000000..bbd90aeae --- /dev/null +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -0,0 +1,547 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.10; + +import {IPool} from "../../interfaces/IPool.sol"; +import "../../interfaces/IParaApeStaking.sol"; +import {IERC20, SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; +import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; +import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; +import "../../interfaces/IAutoCompoundApe.sol"; +import "../../interfaces/ICApe.sol"; +import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; +import "./ApeStakingCommonLogic.sol"; +import "../../protocol/libraries/helpers/Errors.sol"; + +/** + * @title ApeCoinPoolLogic library + * + * @notice Implements the base logic for para ape staking apecoin pool + */ +library ApeCoinPoolLogic { + using PercentageMath for uint256; + using SafeCast for uint256; + using SafeERC20 for IERC20; + using WadRayMath for uint256; + + uint256 public constant BAYC_BAKC_PAIR_POOL_ID = 1; + uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; + uint256 public constant BAYC_SINGLE_POOL_ID = 3; + uint256 public constant MAYC_SINGLE_POOL_ID = 4; + + uint256 constant BAYC_POOL_ID = 1; + uint256 constant MAYC_POOL_ID = 2; + uint256 constant BAKC_POOL_ID = 3; + + /** + * @dev Minimum health factor to consider a user position healthy + * A value of 1e18 results in 1 + */ + uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18; + + event ApeCoinPoolDeposited(bool isBAYC, uint256 tokenId); + event ApeCoinPoolCompounded(bool isBAYC, uint256 tokenId); + event ApeCoinPoolClaimed(bool isBAYC, uint256 tokenId); + event ApeCoinPoolWithdrew(bool isBAYC, uint256 tokenId); + event ApeCoinPoolPairDeposited( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + event ApeCoinPoolPairCompounded( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + event ApeCoinPoolPairClaimed(bool isBAYC, uint256 apeTokenId); + + function depositApeCoinPool( + IApeCoinPool.ApeCoinPoolState storage poolState, + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + mapping(address => uint256) storage sApeBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata tokenIds + ) external { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, Errors.INVALID_PARAMETER); + + if (isBAYC) { + vars.apeToken = vars.bayc; + vars.nApe = vars.nBayc; + vars.positionCap = vars.baycMatchedCap; + } else { + vars.apeToken = vars.mayc; + vars.nApe = vars.nMayc; + vars.positionCap = vars.maycMatchedCap; + } + address msgSender = msg.sender; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + require( + msgSender == IERC721(vars.nApe).ownerOf(tokenId), + Errors.NOT_THE_OWNER + ); + + uint256 currentMatchCount = apeMatchedCount[vars.apeToken][tokenId]; + if (currentMatchCount == 0) { + IERC721(vars.apeToken).safeTransferFrom( + vars.nApe, + address(this), + tokenId + ); + } + apeMatchedCount[vars.apeToken][tokenId] = currentMatchCount + 1; + + //update status + poolState + .tokenStatus[tokenId] + .rewardsDebt = accumulatedRewardsPerNft; + poolState.tokenStatus[tokenId].isInPool = true; + + // construct staking data + _nfts[index] = ApeCoinStaking.SingleNft({ + tokenId: tokenId, + amount: vars.positionCap.toUint224() + }); + + //emit event + emit ApeCoinPoolDeposited(isBAYC, tokenId); + } + + //transfer ape coin + uint256 totalApeCoinAmount = vars.positionCap * arrayLength; + IERC20(vars.apeCoin).safeTransferFrom( + msgSender, + address(this), + totalApeCoinAmount + ); + sApeBalance[msgSender] += totalApeCoinAmount; + + //stake in ApeCoinStaking + if (isBAYC) { + vars.apeCoinStaking.depositBAYC(_nfts); + } else { + vars.apeCoinStaking.depositMAYC(_nfts); + } + + poolState.totalPosition += arrayLength.toUint32(); + } + + function compoundApeCoinPool( + IApeCoinPool.ApeCoinPoolState storage poolState, + mapping(address => uint256) storage cApeShareBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata tokenIds + ) external { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, Errors.INVALID_PARAMETER); + + uint256[] memory _nfts = new uint256[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + require( + poolState.tokenStatus[tokenId].isInPool, + "not in ape coin pool" + ); + + // construct staking data + _nfts[index] = tokenId; + + emit ApeCoinPoolCompounded(isBAYC, tokenId); + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + //claim from ApeCoinStaking + if (isBAYC) { + vars.apeCoinStaking.claimSelfBAYC(_nfts); + } else { + vars.apeCoinStaking.claimSelfMAYC(_nfts); + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); + + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + uint256 cApeShare = vars.totalClaimedApe.rayDiv(cApeExchangeRate); + uint256 compoundFee = cApeShare.percentMul(vars.compoundFee); + cApeShare -= compoundFee; + poolState.accumulatedRewardsPerNft += + cApeShare.toUint128() / + poolState.totalPosition; + + if (compoundFee > 0) { + cApeShareBalance[address(this)] += compoundFee; + } + } + + function claimApeCoinPool( + IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata tokenIds + ) external { + ApeStakingCommonLogic.validateTokenIdArray(tokenIds); + + _claimApeCoinPool(poolState, vars, isBAYC, true, tokenIds); + } + + function depositApeCoinPairPool( + IApeCoinPool.ApeCoinPoolState storage poolState, + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + mapping(address => uint256) storage sApeBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + Errors.INVALID_PARAMETER + ); + + if (isBAYC) { + vars.apeToken = vars.bayc; + vars.nApe = vars.nBayc; + } else { + vars.apeToken = vars.mayc; + vars.nApe = vars.nMayc; + } + vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + ApeCoinStaking.PairNftDepositWithAmount[] + memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + arrayLength + ); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + //check ntoken owner + { + address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); + address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); + require( + msg.sender == nApeOwner && msg.sender == nBakcOwner, + Errors.NOT_THE_OWNER + ); + } + + uint256 currentMatchCount = apeMatchedCount[vars.apeToken][ + apeTokenId + ]; + if (currentMatchCount == 0) { + IERC721(vars.apeToken).safeTransferFrom( + vars.nApe, + address(this), + apeTokenId + ); + } + apeMatchedCount[vars.apeToken][apeTokenId] = currentMatchCount + 1; + + //update status + poolState.tokenStatus[apeTokenId].rewardsDebt = vars + .accumulatedRewardsPerNft; + poolState.tokenStatus[apeTokenId].isInPool = true; + + IERC721(vars.bakc).safeTransferFrom( + vars.nBakc, + address(this), + bakcTokenId + ); + + // construct staking data + _nftPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184() + }); + + //emit event + emit ApeCoinPoolPairDeposited(isBAYC, apeTokenId, bakcTokenId); + } + + //transfer ape coin + uint256 totalApeCoinAmount = vars.bakcMatchedCap * arrayLength; + IERC20(vars.apeCoin).safeTransferFrom( + msg.sender, + address(this), + totalApeCoinAmount + ); + sApeBalance[msg.sender] += totalApeCoinAmount; + + //stake in ApeCoinStaking + ApeCoinStaking.PairNftDepositWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + 0 + ); + if (isBAYC) { + vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); + } + + poolState.totalPosition += arrayLength.toUint32(); + } + + function compoundApeCoinPairPool( + IApeCoinPool.ApeCoinPoolState storage poolState, + mapping(address => uint256) storage cApeShareBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external { + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + Errors.INVALID_PARAMETER + ); + + ApeCoinStaking.PairNft[] + memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + require( + poolState.tokenStatus[apeTokenId].isInPool, + "not in ape coin pool" + ); + + // construct staking data + _nftPairs[index] = ApeCoinStaking.PairNft({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId + }); + } + + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + //claim from ApeCoinStaking + { + ApeCoinStaking.PairNft[] + memory _otherPairs = new ApeCoinStaking.PairNft[](0); + if (isBAYC) { + vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); + } + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); + + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + uint256 cApeShare = vars.totalClaimedApe.rayDiv(cApeExchangeRate); + uint256 compoundFee = cApeShare.percentMul(vars.compoundFee); + cApeShare -= compoundFee; + poolState.accumulatedRewardsPerNft += + cApeShare.toUint128() / + poolState.totalPosition; + + if (compoundFee > 0) { + cApeShareBalance[address(this)] += compoundFee; + } + } + + function claimApeCoinPairPool( + IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata tokenIds + ) external { + ApeStakingCommonLogic.validateTokenIdArray(tokenIds); + + _claimApeCoinPool(poolState, vars, isBAYC, false, tokenIds); + } + + function withdrawApeCoinPool( + IApeCoinPool.ApeCoinPoolState storage poolState, + mapping(address => mapping(uint32 => IParaApeStaking.ApeStatus)) + storage apesStatus, + mapping(address => uint256) storage sApeBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata tokenIds, + bool receiveApeCoin + ) external { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, Errors.INVALID_PARAMETER); + + if (isBAYC) { + vars.apeToken = vars.bayc; + vars.nApe = vars.nBayc; + vars.positionCap = vars.baycMatchedCap; + } else { + vars.apeToken = vars.mayc; + vars.nApe = vars.nMayc; + vars.positionCap = vars.maycMatchedCap; + } + address msgSender = msg.sender; + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + require( + msgSender == IERC721(vars.nApe).ownerOf(tokenId), + Errors.NOT_THE_OWNER + ); + + IParaApeStaking.ApeStatus memory cacheStatus = apesStatus[ + vars.apeToken + ][tokenId]; + require(cacheStatus.isInApeCoinPool, "not in ape coin pool"); + cacheStatus.isInApeCoinPool = false; + cacheStatus.matchedCount -= 1; + //check if need transfer + if (cacheStatus.matchedCount == 0) { + IERC721(vars.apeToken).safeTransferFrom( + address(this), + vars.nApe, + tokenId + ); + } + //update status + apesStatus[vars.apeToken][tokenId] = cacheStatus; + + // construct staking data + _nfts[index] = ApeCoinStaking.SingleNft({ + tokenId: tokenId, + amount: vars.positionCap.toUint224() + }); + + //emit event + emit ApeCoinPoolWithdrew(isBAYC, tokenId); + } + + //withdraw from ApeCoinStaking + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + if (isBAYC) { + vars.apeCoinStaking.withdrawSelfBAYC(_nfts); + } else { + vars.apeCoinStaking.withdrawSelfMAYC(_nfts); + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + + uint256 totalApeCoinAmount = vars.positionCap * arrayLength; + sApeBalance[msgSender] -= totalApeCoinAmount; + if (receiveApeCoin) { + (, , , , , uint256 healthFactor, ) = IPool(vars.pool) + .getUserAccountData(msgSender); + //need to check user health factor + require( + healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD, + Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD + ); + + IERC20(vars.apeCoin).safeTransfer(msgSender, vars.totalClaimedApe); + } else { + IPool(vars.pool).supply( + vars.apeCoin, + vars.totalClaimedApe, + msgSender, + 0 + ); + } + } + + // function _reduceSApeBalance() + + function _claimApeCoinPool( + IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + bool isSinglePool, + uint32[] calldata apeTokenIds + ) internal { + ( + address owner, + uint256 pendingReward, + uint128 accumulatedRewardsPerNft + ) = calculatePendingReward(poolState, vars, isBAYC, apeTokenIds); + + if (pendingReward > 0) { + uint256 arrayLength = apeTokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + + poolState + .tokenStatus[apeTokenId] + .rewardsDebt = accumulatedRewardsPerNft; + + //emit event + if (isSinglePool) { + emit ApeCoinPoolClaimed(isBAYC, apeTokenId); + } else { + emit ApeCoinPoolPairClaimed(isBAYC, apeTokenId); + } + } + + IERC20(vars.cApe).safeTransfer(owner, pendingReward); + } + } + + function calculatePendingReward( + IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata tokenIds + ) + public + view + returns ( + address claimFor, + uint256 pendingReward, + uint128 accumulatedRewardsPerNft + ) + { + uint256 rewardShares; + uint256 arrayLength = tokenIds.length; + accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + address nApe = isBAYC ? vars.nBayc : vars.nMayc; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + //just need to check ape ntoken owner + { + address nApeOwner = IERC721(nApe).ownerOf(tokenId); + if (claimFor == address(0)) { + claimFor = nApeOwner; + } else { + require(nApeOwner == claimFor, Errors.NOT_THE_SAME_OWNER); + } + } + + //check is in pool + require( + poolState.tokenStatus[tokenId].isInPool, + "not in ape coin pool" + ); + + //update reward, to save gas we don't claim pending reward in ApeCoinStaking. + rewardShares += (accumulatedRewardsPerNft - + poolState.tokenStatus[tokenId].rewardsDebt); + } + pendingReward = ICApe(vars.cApe).getPooledApeByShares(rewardShares); + + return (claimFor, pendingReward, accumulatedRewardsPerNft); + } +} diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 12e247243..0e6c520a7 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -8,6 +8,7 @@ import "../../interfaces/ICApe.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import {IPool} from "../../interfaces/IPool.sol"; +import "../../protocol/libraries/helpers/Errors.sol"; /** * @title ApeStakingVaultLogic library @@ -19,6 +20,19 @@ library ApeStakingCommonLogic { using SafeCast for uint256; using WadRayMath for uint256; + function validateTokenIdArray(uint32[] calldata tokenIds) internal pure { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, Errors.INVALID_PARAMETER); + if (arrayLength >= 2) { + for (uint256 index = 1; index < arrayLength; index++) { + require( + tokenIds[index] > tokenIds[index - 1], + Errors.INVALID_PARAMETER + ); + } + } + } + function depositCApeShareForUser( mapping(address => uint256) storage cApeShareBalance, address user, @@ -48,7 +62,7 @@ library ApeStakingCommonLogic { cApeDebtShare -= repayAmount.rayDiv(vars.latestBorrowIndex).rayDiv( vars.cApeExchangeRate ); - poolState.cApeDebtShare = cApeDebtShare.toUint128(); + poolState.cApeDebtShare = cApeDebtShare.toUint104(); uint256 compoundFee = 0; if (vars.totalClaimedApe > debtInterest) { uint256 shareRewardAmount = (vars.totalClaimedApe - debtInterest) @@ -56,10 +70,10 @@ library ApeStakingCommonLogic { compoundFee = shareRewardAmount.percentMul(vars.compoundFee); shareRewardAmount -= compoundFee; //update reward index - uint128 currentTotalPosition = poolState.totalPosition; + uint104 currentTotalPosition = poolState.totalPosition; if (currentTotalPosition != 0) { poolState.accumulatedRewardsPerNft += - shareRewardAmount.toUint128() / + shareRewardAmount.toUint104() / currentTotalPosition; } else { compoundFee += shareRewardAmount; diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index 709cec945..0720bec69 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -71,7 +71,7 @@ library ApeStakingP2PLogic { ); //3 transfer token - _handleApeTransfer(apeOrder, vars); + _handleApeTransfer(apeMatchedCount, apeOrder, vars); uint256 apeAmount = _handleCApeTransferAndConvert(apeCoinOrder, vars); //4 create match order @@ -90,7 +90,6 @@ library ApeStakingP2PLogic { }); orderHash = getMatchedOrderHash(matchedOrder); matchedOrders[orderHash] = matchedOrder; - apeMatchedCount[apeOrder.token][apeOrder.tokenId] += 1; //5 stake for ApeCoinStaking ApeCoinStaking.SingleNft[] @@ -147,7 +146,7 @@ library ApeStakingP2PLogic { ); //3 transfer token - _handleApeTransfer(apeOrder, vars); + _handleApeTransfer(apeMatchedCount, apeOrder, vars); IERC721(vars.bakc).safeTransferFrom( vars.nBakc, address(this), @@ -171,7 +170,6 @@ library ApeStakingP2PLogic { }); orderHash = getMatchedOrderHash(matchedOrder); matchedOrders[orderHash] = matchedOrder; - apeMatchedCount[apeOrder.token][apeOrder.tokenId] += 1; //5 stake for ApeCoinStaking ApeCoinStaking.PairNftDepositWithAmount[] @@ -515,11 +513,12 @@ library ApeStakingP2PLogic { } function _handleApeTransfer( + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, IApeStakingP2P.ListingOrder calldata order, IParaApeStaking.ApeStakingVaultCacheVars memory vars ) internal { - address currentOwner = IERC721(order.token).ownerOf(order.tokenId); - if (currentOwner != address(this)) { + uint256 currentMatchCount = apeMatchedCount[order.token][order.tokenId]; + if (currentMatchCount == 0) { address nTokenAddress = _getApeNTokenAddress(vars, order.token); IERC721(order.token).safeTransferFrom( nTokenAddress, @@ -527,6 +526,7 @@ library ApeStakingP2PLogic { order.tokenId ); } + apeMatchedCount[order.token][order.tokenId] = currentMatchCount + 1; } function _handleCApeTransferAndConvert( diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 6e2a738ff..543b171c3 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -132,7 +132,7 @@ library ApeStakingPairPoolLogic { emit PairNFTDeposited(isBAYC, apeTokenId, bakcTokenId); } - poolState.totalPosition += arrayLength.toUint32(); + poolState.totalPosition += arrayLength.toUint24(); } function stakingPairNFT( @@ -192,7 +192,7 @@ library ApeStakingPairPoolLogic { vars, totalBorrow ); - poolState.cApeDebtShare += cApeDebtShare.toUint128(); + poolState.cApeDebtShare += cApeDebtShare.toUint104(); //stake in ApeCoinStaking ApeCoinStaking.PairNftDepositWithAmount[] @@ -207,7 +207,7 @@ library ApeStakingPairPoolLogic { vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); } - poolState.stakingPosition += arrayLength.toUint32(); + poolState.stakingPosition += arrayLength.toUint24(); } function withdrawPairNFT( @@ -243,7 +243,7 @@ library ApeStakingPairPoolLogic { memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength ); - uint32 stakingPair = 0; + uint24 stakingPair = 0; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; @@ -288,7 +288,7 @@ library ApeStakingPairPoolLogic { } //update state - poolState.totalPosition -= arrayLength.toUint32(); + poolState.totalPosition -= arrayLength.toUint24(); //withdraw from ApeCoinStaking and compound if (stakingPair > 0) { @@ -372,9 +372,9 @@ library ApeStakingPairPoolLogic { uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external { - uint256 arrayLength = apeTokenIds.length; + ApeStakingCommonLogic.validateTokenIdArray(apeTokenIds); require( - arrayLength == bakcTokenIds.length && arrayLength > 0, + apeTokenIds.length == bakcTokenIds.length, Errors.INVALID_PARAMETER ); diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index b549c7d52..e98e45fdf 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -114,13 +114,13 @@ library ApeStakingSinglePoolLogic { if (nft == vars.bayc) { vaultStorage .poolStates[BAYC_SINGLE_POOL_ID] - .totalPosition += arrayLength.toUint32(); + .totalPosition += arrayLength.toUint24(); } else if (nft == vars.mayc) { vaultStorage .poolStates[MAYC_SINGLE_POOL_ID] - .totalPosition += arrayLength.toUint32(); + .totalPosition += arrayLength.toUint24(); } else { - vaultStorage.bakcPoolState.totalPosition += arrayLength.toUint32(); + vaultStorage.bakcPoolState.totalPosition += arrayLength.toUint24(); } } @@ -162,7 +162,7 @@ library ApeStakingSinglePoolLogic { vars, totalBorrow ); - poolState.cApeDebtShare += cApeDebtShare.toUint128(); + poolState.cApeDebtShare += cApeDebtShare.toUint104(); //stake in ApeCoinStaking if (isBAYC) { @@ -171,7 +171,7 @@ library ApeStakingSinglePoolLogic { vars.apeCoinStaking.depositMAYC(_nfts); } - poolState.stakingPosition += arrayLength.toUint32(); + poolState.stakingPosition += arrayLength.toUint24(); } function stakingBAKC( @@ -230,12 +230,12 @@ library ApeStakingSinglePoolLogic { ); if (isBAYC) { vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); - bakcPoolState.baycStakingPosition += arrayLength.toUint32(); - bakcPoolState.baycCApeDebtShare += cApeDebtShare.toUint128(); + bakcPoolState.baycStakingPosition += arrayLength.toUint24(); + bakcPoolState.baycCApeDebtShare += cApeDebtShare.toUint104(); } else { vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); - bakcPoolState.maycStakingPosition += arrayLength.toUint32(); - bakcPoolState.maycCApeDebtShare += cApeDebtShare.toUint128(); + bakcPoolState.maycStakingPosition += arrayLength.toUint24(); + bakcPoolState.maycCApeDebtShare += cApeDebtShare.toUint104(); } } @@ -393,8 +393,7 @@ library ApeStakingSinglePoolLogic { address nft, uint32[] calldata tokenIds ) external { - uint256 arrayLength = tokenIds.length; - require(arrayLength > 0, Errors.INVALID_PARAMETER); + ApeStakingCommonLogic.validateTokenIdArray(tokenIds); vars.accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( vaultStorage, @@ -477,7 +476,7 @@ library ApeStakingSinglePoolLogic { vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; } - apePoolState.totalPosition -= tokenIds.length.toUint32(); + apePoolState.totalPosition -= tokenIds.length.toUint24(); ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](tokenIds.length); @@ -485,8 +484,8 @@ library ApeStakingSinglePoolLogic { memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( tokenIds.length ); - uint32 singleStakingCount; - uint32 pairStakingCount; + uint24 singleStakingCount; + uint24 pairStakingCount; for (uint256 index = 0; index < tokenIds.length; index++) { uint32 tokenId = tokenIds[index]; @@ -615,7 +614,7 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; IParaApeStaking.BAKCPoolState storage bakcPoolState = vaultStorage .bakcPoolState; - vaultStorage.bakcPoolState.totalPosition -= arrayLength.toUint32(); + vaultStorage.bakcPoolState.totalPosition -= arrayLength.toUint24(); ApeCoinStaking.PairNftWithdrawWithAmount[] memory baycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength @@ -624,8 +623,8 @@ library ApeStakingSinglePoolLogic { memory maycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength ); - uint32 baycPairCount; - uint32 maycPairCount; + uint24 baycPairCount; + uint24 maycPairCount; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -861,9 +860,9 @@ library ApeStakingSinglePoolLogic { vars.cApeExchangeRate ); if (isBAYC) { - bakcPoolState.baycCApeDebtShare = cApeDebtShare.toUint128(); + bakcPoolState.baycCApeDebtShare = cApeDebtShare.toUint104(); } else { - bakcPoolState.maycCApeDebtShare = cApeDebtShare.toUint128(); + bakcPoolState.maycCApeDebtShare = cApeDebtShare.toUint104(); } //calculate compound fee @@ -878,19 +877,19 @@ library ApeStakingSinglePoolLogic { vars.apeRewardRatio ); - uint128 apeTotalPosition = apePoolState.totalPosition; + uint104 apeTotalPosition = apePoolState.totalPosition; if (apeTotalPosition != 0) { apePoolState.accumulatedRewardsPerNft += - apeShareAmount.toUint128() / + apeShareAmount.toUint104() / apeTotalPosition; } else { compoundFee += apeShareAmount; } - uint128 bakcTotalPosition = bakcPoolState.totalPosition; + uint104 bakcTotalPosition = bakcPoolState.totalPosition; shareRewardAmount -= apeShareAmount; if (bakcTotalPosition != 0) { bakcPoolState.accumulatedRewardsPerNft += - shareRewardAmount.toUint128() / + shareRewardAmount.toUint104() / bakcTotalPosition; } else { compoundFee += shareRewardAmount; diff --git a/contracts/dependencies/openzeppelin/contracts/SafeCast.sol b/contracts/dependencies/openzeppelin/contracts/SafeCast.sol index ea2c33a01..2124771ff 100644 --- a/contracts/dependencies/openzeppelin/contracts/SafeCast.sol +++ b/contracts/dependencies/openzeppelin/contracts/SafeCast.sol @@ -71,6 +71,23 @@ library SafeCast { return uint128(value); } + /** + * @dev Returns the downcasted uint104 from uint256, reverting on + * overflow (when the input is greater than largest uint104). + * + * Counterpart to Solidity's `uint104` operator. + * + * Requirements: + * + * - input must fit into 104 bits + * + * _Available since v4.7._ + */ + function toUint104(uint256 value) internal pure returns (uint104) { + require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits"); + return uint104(value); + } + /** * @dev Returns the downcasted uint96 from uint256, reverting on * overflow (when the input is greater than largest uint96). @@ -142,6 +159,23 @@ library SafeCast { return uint32(value); } + /** + * @dev Returns the downcasted uint24 from uint256, reverting on + * overflow (when the input is greater than largest uint24). + * + * Counterpart to Solidity's `uint24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + * + * _Available since v4.7._ + */ + function toUint24(uint256 value) internal pure returns (uint24) { + require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits"); + return uint24(value); + } + /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). diff --git a/contracts/interfaces/IApeCoinPool.sol b/contracts/interfaces/IApeCoinPool.sol new file mode 100644 index 000000000..8b5c4f639 --- /dev/null +++ b/contracts/interfaces/IApeCoinPool.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.8.10; + +interface IApeCoinPool { + struct TokenStatus { + //record tokenId reward debt position + uint128 rewardsDebt; + // identify if tokenId is in pool + bool isInPool; + } + + struct ApeCoinPoolState { + // total NFT position count + uint32 totalPosition; + // accumulated cApe reward for per NFT position + uint128 accumulatedRewardsPerNft; + //tokenId => reward debt position + mapping(uint256 => TokenStatus) tokenStatus; + } +} diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index 452f1feb6..b1df999fd 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -13,14 +13,14 @@ interface IApeStakingVault { bool isInPool; } struct PoolState { - //pool cape debt token share - uint128 cApeDebtShare; - // total NFT position count - uint32 totalPosition; - // total staking position - uint32 stakingPosition; + //pool cape debt token share, max value for uint104 is 2e31, ape coin total supply is 1e27. + uint104 cApeDebtShare; // accumulated cApe reward for per NFT position - uint128 accumulatedRewardsPerNft; + uint104 accumulatedRewardsPerNft; + // total NFT position count, max value for uint24 is 16777216 + uint24 totalPosition; + // total staking position + uint24 stakingPosition; //tokenId => reward debt position mapping(uint256 => TokenStatus) tokenStatus; //for pair pool, apeTokenId => PairingStatus @@ -28,18 +28,18 @@ interface IApeStakingVault { } struct BAKCPoolState { + // accumulated cApe reward for per NFT position + uint104 accumulatedRewardsPerNft; // total NFT position count - uint32 totalPosition; + uint24 totalPosition; //bayc pair cape debt token share - uint128 baycCApeDebtShare; + uint104 baycCApeDebtShare; //bayc pair staking position - uint32 baycStakingPosition; + uint24 baycStakingPosition; //mayc pair cape debt token share - uint128 maycCApeDebtShare; + uint104 maycCApeDebtShare; //mayc pair staking position - uint32 maycStakingPosition; - // accumulated cApe reward for per NFT position - uint128 accumulatedRewardsPerNft; + uint24 maycStakingPosition; //tokenId => reward debt position mapping(uint256 => TokenStatus) tokenStatus; } diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index 2df2a18c6..e1eceaec3 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.10; import "../dependencies/yoga-labs/ApeCoinStaking.sol"; import "./IApeStakingVault.sol"; import "./IApeStakingP2P.sol"; +import "./IApeCoinPool.sol"; interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { struct ApeStakingVaultCacheVars { @@ -39,6 +40,12 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { bool isPaired; } + struct ApeStatus { + uint32 matchedCount; + bool isInApeCoinPool; + bool isInApeCoinPairPool; + } + /** * @dev Emitted during setApeStakingBot() * @param oldBot The address of the old compound bot From 7c82970bf50c7a6860a91363a6d0efcfcb3f0059 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 19 Jul 2023 21:41:40 +0800 Subject: [PATCH 60/99] chore: Ape coin pool --- contracts/apestaking/ParaApeStaking.sol | 161 +++++- .../apestaking/logic/ApeCoinPoolLogic.sol | 507 ++++++++++++------ .../logic/ApeStakingPairPoolLogic.sol | 14 +- .../logic/ApeStakingSinglePoolLogic.sol | 2 +- contracts/interfaces/IApeCoinPool.sol | 18 +- contracts/interfaces/IApeStakingVault.sol | 49 -- contracts/interfaces/IParaApeStaking.sol | 66 ++- contracts/interfaces/IPoolApeStaking.sol | 56 -- contracts/protocol/pool/PoolApeStaking.sol | 341 ------------ 9 files changed, 566 insertions(+), 648 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index b7bcb431f..4a367faa5 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -16,6 +16,7 @@ import {WadRayMath} from "../protocol/libraries/math/WadRayMath.sol"; import "./logic/ApeStakingP2PLogic.sol"; import "./logic/ApeStakingPairPoolLogic.sol"; import "./logic/ApeStakingSinglePoolLogic.sol"; +import "./logic/ApeCoinPoolLogic.sol"; import "./logic/ApeStakingCommonLogic.sol"; import "../protocol/libraries/helpers/Errors.sol"; @@ -49,12 +50,13 @@ contract ParaApeStaking is uint256 private immutable maycMatchedCap; uint256 private immutable bakcMatchedCap; IACLManager private immutable aclManager; + uint16 private immutable sApeReserveId; + address private immutable psApe; //record Ape in P2P and ApeCoin pool - mapping(address => IApeCoinPool.ApeCoinPoolState) private apeCoinPoolStates; mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; mapping(address => uint256) private cApeShareBalance; - mapping(address => uint256) private sApeBalance; + mapping(address => SApeBalance) private sApeBalance; address public apeStakingBot; uint64 public compoundFee; @@ -93,6 +95,12 @@ contract ParaApeStaking is baycMatchedCap = baycPool.currentTimeRange.capPerPosition; maycMatchedCap = maycPool.currentTimeRange.capPerPosition; bakcMatchedCap = bakcPool.currentTimeRange.capPerPosition; + + DataTypes.ReserveData memory sApeData = IPool(_pool).getReserveData( + DataTypes.SApeAddress + ); + psApe = sApeData.xTokenAddress; + sApeReserveId = sApeData.id; } function initialize() public initializer { @@ -207,42 +215,157 @@ contract ParaApeStaking is /* *Ape Coin Staking Pool Logic */ + mapping(uint256 => ApeCoinPoolState) internal apeCoinPoolStates; + + function isApeInApeCoinPool(bool isBAYC, uint32 tokenId) external view {} + + function withdrawSApe(uint128 amount) external whenNotPaused nonReentrant { + ApeCoinPoolLogic.withdrawSApe( + sApeBalance, + pool, + apeCoin, + sApeReserveId, + msg.sender, + amount + ); + } function depositApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external whenNotPaused nonReentrant - {} - - function depositApeCoinPairPool( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant {} + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ApeCoinPoolLogic.depositApeCoinPool( + apeCoinPoolStates[poolId], + apeMatchedCount, + sApeBalance, + vars, + isBAYC, + tokenIds + ); + } function CompoundApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external whenNotPaused nonReentrant - {} + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ApeCoinPoolLogic.compoundApeCoinPool( + apeCoinPoolStates[poolId], + cApeShareBalance, + vars, + isBAYC, + tokenIds + ); + } - function CompoundApeCoinPairPool( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant {} + function claimApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) + external + whenNotPaused + nonReentrant + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ApeCoinPoolLogic.claimApeCoinPool( + apeCoinPoolStates[poolId], + vars, + isBAYC, + tokenIds + ); + } - function WithdrawApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) + function withdrawApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external whenNotPaused nonReentrant - {} + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ApeCoinPoolLogic.withdrawApeCoinPool( + apeCoinPoolStates[poolId], + cApeShareBalance, + apeMatchedCount, + sApeBalance, + vars, + isBAYC, + tokenIds + ); + } + + function depositApeCoinPairPool( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external whenNotPaused nonReentrant { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ApeCoinPoolLogic.depositApeCoinPairPool( + apeCoinPoolStates[poolId], + apeMatchedCount, + sApeBalance, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + } + + function CompoundApeCoinPairPool( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external whenNotPaused nonReentrant { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ApeCoinPoolLogic.depositApeCoinPairPool( + apeCoinPoolStates[poolId], + apeMatchedCount, + sApeBalance, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + } - function WithdrawApeCoinPairPool( + function withdrawApeCoinPairPool( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant {} + ) external whenNotPaused nonReentrant { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ApeCoinPoolLogic.withdrawApeCoinPairPool( + apeCoinPoolStates[poolId], + cApeShareBalance, + apeMatchedCount, + sApeBalance, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); + } /* * P2P Pair Staking Logic diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index bbd90aeae..9ac274124 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -12,6 +12,8 @@ import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import "./ApeStakingCommonLogic.sol"; import "../../protocol/libraries/helpers/Errors.sol"; +import {UserConfiguration} from "../../protocol/libraries/configuration/UserConfiguration.sol"; +import {DataTypes} from "../../protocol/libraries/types/DataTypes.sol"; /** * @title ApeCoinPoolLogic library @@ -19,15 +21,16 @@ import "../../protocol/libraries/helpers/Errors.sol"; * @notice Implements the base logic for para ape staking apecoin pool */ library ApeCoinPoolLogic { + using UserConfiguration for DataTypes.UserConfigurationMap; using PercentageMath for uint256; using SafeCast for uint256; using SafeERC20 for IERC20; using WadRayMath for uint256; - uint256 public constant BAYC_BAKC_PAIR_POOL_ID = 1; - uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; - uint256 public constant BAYC_SINGLE_POOL_ID = 3; - uint256 public constant MAYC_SINGLE_POOL_ID = 4; + uint256 public constant BAYC_APECOIN_POOL_ID = 1; + uint256 public constant MAYC_APECOIN_POOL_ID = 2; + uint256 public constant BAYC_BAKC_APECOIN_POOL_ID = 3; + uint256 public constant MAYC_BAKC_APECOIN_POOL_ID = 4; uint256 constant BAYC_POOL_ID = 1; uint256 constant MAYC_POOL_ID = 2; @@ -43,22 +46,58 @@ library ApeCoinPoolLogic { event ApeCoinPoolCompounded(bool isBAYC, uint256 tokenId); event ApeCoinPoolClaimed(bool isBAYC, uint256 tokenId); event ApeCoinPoolWithdrew(bool isBAYC, uint256 tokenId); - event ApeCoinPoolPairDeposited( + event ApeCoinPairPoolDeposited( bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId ); - event ApeCoinPoolPairCompounded( + event ApeCoinPairPoolCompounded( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + event ApeCoinPairPoolClaimed(bool isBAYC, uint256 apeTokenId); + event ApeCoinPairPoolWithdrew( bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId ); - event ApeCoinPoolPairClaimed(bool isBAYC, uint256 apeTokenId); + + function withdrawSApe( + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + address pool, + address apeCoin, + uint16 sApeReserveId, + address user, + uint128 amount + ) external { + IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; + require(sApeBalanceCache.freeBalance >= amount, "balance not enough"); + sApeBalanceCache.freeBalance -= amount; + + DataTypes.UserConfigurationMap memory userConfig = IPool(pool) + .getUserConfiguration(user); + bool usageAsCollateralEnabled = userConfig.isUsingAsCollateral( + sApeReserveId + ); + if (usageAsCollateralEnabled) { + (, , , , , uint256 healthFactor, ) = IPool(pool).getUserAccountData( + user + ); + //need to check user health factor + require( + healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD, + Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD + ); + } + + IERC20(apeCoin).safeTransfer(user, amount); + } function depositApeCoinPool( - IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeCoinPoolState storage poolState, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, - mapping(address => uint256) storage sApeBalance, + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata tokenIds @@ -114,13 +153,13 @@ library ApeCoinPoolLogic { } //transfer ape coin - uint256 totalApeCoinAmount = vars.positionCap * arrayLength; - IERC20(vars.apeCoin).safeTransferFrom( - msgSender, - address(this), - totalApeCoinAmount + uint256 totalApeCoinNeeded = vars.positionCap * arrayLength; + _prepareApeCoin( + sApeBalance, + vars, + totalApeCoinNeeded.toUint128(), + msgSender ); - sApeBalance[msgSender] += totalApeCoinAmount; //stake in ApeCoinStaking if (isBAYC) { @@ -133,7 +172,7 @@ library ApeCoinPoolLogic { } function compoundApeCoinPool( - IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeCoinPoolState storage poolState, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, @@ -166,28 +205,17 @@ library ApeCoinPoolLogic { } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe + _distributePoolReward( + poolState, + cApeShareBalance, + vars, + vars.totalClaimedApe, + poolState.totalPosition ); - - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); - uint256 cApeShare = vars.totalClaimedApe.rayDiv(cApeExchangeRate); - uint256 compoundFee = cApeShare.percentMul(vars.compoundFee); - cApeShare -= compoundFee; - poolState.accumulatedRewardsPerNft += - cApeShare.toUint128() / - poolState.totalPosition; - - if (compoundFee > 0) { - cApeShareBalance[address(this)] += compoundFee; - } } function claimApeCoinPool( - IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeCoinPoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata tokenIds @@ -197,10 +225,104 @@ library ApeCoinPoolLogic { _claimApeCoinPool(poolState, vars, isBAYC, true, tokenIds); } + function withdrawApeCoinPool( + IParaApeStaking.ApeCoinPoolState storage poolState, + mapping(address => uint256) storage cApeShareBalance, + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint32[] calldata tokenIds + ) external { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, Errors.INVALID_PARAMETER); + + _claimApeCoinPool(poolState, vars, isBAYC, true, tokenIds); + + if (isBAYC) { + vars.apeToken = vars.bayc; + vars.nApe = vars.nBayc; + vars.positionCap = vars.baycMatchedCap; + } else { + vars.apeToken = vars.mayc; + vars.nApe = vars.nMayc; + vars.positionCap = vars.maycMatchedCap; + } + address msgSender = msg.sender; + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + require( + msgSender == IERC721(vars.nApe).ownerOf(tokenId), + Errors.NOT_THE_OWNER + ); + + require( + poolState.tokenStatus[tokenId].isInPool, + "not in ape coin pool" + ); + + uint256 matchedCount = apeMatchedCount[vars.apeToken][tokenId]; + if (matchedCount == 1) { + IERC721(vars.apeToken).safeTransferFrom( + address(this), + vars.nApe, + tokenId + ); + } + apeMatchedCount[vars.apeToken][tokenId] = matchedCount - 1; + delete poolState.tokenStatus[tokenId]; + + // construct staking data + _nfts[index] = ApeCoinStaking.SingleNft({ + tokenId: tokenId, + amount: vars.positionCap.toUint224() + }); + + //emit event + emit ApeCoinPoolWithdrew(isBAYC, tokenId); + } + + //withdraw from ApeCoinStaking + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + if (isBAYC) { + vars.apeCoinStaking.withdrawSelfBAYC(_nfts); + } else { + vars.apeCoinStaking.withdrawSelfMAYC(_nfts); + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + + uint128 totalApeCoinAmount = (vars.bakcMatchedCap * arrayLength) + .toUint128(); + IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[ + msgSender + ]; + sApeBalanceCache.stakedBalance -= totalApeCoinAmount; + sApeBalanceCache.freeBalance += totalApeCoinAmount; + sApeBalance[msgSender] = sApeBalanceCache; + + //distribute reward + uint32 totalPosition = poolState.totalPosition; + totalPosition -= arrayLength.toUint32(); + if (vars.totalClaimedApe > totalApeCoinAmount) { + _distributePoolReward( + poolState, + cApeShareBalance, + vars, + vars.totalClaimedApe - totalApeCoinAmount, + totalPosition + ); + } + poolState.totalPosition = totalPosition; + } + function depositApeCoinPairPool( - IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeCoinPoolState storage poolState, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, - mapping(address => uint256) storage sApeBalance, + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata apeTokenIds, @@ -269,17 +391,17 @@ library ApeCoinPoolLogic { }); //emit event - emit ApeCoinPoolPairDeposited(isBAYC, apeTokenId, bakcTokenId); + emit ApeCoinPairPoolDeposited(isBAYC, apeTokenId, bakcTokenId); } //transfer ape coin - uint256 totalApeCoinAmount = vars.bakcMatchedCap * arrayLength; - IERC20(vars.apeCoin).safeTransferFrom( - msg.sender, - address(this), - totalApeCoinAmount + uint256 totalApeCoinNeeded = vars.bakcMatchedCap * arrayLength; + _prepareApeCoin( + sApeBalance, + vars, + totalApeCoinNeeded.toUint128(), + msg.sender ); - sApeBalance[msg.sender] += totalApeCoinAmount; //stake in ApeCoinStaking ApeCoinStaking.PairNftDepositWithAmount[] @@ -296,7 +418,7 @@ library ApeCoinPoolLogic { } function compoundApeCoinPairPool( - IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeCoinPoolState storage poolState, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, @@ -340,28 +462,17 @@ library ApeCoinPoolLogic { } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); - - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY + _distributePoolReward( + poolState, + cApeShareBalance, + vars, + vars.totalClaimedApe, + poolState.totalPosition ); - uint256 cApeShare = vars.totalClaimedApe.rayDiv(cApeExchangeRate); - uint256 compoundFee = cApeShare.percentMul(vars.compoundFee); - cApeShare -= compoundFee; - poolState.accumulatedRewardsPerNft += - cApeShare.toUint128() / - poolState.totalPosition; - - if (compoundFee > 0) { - cApeShareBalance[address(this)] += compoundFee; - } } function claimApeCoinPairPool( - IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeCoinPoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata tokenIds @@ -371,136 +482,142 @@ library ApeCoinPoolLogic { _claimApeCoinPool(poolState, vars, isBAYC, false, tokenIds); } - function withdrawApeCoinPool( - IApeCoinPool.ApeCoinPoolState storage poolState, - mapping(address => mapping(uint32 => IParaApeStaking.ApeStatus)) - storage apesStatus, - mapping(address => uint256) storage sApeBalance, + function withdrawApeCoinPairPool( + IParaApeStaking.ApeCoinPoolState storage poolState, + mapping(address => uint256) storage cApeShareBalance, + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, - uint32[] calldata tokenIds, - bool receiveApeCoin + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds ) external { - uint256 arrayLength = tokenIds.length; - require(arrayLength > 0, Errors.INVALID_PARAMETER); + uint256 arrayLength = apeTokenIds.length; + require( + arrayLength == bakcTokenIds.length && arrayLength > 0, + Errors.INVALID_PARAMETER + ); + + _claimApeCoinPool(poolState, vars, isBAYC, false, apeTokenIds); if (isBAYC) { vars.apeToken = vars.bayc; vars.nApe = vars.nBayc; - vars.positionCap = vars.baycMatchedCap; } else { vars.apeToken = vars.mayc; vars.nApe = vars.nMayc; - vars.positionCap = vars.maycMatchedCap; } - address msgSender = msg.sender; - ApeCoinStaking.SingleNft[] - memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + address nApeOwner; + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); for (uint256 index = 0; index < arrayLength; index++) { - uint32 tokenId = tokenIds[index]; + uint32 apeTokenId = apeTokenIds[index]; + uint32 bakcTokenId = bakcTokenIds[index]; + + //check ntoken owner + { + address tmpApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); + if (nApeOwner == address(0)) { + nApeOwner = tmpApeOwner; + } else { + require( + nApeOwner == tmpApeOwner, + Errors.NOT_THE_SAME_OWNER + ); + } + if (nApeOwner != msg.sender) { + address nBakcOwner = IERC721(vars.nBakc).ownerOf( + bakcTokenId + ); + require(msg.sender == nBakcOwner, Errors.NOT_THE_OWNER); + } + } require( - msgSender == IERC721(vars.nApe).ownerOf(tokenId), - Errors.NOT_THE_OWNER + poolState.tokenStatus[apeTokenId].isInPool, + "not in ape coin pool" ); - IParaApeStaking.ApeStatus memory cacheStatus = apesStatus[ - vars.apeToken - ][tokenId]; - require(cacheStatus.isInApeCoinPool, "not in ape coin pool"); - cacheStatus.isInApeCoinPool = false; - cacheStatus.matchedCount -= 1; - //check if need transfer - if (cacheStatus.matchedCount == 0) { - IERC721(vars.apeToken).safeTransferFrom( + //transfer nft + { + uint256 matchedCount = apeMatchedCount[vars.apeToken][ + apeTokenId + ]; + if (matchedCount == 1) { + IERC721(vars.apeToken).safeTransferFrom( + address(this), + vars.apeToken, + apeTokenId + ); + } + apeMatchedCount[vars.apeToken][apeTokenId] = matchedCount - 1; + IERC721(vars.bakc).safeTransferFrom( address(this), - vars.nApe, - tokenId + vars.nBakc, + bakcTokenId ); } - //update status - apesStatus[vars.apeToken][tokenId] = cacheStatus; + + delete poolState.tokenStatus[apeTokenId]; // construct staking data - _nfts[index] = ApeCoinStaking.SingleNft({ - tokenId: tokenId, - amount: vars.positionCap.toUint224() + _nftPairs[index] = ApeCoinStaking.PairNftWithdrawWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184(), + isUncommit: true }); //emit event - emit ApeCoinPoolWithdrew(isBAYC, tokenId); + emit ApeCoinPairPoolWithdrew(isBAYC, apeTokenId, bakcTokenId); } //withdraw from ApeCoinStaking - vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - if (isBAYC) { - vars.apeCoinStaking.withdrawSelfBAYC(_nfts); - } else { - vars.apeCoinStaking.withdrawSelfMAYC(_nfts); + { + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); + if (isBAYC) { + vars.apeCoinStaking.withdrawBAKC(_nftPairs, _otherPairs); + } else { + vars.apeCoinStaking.withdrawBAKC(_otherPairs, _nftPairs); + } + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; } - vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - uint256 totalApeCoinAmount = vars.positionCap * arrayLength; - sApeBalance[msgSender] -= totalApeCoinAmount; - if (receiveApeCoin) { - (, , , , , uint256 healthFactor, ) = IPool(vars.pool) - .getUserAccountData(msgSender); - //need to check user health factor - require( - healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD, - Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - - IERC20(vars.apeCoin).safeTransfer(msgSender, vars.totalClaimedApe); - } else { - IPool(vars.pool).supply( - vars.apeCoin, - vars.totalClaimedApe, - msgSender, - 0 - ); + uint128 totalApeCoinAmount = (vars.bakcMatchedCap * arrayLength) + .toUint128(); + { + IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[ + nApeOwner + ]; + sApeBalanceCache.stakedBalance -= totalApeCoinAmount; + sApeBalanceCache.freeBalance += totalApeCoinAmount; + sApeBalance[nApeOwner] = sApeBalanceCache; } - } - - // function _reduceSApeBalance() - - function _claimApeCoinPool( - IApeCoinPool.ApeCoinPoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - bool isSinglePool, - uint32[] calldata apeTokenIds - ) internal { - ( - address owner, - uint256 pendingReward, - uint128 accumulatedRewardsPerNft - ) = calculatePendingReward(poolState, vars, isBAYC, apeTokenIds); - - if (pendingReward > 0) { - uint256 arrayLength = apeTokenIds.length; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - - poolState - .tokenStatus[apeTokenId] - .rewardsDebt = accumulatedRewardsPerNft; - - //emit event - if (isSinglePool) { - emit ApeCoinPoolClaimed(isBAYC, apeTokenId); - } else { - emit ApeCoinPoolPairClaimed(isBAYC, apeTokenId); - } - } - IERC20(vars.cApe).safeTransfer(owner, pendingReward); + //distribute reward + uint32 totalPosition = poolState.totalPosition; + totalPosition -= arrayLength.toUint32(); + if (vars.totalClaimedApe > totalApeCoinAmount) { + _distributePoolReward( + poolState, + cApeShareBalance, + vars, + vars.totalClaimedApe - totalApeCoinAmount, + totalPosition + ); } + poolState.totalPosition = totalPosition; } function calculatePendingReward( - IApeCoinPool.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeCoinPoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata tokenIds @@ -544,4 +661,80 @@ library ApeCoinPoolLogic { return (claimFor, pendingReward, accumulatedRewardsPerNft); } + + function _prepareApeCoin( + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint128 totalApeCoinNeeded, + address user + ) internal { + IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; + if (sApeBalanceCache.freeBalance < totalApeCoinNeeded) { + IERC20(vars.apeCoin).safeTransferFrom( + user, + address(this), + totalApeCoinNeeded - sApeBalanceCache.freeBalance + ); + sApeBalanceCache.freeBalance = 0; + } else { + sApeBalanceCache.freeBalance -= totalApeCoinNeeded; + } + sApeBalanceCache.stakedBalance += totalApeCoinNeeded; + sApeBalance[user] = sApeBalanceCache; + } + + function _claimApeCoinPool( + IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + bool isSinglePool, + uint32[] calldata apeTokenIds + ) internal { + ( + address owner, + uint256 pendingReward, + uint128 accumulatedRewardsPerNft + ) = calculatePendingReward(poolState, vars, isBAYC, apeTokenIds); + + if (pendingReward > 0) { + uint256 arrayLength = apeTokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = apeTokenIds[index]; + + poolState + .tokenStatus[apeTokenId] + .rewardsDebt = accumulatedRewardsPerNft; + + //emit event + if (isSinglePool) { + emit ApeCoinPoolClaimed(isBAYC, apeTokenId); + } else { + emit ApeCoinPairPoolClaimed(isBAYC, apeTokenId); + } + } + + IERC20(vars.cApe).safeTransfer(owner, pendingReward); + } + } + + function _distributePoolReward( + IParaApeStaking.ApeCoinPoolState storage poolState, + mapping(address => uint256) storage cApeShareBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 rewardAmount, + uint32 totalPosition + ) internal { + IAutoCompoundApe(vars.cApe).deposit(address(this), rewardAmount); + + uint256 cApeShare = ICApe(vars.cApe).getShareByPooledApe(rewardAmount); + uint256 compoundFee = cApeShare.percentMul(vars.compoundFee); + cApeShare -= compoundFee; + poolState.accumulatedRewardsPerNft += + cApeShare.toUint128() / + totalPosition; + + if (compoundFee > 0) { + cApeShareBalance[address(this)] += compoundFee; + } + } } diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 543b171c3..7f06c1901 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -105,13 +105,13 @@ library ApeStakingPairPoolLogic { } //update pair status - poolState.pairStatus[apeTokenId] = IApeStakingVault.PairingStatus({ + poolState.pairStatus[apeTokenId] = IParaApeStaking.PairingStatus({ tokenId: bakcTokenId, isPaired: true }); //update token status - poolState.tokenStatus[apeTokenId] = IApeStakingVault.TokenStatus({ + poolState.tokenStatus[apeTokenId] = IParaApeStaking.TokenStatus({ rewardsDebt: accumulatedRewardsPerNft, isInPool: true }); @@ -161,8 +161,8 @@ library ApeStakingPairPoolLogic { // check pair status { - IApeStakingVault.PairingStatus - memory localPairStatus = poolState.pairStatus[apeTokenId]; + IParaApeStaking.PairingStatus memory localPairStatus = poolState + .pairStatus[apeTokenId]; require( localPairStatus.tokenId == bakcTokenId && localPairStatus.isPaired, @@ -403,7 +403,7 @@ library ApeStakingPairPoolLogic { uint32 bakcTokenId = bakcTokenIds[index]; // check pair status - IApeStakingVault.PairingStatus memory localPairStatus = poolState + IParaApeStaking.PairingStatus memory localPairStatus = poolState .pairStatus[apeTokenId]; require( localPairStatus.tokenId == bakcTokenId && @@ -498,8 +498,8 @@ library ApeStakingPairPoolLogic { // check pair status { - IApeStakingVault.PairingStatus - memory localPairStatus = poolState.pairStatus[apeTokenId]; + IParaApeStaking.PairingStatus memory localPairStatus = poolState + .pairStatus[apeTokenId]; require( localPairStatus.tokenId == bakcTokenIds[index] && localPairStatus.isPaired, diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index e98e45fdf..acad49d47 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -102,7 +102,7 @@ library ApeStakingSinglePoolLogic { IERC721(nft).safeTransferFrom(nToken, address(this), tokenId); //update token status - tokenStatus[tokenId] = IApeStakingVault.TokenStatus({ + tokenStatus[tokenId] = IParaApeStaking.TokenStatus({ rewardsDebt: accumulatedRewardsPerNft, isInPool: true }); diff --git a/contracts/interfaces/IApeCoinPool.sol b/contracts/interfaces/IApeCoinPool.sol index 8b5c4f639..4b5c5412a 100644 --- a/contracts/interfaces/IApeCoinPool.sol +++ b/contracts/interfaces/IApeCoinPool.sol @@ -1,20 +1,4 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.10; -interface IApeCoinPool { - struct TokenStatus { - //record tokenId reward debt position - uint128 rewardsDebt; - // identify if tokenId is in pool - bool isInPool; - } - - struct ApeCoinPoolState { - // total NFT position count - uint32 totalPosition; - // accumulated cApe reward for per NFT position - uint128 accumulatedRewardsPerNft; - //tokenId => reward debt position - mapping(uint256 => TokenStatus) tokenStatus; - } -} +interface IApeCoinPool {} diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index b1df999fd..be15d7d0c 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -2,55 +2,6 @@ pragma solidity 0.8.10; interface IApeStakingVault { - struct PairingStatus { - uint248 tokenId; - bool isPaired; - } - struct TokenStatus { - //record tokenId reward debt position - uint128 rewardsDebt; - // identify if tokenId is in pool - bool isInPool; - } - struct PoolState { - //pool cape debt token share, max value for uint104 is 2e31, ape coin total supply is 1e27. - uint104 cApeDebtShare; - // accumulated cApe reward for per NFT position - uint104 accumulatedRewardsPerNft; - // total NFT position count, max value for uint24 is 16777216 - uint24 totalPosition; - // total staking position - uint24 stakingPosition; - //tokenId => reward debt position - mapping(uint256 => TokenStatus) tokenStatus; - //for pair pool, apeTokenId => PairingStatus - mapping(uint256 => PairingStatus) pairStatus; - } - - struct BAKCPoolState { - // accumulated cApe reward for per NFT position - uint104 accumulatedRewardsPerNft; - // total NFT position count - uint24 totalPosition; - //bayc pair cape debt token share - uint104 baycCApeDebtShare; - //bayc pair staking position - uint24 baycStakingPosition; - //mayc pair cape debt token share - uint104 maycCApeDebtShare; - //mayc pair staking position - uint24 maycStakingPosition; - //tokenId => reward debt position - mapping(uint256 => TokenStatus) tokenStatus; - } - - struct VaultStorage { - mapping(uint256 => PoolState) poolStates; - BAKCPoolState bakcPoolState; - uint128 baycPairStakingRewardRatio; - uint128 maycPairStakingRewardRatio; - } - /** * @dev Emitted during setSinglePoolApeRewardRatio() * @param oldRatio The value of the old baycPairStakingRewardRatio diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index e1eceaec3..ba4c3d30c 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -6,7 +6,7 @@ import "./IApeStakingVault.sol"; import "./IApeStakingP2P.sol"; import "./IApeCoinPool.sol"; -interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { +interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { struct ApeStakingVaultCacheVars { address pool; address bayc; @@ -46,6 +46,70 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P { bool isInApeCoinPairPool; } + struct SApeBalance { + uint128 freeBalance; + uint128 stakedBalance; + } + + struct PairingStatus { + uint248 tokenId; + bool isPaired; + } + struct TokenStatus { + //record tokenId reward debt position + uint128 rewardsDebt; + // identify if tokenId is in pool + bool isInPool; + } + + struct PoolState { + //pool cape debt token share, max value for uint104 is 2e31, ape coin total supply is 1e27. + uint104 cApeDebtShare; + // accumulated cApe reward for per NFT position + uint104 accumulatedRewardsPerNft; + // total NFT position count, max value for uint24 is 16777216 + uint24 totalPosition; + // total staking position + uint24 stakingPosition; + //tokenId => reward debt position + mapping(uint256 => TokenStatus) tokenStatus; + //for pair pool, apeTokenId => PairingStatus + mapping(uint256 => PairingStatus) pairStatus; + } + + struct BAKCPoolState { + // accumulated cApe reward for per NFT position + uint104 accumulatedRewardsPerNft; + // total NFT position count + uint24 totalPosition; + //bayc pair cape debt token share + uint104 baycCApeDebtShare; + //bayc pair staking position + uint24 baycStakingPosition; + //mayc pair cape debt token share + uint104 maycCApeDebtShare; + //mayc pair staking position + uint24 maycStakingPosition; + //tokenId => reward debt position + mapping(uint256 => TokenStatus) tokenStatus; + } + + struct ApeCoinPoolState { + // total NFT position count + uint32 totalPosition; + // accumulated cApe reward for per NFT position + uint128 accumulatedRewardsPerNft; + //tokenId => reward debt position + mapping(uint256 => TokenStatus) tokenStatus; + } + + struct VaultStorage { + mapping(uint256 => PoolState) poolStates; + BAKCPoolState bakcPoolState; + uint128 baycPairStakingRewardRatio; + uint128 maycPairStakingRewardRatio; + } + /** * @dev Emitted during setApeStakingBot() * @param oldBot The address of the old compound bot diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 3d7fe4925..ac62f0898 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -24,62 +24,6 @@ interface IPoolApeStaking { function borrowPoolCApe(uint256 amount) external returns (uint256); - /** - * @notice Deposit ape coin to BAYC/MAYC pool or BAKC pool - * @param stakingInfo Detail info of the staking - * @param _nfts Array of BAYC/MAYC NFT's with staked amounts - * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts - * @param _openSApeCollateralFlag if true and when user current sApe collateral flag is false, we will open it. We don't close the flag here, user should call setUserUseERC20AsCollateral to turn off the flag. - * @dev Need check User health factor > 1. - */ - function borrowApeAndStake( - StakingInfo calldata stakingInfo, - ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs, - bool _openSApeCollateralFlag - ) external; - - /** - * @notice Withdraw staked ApeCoin from the BAYC/MAYC pool - * @param nftAsset Contract address of BAYC/MAYC - * @param _nfts Array of BAYC/MAYC NFT's with staked amounts - * @dev Need check User health factor > 1. - */ - function withdrawApeCoin( - address nftAsset, - ApeCoinStaking.SingleNft[] calldata _nfts - ) external; - - /** - * @notice Claim rewards for array of tokenIds from the BAYC/MAYC pool - * @param nftAsset Contract address of BAYC/MAYC - * @param _nfts Array of NFTs owned and committed by the msg.sender - * @dev Need check User health factor > 1. - */ - function claimApeCoin(address nftAsset, uint256[] calldata _nfts) external; - - /** - * @notice Withdraw staked ApeCoin from the BAKC pool - * @param nftAsset Contract address of BAYC/MAYC - * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts - * @dev Need check User health factor > 1. - */ - function withdrawBAKC( - address nftAsset, - ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs - ) external; - - /** - * @notice Claim rewards for array of tokenIds from the BAYC/MAYC pool - * @param nftAsset Contract address of BAYC/MAYC - * @param _nftPairs Array of Paired BAYC/MAYC NFT's - * @dev Need check User health factor > 1. - */ - function claimBAKC( - address nftAsset, - ApeCoinStaking.PairNft[] calldata _nftPairs - ) external; - /** * @notice Unstake user Ape coin staking position and repay user debt * @param nftAsset Contract address of BAYC/MAYC diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index b5d9a3bd3..bf254e771 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -132,347 +132,6 @@ contract PoolApeStaking is return latestBorrowIndex; } - /// @inheritdoc IPoolApeStaking - function withdrawApeCoin( - address nftAsset, - ApeCoinStaking.SingleNft[] calldata _nfts - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - DataTypes.ReserveData storage nftReserve = ps._reserves[nftAsset]; - address xTokenAddress = nftReserve.xTokenAddress; - INToken nToken = INToken(xTokenAddress); - uint256 totalWithdrawAmount = 0; - uint256 arrayLength = _nfts.length; - for (uint256 index = 0; index < arrayLength; index++) { - require( - nToken.ownerOf(_nfts[index].tokenId) == msg.sender, - Errors.NOT_THE_OWNER - ); - totalWithdrawAmount += _nfts[index].amount; - } - - DataTypes.TimeLockParams memory timeLockParams = GenericLogic - .calculateTimeLockParams( - ps._reserves[address(APE_COIN)], - DataTypes.TimeLockFactorParams({ - assetType: DataTypes.AssetType.ERC20, - asset: address(APE_COIN), - amount: totalWithdrawAmount - }) - ); - INTokenApeStaking(xTokenAddress).withdrawApeCoin( - _nfts, - msg.sender, - timeLockParams - ); - - DataTypes.UserConfigurationMap memory userConfig = ps._usersConfig[ - msg.sender - ]; - DataTypes.ReserveData storage reserve = ps._reserves[ - DataTypes.SApeAddress - ]; - if (userConfig.isUsingAsCollateral(reserve.id)) { - _checkUserHf(ps, userConfig, msg.sender, true); - } - } - - /// @inheritdoc IPoolApeStaking - function claimApeCoin(address nftAsset, uint256[] calldata _nfts) - external - nonReentrant - { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - DataTypes.ReserveData storage nftReserve = ps._reserves[nftAsset]; - address xTokenAddress = nftReserve.xTokenAddress; - INToken nToken = INToken(xTokenAddress); - for (uint256 index = 0; index < _nfts.length; index++) { - require( - nToken.ownerOf(_nfts[index]) == msg.sender, - Errors.NOT_THE_OWNER - ); - } - INTokenApeStaking(xTokenAddress).claimApeCoin(_nfts, msg.sender); - } - - /// @inheritdoc IPoolApeStaking - function withdrawBAKC( - address nftAsset, - ApeCoinStaking.PairNftWithdrawWithAmount[] calldata _nftPairs - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - ApeStakingLocalVars memory localVar = _generalCache(ps, nftAsset); - uint256 arrayLength = _nftPairs.length; - localVar.transferredTokenOwners = new address[](arrayLength); - uint256[] memory transferredTokenIds = new uint256[](arrayLength); - uint256 actualTransferAmount = 0; - uint256 totalWithdrawAmount = 0; - for (uint256 index = 0; index < arrayLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[index].mainTokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - - if ( - !_nftPairs[index].isUncommit || - localVar.bakcContract.ownerOf(_nftPairs[index].bakcTokenId) == - localVar.bakcNToken - ) { - localVar.transferredTokenOwners[ - actualTransferAmount - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[index].bakcTokenId, - msg.sender - ); - transferredTokenIds[actualTransferAmount] = _nftPairs[index] - .bakcTokenId; - actualTransferAmount++; - } - - totalWithdrawAmount += _nftPairs[index].amount; - } - - DataTypes.TimeLockParams memory timeLockParams = GenericLogic - .calculateTimeLockParams( - ps._reserves[address(APE_COIN)], - DataTypes.TimeLockFactorParams({ - assetType: DataTypes.AssetType.ERC20, - asset: address(APE_COIN), - amount: totalWithdrawAmount - }) - ); - INTokenApeStaking(localVar.xTokenAddress).withdrawBAKC( - _nftPairs, - msg.sender, - timeLockParams - ); - - ////transfer BAKC back for user - for (uint256 index = 0; index < actualTransferAmount; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - transferredTokenIds[index] - ); - } - - DataTypes.UserConfigurationMap memory userConfig = ps._usersConfig[ - msg.sender - ]; - DataTypes.ReserveData storage reserve = ps._reserves[ - DataTypes.SApeAddress - ]; - if (userConfig.isUsingAsCollateral(reserve.id)) { - _checkUserHf(ps, userConfig, msg.sender, true); - } - } - - /// @inheritdoc IPoolApeStaking - function claimBAKC( - address nftAsset, - ApeCoinStaking.PairNft[] calldata _nftPairs - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - ApeStakingLocalVars memory localVar = _generalCache(ps, nftAsset); - uint256 arrayLength = _nftPairs.length; - localVar.transferredTokenOwners = new address[](arrayLength); - - for (uint256 index = 0; index < arrayLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[index].mainTokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - - localVar.transferredTokenOwners[ - index - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[index].bakcTokenId, - msg.sender - ); - } - - INTokenApeStaking(localVar.xTokenAddress).claimBAKC( - _nftPairs, - msg.sender - ); - - //transfer BAKC back for user - for (uint256 index = 0; index < arrayLength; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - _nftPairs[index].bakcTokenId - ); - } - } - - /// @inheritdoc IPoolApeStaking - function borrowApeAndStake( - StakingInfo calldata stakingInfo, - ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs, - bool _openSApeCollateralFlag - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - require( - stakingInfo.borrowAsset == address(APE_COIN) || - stakingInfo.borrowAsset == address(APE_COMPOUND), - Errors.INVALID_ASSET_TYPE - ); - - ApeStakingLocalVars memory localVar = _generalCache( - ps, - stakingInfo.nftAsset - ); - localVar.transferredTokenOwners = new address[](_nftPairs.length); - localVar.balanceBefore = APE_COIN.balanceOf(localVar.xTokenAddress); - - DataTypes.ReserveData storage borrowAssetReserve = ps._reserves[ - stakingInfo.borrowAsset - ]; - // 1, handle borrow part - if (stakingInfo.borrowAmount > 0) { - // no time lock needed here - DataTypes.TimeLockParams memory timeLockParams; - if (stakingInfo.borrowAsset == address(APE_COIN)) { - IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( - localVar.xTokenAddress, - stakingInfo.borrowAmount, - timeLockParams - ); - } else { - IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( - address(this), - stakingInfo.borrowAmount, - timeLockParams - ); - APE_COMPOUND.withdraw(stakingInfo.borrowAmount); - APE_COIN.safeTransfer( - localVar.xTokenAddress, - stakingInfo.borrowAmount - ); - } - } - - // 2, send cash part to xTokenAddress - if (stakingInfo.cashAmount > 0) { - APE_COIN.safeTransferFrom( - msg.sender, - localVar.xTokenAddress, - stakingInfo.cashAmount - ); - } - - // 3, deposit bayc or mayc pool - { - uint256 nftsLength = _nfts.length; - for (uint256 index = 0; index < nftsLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nfts[index].tokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - } - - if (nftsLength > 0) { - INTokenApeStaking(localVar.xTokenAddress).depositApeCoin(_nfts); - } - } - - // 4, deposit bakc pool - { - uint256 nftPairsLength = _nftPairs.length; - for (uint256 index = 0; index < nftPairsLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[index].mainTokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - - localVar.transferredTokenOwners[ - index - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[index].bakcTokenId, - msg.sender - ); - } - - if (nftPairsLength > 0) { - INTokenApeStaking(localVar.xTokenAddress).depositBAKC( - _nftPairs - ); - } - //transfer BAKC back for user - for (uint256 index = 0; index < nftPairsLength; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - _nftPairs[index].bakcTokenId - ); - } - } - - //5 check if need to collateralize sAPE - if (_openSApeCollateralFlag) { - DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ - msg.sender - ]; - Helpers.setAssetUsedAsCollateral( - userConfig, - ps._reserves, - DataTypes.SApeAddress, - msg.sender - ); - } - - // 6 mint debt token - if (stakingInfo.borrowAmount > 0) { - BorrowLogic.executeBorrow( - ps._reserves, - ps._reservesList, - ps._usersConfig[msg.sender], - DataTypes.ExecuteBorrowParams({ - asset: stakingInfo.borrowAsset, - user: msg.sender, - onBehalfOf: msg.sender, - amount: stakingInfo.borrowAmount, - referralCode: 0, - releaseUnderlying: false, - reservesCount: ps._reservesCount, - oracle: ADDRESSES_PROVIDER.getPriceOracle(), - priceOracleSentinel: ADDRESSES_PROVIDER - .getPriceOracleSentinel() - }) - ); - } - - //7 checkout ape balance - require( - APE_COIN.balanceOf(localVar.xTokenAddress) == - localVar.balanceBefore, - Errors.TOTAL_STAKING_AMOUNT_WRONG - ); - } - /// @inheritdoc IPoolApeStaking function unstakeApePositionAndRepay(address nftAsset, uint256 tokenId) external From 7e8079e14c4e3e05b6ac5134793029688a049554 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 20 Jul 2023 22:15:42 +0800 Subject: [PATCH 61/99] chore: ape coin pool logic implementation --- contracts/apestaking/ParaApeStaking.sol | 121 +++- .../apestaking/logic/ApeCoinPoolLogic.sol | 376 ++++++++---- .../logic/ApeStakingPairPoolLogic.sol | 59 +- .../logic/ApeStakingSinglePoolLogic.sol | 15 +- contracts/interfaces/IApeCoinPool.sol | 7 +- contracts/interfaces/INTokenApeStaking.sol | 45 -- contracts/interfaces/IParaApeStaking.sol | 45 +- contracts/interfaces/IPoolApeStaking.sol | 69 --- .../libraries/logic/FlashClaimLogic.sol | 1 - .../protocol/libraries/logic/SupplyLogic.sol | 1 - .../libraries/logic/ValidationLogic.sol | 1 - contracts/protocol/pool/PoolApeStaking.sol | 562 +----------------- .../tokenization/NTokenApeStaking.sol | 188 +----- .../protocol/tokenization/NTokenBAKC.sol | 103 +--- .../protocol/tokenization/NTokenBAYC.sol | 114 +--- .../tokenization/NTokenChromieSquiggle.sol | 3 - .../protocol/tokenization/NTokenMAYC.sol | 114 +--- .../protocol/tokenization/PTokenSApe.sol | 29 +- .../libraries/ApeStakingLogic.sol | 387 ------------ helpers/contracts-deployments.ts | 57 +- helpers/init-helpers.ts | 32 +- helpers/types.ts | 2 +- scripts/upgrade/ntoken.ts | 14 - scripts/upgrade/ptoken.ts | 12 +- 24 files changed, 510 insertions(+), 1847 deletions(-) delete mode 100644 contracts/interfaces/INTokenApeStaking.sol delete mode 100644 contracts/protocol/tokenization/libraries/ApeStakingLogic.sol diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 4a367faa5..cf78d326d 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -213,30 +213,66 @@ contract ParaApeStaking is } /* - *Ape Coin Staking Pool Logic + *sApe Logic */ - mapping(uint256 => ApeCoinPoolState) internal apeCoinPoolStates; - function isApeInApeCoinPool(bool isBAYC, uint32 tokenId) external view {} + function stakedSApeBalance(address user) external view returns (uint256) { + return sApeBalance[user].stakedBalance; + } - function withdrawSApe(uint128 amount) external whenNotPaused nonReentrant { - ApeCoinPoolLogic.withdrawSApe( + function freeSApeBalance(address user) public view returns (uint256) { + return + ICApe(cApe).getPooledApeByShares( + sApeBalance[user].freeShareBalance + ); + } + + function totalSApeBalance(address user) external view returns (uint256) { + IParaApeStaking.SApeBalance memory cache = sApeBalance[user]; + return + ICApe(cApe).getPooledApeByShares(cache.freeShareBalance) + + cache.stakedBalance; + } + + function transferSApeBalance( + address from, + address to, + uint256 amount + ) external { + require(msg.sender == psApe, "invalid"); + uint256 shareAmount = ICApe(cApe).getShareByPooledApe(amount); + sApeBalance[from].freeShareBalance -= shareAmount.toUint128(); + sApeBalance[to].freeShareBalance += shareAmount.toUint128(); + } + + function withdrawFreeSApe(address receiver, uint128 amount) + external + whenNotPaused + nonReentrant + { + ApeCoinPoolLogic.withdrawFreeSApe( sApeBalance, pool, - apeCoin, + cApe, sApeReserveId, msg.sender, + receiver, amount ); } - function depositApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) + /* + *Ape Coin Staking Pool Logic + */ + mapping(uint256 => ApeCoinPoolState) internal apeCoinPoolStates; + + function depositApeCoinPool(ApeCoinActionInfo calldata depositInfo) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = isBAYC + uint256 poolId = depositInfo.isBAYC ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; ApeCoinPoolLogic.depositApeCoinPool( @@ -244,8 +280,7 @@ contract ParaApeStaking is apeMatchedCount, sApeBalance, vars, - isBAYC, - tokenIds + depositInfo ); } @@ -285,33 +320,34 @@ contract ParaApeStaking is ); } - function withdrawApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) + function withdrawApeCoinPool(ApeCoinActionInfo calldata withdrawInfo) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = isBAYC + vars.compoundFee = compoundFee; + vars.sApeReserveId = sApeReserveId; + uint256 poolId = withdrawInfo.isBAYC ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; ApeCoinPoolLogic.withdrawApeCoinPool( apeCoinPoolStates[poolId], - cApeShareBalance, apeMatchedCount, sApeBalance, + cApeShareBalance, vars, - isBAYC, - tokenIds + withdrawInfo ); } - function depositApeCoinPairPool( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant { + function depositApeCoinPairPool(ApeCoinPairActionInfo calldata depositInfo) + external + whenNotPaused + nonReentrant + { ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = isBAYC + uint256 poolId = depositInfo.isBAYC ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; ApeCoinPoolLogic.depositApeCoinPairPool( @@ -319,9 +355,7 @@ contract ParaApeStaking is apeMatchedCount, sApeBalance, vars, - isBAYC, - apeTokenIds, - bakcTokenIds + depositInfo ); } @@ -335,10 +369,9 @@ contract ParaApeStaking is uint256 poolId = isBAYC ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; - ApeCoinPoolLogic.depositApeCoinPairPool( + ApeCoinPoolLogic.compoundApeCoinPairPool( apeCoinPoolStates[poolId], - apeMatchedCount, - sApeBalance, + cApeShareBalance, vars, isBAYC, apeTokenIds, @@ -347,23 +380,45 @@ contract ParaApeStaking is } function withdrawApeCoinPairPool( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds + ApeCoinPairActionInfo calldata withdrawInfo ) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = isBAYC + vars.compoundFee = compoundFee; + vars.sApeReserveId = sApeReserveId; + uint256 poolId = withdrawInfo.isBAYC ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; ApeCoinPoolLogic.withdrawApeCoinPairPool( apeCoinPoolStates[poolId], + apeMatchedCount, + sApeBalance, cApeShareBalance, + vars, + withdrawInfo + ); + } + + function tryUnstakeApeCoinPoolPosition( + bool isBAYC, + uint256[] calldata tokenIds + ) external whenNotPaused nonReentrant { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 singlePoolId = isBAYC + ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + uint256 PairPoolId = isBAYC + ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID; + + ApeCoinPoolLogic.tryUnstakeApeCoinPoolPosition( + apeCoinPoolStates[singlePoolId], + apeCoinPoolStates[PairPoolId], apeMatchedCount, sApeBalance, + cApeShareBalance, vars, isBAYC, - apeTokenIds, - bakcTokenIds + tokenIds ); } diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 9ac274124..b288051a6 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -63,35 +63,26 @@ library ApeCoinPoolLogic { uint256 bakcTokenId ); - function withdrawSApe( + function withdrawFreeSApe( mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, address pool, - address apeCoin, + address cApe, uint16 sApeReserveId, address user, + address receiver, uint128 amount ) external { IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; - require(sApeBalanceCache.freeBalance >= amount, "balance not enough"); - sApeBalanceCache.freeBalance -= amount; - - DataTypes.UserConfigurationMap memory userConfig = IPool(pool) - .getUserConfiguration(user); - bool usageAsCollateralEnabled = userConfig.isUsingAsCollateral( - sApeReserveId + uint256 shareAmount = ICApe(cApe).getShareByPooledApe(amount); + require( + sApeBalanceCache.freeShareBalance >= shareAmount, + "balance not enough" ); - if (usageAsCollateralEnabled) { - (, , , , , uint256 healthFactor, ) = IPool(pool).getUserAccountData( - user - ); - //need to check user health factor - require( - healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD, - Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - } + sApeBalanceCache.freeShareBalance -= shareAmount.toUint128(); + + _validateDropSApeBalance(pool, sApeReserveId, user); - IERC20(apeCoin).safeTransfer(user, amount); + IERC20(cApe).safeTransfer(receiver, amount); } function depositApeCoinPool( @@ -99,13 +90,12 @@ library ApeCoinPoolLogic { mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata tokenIds + IParaApeStaking.ApeCoinActionInfo calldata depositInfo ) external { - uint256 arrayLength = tokenIds.length; + uint256 arrayLength = depositInfo.tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - if (isBAYC) { + if (depositInfo.isBAYC) { vars.apeToken = vars.bayc; vars.nApe = vars.nBayc; vars.positionCap = vars.baycMatchedCap; @@ -119,7 +109,7 @@ library ApeCoinPoolLogic { ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); for (uint256 index = 0; index < arrayLength; index++) { - uint32 tokenId = tokenIds[index]; + uint32 tokenId = depositInfo.tokenIds[index]; require( msgSender == IERC721(vars.nApe).ownerOf(tokenId), @@ -149,7 +139,7 @@ library ApeCoinPoolLogic { }); //emit event - emit ApeCoinPoolDeposited(isBAYC, tokenId); + emit ApeCoinPoolDeposited(depositInfo.isBAYC, tokenId); } //transfer ape coin @@ -158,11 +148,13 @@ library ApeCoinPoolLogic { sApeBalance, vars, totalApeCoinNeeded.toUint128(), + depositInfo.cashToken, + depositInfo.cashAmount, msgSender ); //stake in ApeCoinStaking - if (isBAYC) { + if (depositInfo.isBAYC) { vars.apeCoinStaking.depositBAYC(_nfts); } else { vars.apeCoinStaking.depositMAYC(_nfts); @@ -227,19 +219,24 @@ library ApeCoinPoolLogic { function withdrawApeCoinPool( IParaApeStaking.ApeCoinPoolState storage poolState, - mapping(address => uint256) storage cApeShareBalance, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata tokenIds - ) external { - uint256 arrayLength = tokenIds.length; + IParaApeStaking.ApeCoinActionInfo memory withdrawInfo + ) public { + uint256 arrayLength = withdrawInfo.tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - _claimApeCoinPool(poolState, vars, isBAYC, true, tokenIds); + address nApeOwner = _claimApeCoinPool( + poolState, + vars, + withdrawInfo.isBAYC, + true, + withdrawInfo.tokenIds + ); - if (isBAYC) { + if (withdrawInfo.isBAYC) { vars.apeToken = vars.bayc; vars.nApe = vars.nBayc; vars.positionCap = vars.baycMatchedCap; @@ -249,15 +246,11 @@ library ApeCoinPoolLogic { vars.positionCap = vars.maycMatchedCap; } address msgSender = msg.sender; + require(msgSender == nApeOwner, Errors.NOT_THE_OWNER); ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); for (uint256 index = 0; index < arrayLength; index++) { - uint32 tokenId = tokenIds[index]; - - require( - msgSender == IERC721(vars.nApe).ownerOf(tokenId), - Errors.NOT_THE_OWNER - ); + uint32 tokenId = withdrawInfo.tokenIds[index]; require( poolState.tokenStatus[tokenId].isInPool, @@ -282,12 +275,12 @@ library ApeCoinPoolLogic { }); //emit event - emit ApeCoinPoolWithdrew(isBAYC, tokenId); + emit ApeCoinPoolWithdrew(withdrawInfo.isBAYC, tokenId); } //withdraw from ApeCoinStaking vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - if (isBAYC) { + if (withdrawInfo.isBAYC) { vars.apeCoinStaking.withdrawSelfBAYC(_nfts); } else { vars.apeCoinStaking.withdrawSelfMAYC(_nfts); @@ -297,12 +290,14 @@ library ApeCoinPoolLogic { uint128 totalApeCoinAmount = (vars.bakcMatchedCap * arrayLength) .toUint128(); - IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[ + _handleApeCoin( + sApeBalance, + vars, + totalApeCoinAmount, + withdrawInfo.cashToken, + withdrawInfo.cashAmount, msgSender - ]; - sApeBalanceCache.stakedBalance -= totalApeCoinAmount; - sApeBalanceCache.freeBalance += totalApeCoinAmount; - sApeBalance[msgSender] = sApeBalanceCache; + ); //distribute reward uint32 totalPosition = poolState.totalPosition; @@ -324,17 +319,15 @@ library ApeCoinPoolLogic { mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds + IParaApeStaking.ApeCoinPairActionInfo calldata depositInfo ) external { - uint256 arrayLength = apeTokenIds.length; + uint256 arrayLength = depositInfo.apeTokenIds.length; require( - arrayLength == bakcTokenIds.length && arrayLength > 0, + arrayLength == depositInfo.bakcTokenIds.length && arrayLength > 0, Errors.INVALID_PARAMETER ); - if (isBAYC) { + if (depositInfo.isBAYC) { vars.apeToken = vars.bayc; vars.nApe = vars.nBayc; } else { @@ -347,8 +340,8 @@ library ApeCoinPoolLogic { arrayLength ); for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; + uint32 apeTokenId = depositInfo.apeTokenIds[index]; + uint32 bakcTokenId = depositInfo.bakcTokenIds[index]; //check ntoken owner { @@ -376,6 +369,7 @@ library ApeCoinPoolLogic { poolState.tokenStatus[apeTokenId].rewardsDebt = vars .accumulatedRewardsPerNft; poolState.tokenStatus[apeTokenId].isInPool = true; + poolState.tokenStatus[apeTokenId].bakcTokenId = bakcTokenId; IERC721(vars.bakc).safeTransferFrom( vars.nBakc, @@ -391,7 +385,11 @@ library ApeCoinPoolLogic { }); //emit event - emit ApeCoinPairPoolDeposited(isBAYC, apeTokenId, bakcTokenId); + emit ApeCoinPairPoolDeposited( + depositInfo.isBAYC, + apeTokenId, + bakcTokenId + ); } //transfer ape coin @@ -400,6 +398,8 @@ library ApeCoinPoolLogic { sApeBalance, vars, totalApeCoinNeeded.toUint128(), + depositInfo.cashToken, + depositInfo.cashAmount, msg.sender ); @@ -408,7 +408,7 @@ library ApeCoinPoolLogic { memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( 0 ); - if (isBAYC) { + if (depositInfo.isBAYC) { vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); } else { vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); @@ -484,50 +484,45 @@ library ApeCoinPoolLogic { function withdrawApeCoinPairPool( IParaApeStaking.ApeCoinPoolState storage poolState, - mapping(address => uint256) storage cApeShareBalance, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external { - uint256 arrayLength = apeTokenIds.length; + IParaApeStaking.ApeCoinPairActionInfo memory withdrawInfo + ) public { + uint256 arrayLength = withdrawInfo.apeTokenIds.length; require( - arrayLength == bakcTokenIds.length && arrayLength > 0, + arrayLength == withdrawInfo.bakcTokenIds.length && arrayLength > 0, Errors.INVALID_PARAMETER ); - _claimApeCoinPool(poolState, vars, isBAYC, false, apeTokenIds); + address nApeOwner = _claimApeCoinPool( + poolState, + vars, + withdrawInfo.isBAYC, + false, + withdrawInfo.apeTokenIds + ); - if (isBAYC) { + if (withdrawInfo.isBAYC) { vars.apeToken = vars.bayc; vars.nApe = vars.nBayc; } else { vars.apeToken = vars.mayc; vars.nApe = vars.nMayc; } - address nApeOwner; + ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength ); for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; + uint32 apeTokenId = withdrawInfo.apeTokenIds[index]; + uint32 bakcTokenId = withdrawInfo.bakcTokenIds[index]; //check ntoken owner { - address tmpApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); - if (nApeOwner == address(0)) { - nApeOwner = tmpApeOwner; - } else { - require( - nApeOwner == tmpApeOwner, - Errors.NOT_THE_SAME_OWNER - ); - } - if (nApeOwner != msg.sender) { + if (nApeOwner != msg.sender && vars.nApe != msg.sender) { address nBakcOwner = IERC721(vars.nBakc).ownerOf( bakcTokenId ); @@ -571,7 +566,11 @@ library ApeCoinPoolLogic { }); //emit event - emit ApeCoinPairPoolWithdrew(isBAYC, apeTokenId, bakcTokenId); + emit ApeCoinPairPoolWithdrew( + withdrawInfo.isBAYC, + apeTokenId, + bakcTokenId + ); } //withdraw from ApeCoinStaking @@ -581,7 +580,7 @@ library ApeCoinPoolLogic { memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( 0 ); - if (isBAYC) { + if (withdrawInfo.isBAYC) { vars.apeCoinStaking.withdrawBAKC(_nftPairs, _otherPairs); } else { vars.apeCoinStaking.withdrawBAKC(_otherPairs, _nftPairs); @@ -592,14 +591,14 @@ library ApeCoinPoolLogic { uint128 totalApeCoinAmount = (vars.bakcMatchedCap * arrayLength) .toUint128(); - { - IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[ - nApeOwner - ]; - sApeBalanceCache.stakedBalance -= totalApeCoinAmount; - sApeBalanceCache.freeBalance += totalApeCoinAmount; - sApeBalance[nApeOwner] = sApeBalanceCache; - } + _handleApeCoin( + sApeBalance, + vars, + totalApeCoinAmount, + withdrawInfo.cashToken, + withdrawInfo.cashAmount, + msg.sender + ); //distribute reward uint32 totalPosition = poolState.totalPosition; @@ -616,11 +615,104 @@ library ApeCoinPoolLogic { poolState.totalPosition = totalPosition; } + function tryUnstakeApeCoinPoolPosition( + IParaApeStaking.ApeCoinPoolState storage singlePoolState, + IParaApeStaking.ApeCoinPoolState storage pairPoolState, + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + mapping(address => uint256) storage cApeShareBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool isBAYC, + uint256[] calldata tokenIds + ) external { + require(tokenIds.length > 0, Errors.INVALID_PARAMETER); + + // check single + { + uint32[] memory singlePoolTokenIds = new uint32[](tokenIds.length); + uint256 singleCount = 0; + for (uint256 index = 0; index < tokenIds.length; index++) { + uint256 tokenId = tokenIds[index]; + + IParaApeStaking.TokenStatus + memory singlePoolTokenStatus = singlePoolState.tokenStatus[ + tokenId + ]; + if (singlePoolTokenStatus.isInPool) { + singlePoolTokenIds[singleCount] = tokenId.toUint32(); + singleCount++; + } + } + + if (singleCount > 0) { + assembly { + mstore(singlePoolTokenIds, singleCount) + } + + withdrawApeCoinPool( + singlePoolState, + apeMatchedCount, + sApeBalance, + cApeShareBalance, + vars, + IParaApeStaking.ApeCoinActionInfo({ + cashToken: vars.cApe, + cashAmount: 0, + isBAYC: isBAYC, + tokenIds: singlePoolTokenIds + }) + ); + } + } + + // check pair + { + uint32[] memory parePoolTokenIds = new uint32[](tokenIds.length); + uint32[] memory bakcTokenIds = new uint32[](tokenIds.length); + uint256 pairCount = 0; + for (uint256 index = 0; index < tokenIds.length; index++) { + uint256 tokenId = tokenIds[index]; + + IParaApeStaking.TokenStatus + memory pairPoolTokenStatus = pairPoolState.tokenStatus[ + tokenId + ]; + if (pairPoolTokenStatus.isInPool) { + parePoolTokenIds[pairCount] = tokenId.toUint32(); + bakcTokenIds[pairCount] = pairPoolTokenStatus.bakcTokenId; + pairCount++; + } + } + + if (pairCount > 0) { + assembly { + mstore(parePoolTokenIds, pairCount) + mstore(bakcTokenIds, pairCount) + } + + withdrawApeCoinPairPool( + pairPoolState, + apeMatchedCount, + sApeBalance, + cApeShareBalance, + vars, + IParaApeStaking.ApeCoinPairActionInfo({ + cashToken: vars.cApe, + cashAmount: 0, + isBAYC: isBAYC, + apeTokenIds: parePoolTokenIds, + bakcTokenIds: bakcTokenIds + }) + ); + } + } + } + function calculatePendingReward( IParaApeStaking.ApeCoinPoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, - uint32[] calldata tokenIds + uint32[] memory tokenIds ) public view @@ -666,30 +758,91 @@ library ApeCoinPoolLogic { mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint128 totalApeCoinNeeded, + address cashToken, + uint256 cashAmount, address user ) internal { + require(cashToken == vars.cApe || cashToken == vars.apeCoin, ""); + require(cashAmount <= totalApeCoinNeeded, ""); + + if (cashAmount != 0) { + IERC20(cashToken).safeTransferFrom(user, address(this), cashAmount); + } + + uint256 cApeWithdrawAmount = (cashToken == vars.apeCoin) + ? 0 + : cashAmount; IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; - if (sApeBalanceCache.freeBalance < totalApeCoinNeeded) { - IERC20(vars.apeCoin).safeTransferFrom( - user, - address(this), - totalApeCoinNeeded - sApeBalanceCache.freeBalance + if (cashAmount < totalApeCoinNeeded) { + uint256 freeSApeBalanceNeeded = totalApeCoinNeeded - cashAmount; + uint256 freeShareBalanceNeeded = ICApe(vars.cApe) + .getShareByPooledApe(freeSApeBalanceNeeded); + require( + sApeBalanceCache.freeShareBalance >= freeShareBalanceNeeded, + "free balance not enough" ); - sApeBalanceCache.freeBalance = 0; - } else { - sApeBalanceCache.freeBalance -= totalApeCoinNeeded; + sApeBalanceCache.freeShareBalance -= freeShareBalanceNeeded + .toUint128(); + cApeWithdrawAmount += freeSApeBalanceNeeded; + } + + if (cApeWithdrawAmount > 0) { + IAutoCompoundApe(vars.cApe).withdraw(cApeWithdrawAmount); } + sApeBalanceCache.stakedBalance += totalApeCoinNeeded; sApeBalance[user] = sApeBalanceCache; } + function _handleApeCoin( + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint128 totalApeCoinWithdrew, + address cashToken, + uint256 cashAmount, + address user + ) internal { + require(cashToken == vars.cApe || cashToken == vars.apeCoin, ""); + require(cashAmount <= totalApeCoinWithdrew, ""); + + if (cashAmount != 0) { + IERC20(cashToken).safeTransfer(user, cashAmount); + } + + uint256 cApeDepositAmount = (cashToken == vars.apeCoin) + ? 0 + : cashAmount; + IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; + if (cashAmount < totalApeCoinWithdrew) { + uint256 freeSApeBalanceAdded = totalApeCoinWithdrew - cashAmount; + uint256 freeShareBalanceAdded = ICApe(vars.cApe) + .getShareByPooledApe(freeSApeBalanceAdded); + sApeBalanceCache.freeShareBalance += freeShareBalanceAdded + .toUint128(); + cApeDepositAmount += freeSApeBalanceAdded; + } + sApeBalanceCache.stakedBalance -= totalApeCoinWithdrew; + sApeBalance[user] = sApeBalanceCache; + + if (cashAmount > 0) { + _validateDropSApeBalance(vars.pool, vars.sApeReserveId, user); + } + + if (cApeDepositAmount > 0) { + IAutoCompoundApe(vars.cApe).deposit( + address(this), + cApeDepositAmount + ); + } + } + function _claimApeCoinPool( IParaApeStaking.ApeCoinPoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, bool isSinglePool, - uint32[] calldata apeTokenIds - ) internal { + uint32[] memory apeTokenIds + ) internal returns (address) { ( address owner, uint256 pendingReward, @@ -715,6 +868,7 @@ library ApeCoinPoolLogic { IERC20(vars.cApe).safeTransfer(owner, pendingReward); } + return owner; } function _distributePoolReward( @@ -737,4 +891,26 @@ library ApeCoinPoolLogic { cApeShareBalance[address(this)] += compoundFee; } } + + function _validateDropSApeBalance( + address pool, + uint16 sApeReserveId, + address user + ) internal view { + DataTypes.UserConfigurationMap memory userConfig = IPool(pool) + .getUserConfiguration(user); + bool usageAsCollateralEnabled = userConfig.isUsingAsCollateral( + sApeReserveId + ); + if (usageAsCollateralEnabled) { + (, , , , , uint256 healthFactor, ) = IPool(pool).getUserAccountData( + user + ); + //need to check user health factor + require( + healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD, + Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD + ); + } + } } diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 7f06c1901..2dce47584 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -104,16 +104,12 @@ library ApeStakingPairPoolLogic { require(!isPaired, Errors.PAIR_POSITION_EXISTED); } - //update pair status - poolState.pairStatus[apeTokenId] = IParaApeStaking.PairingStatus({ - tokenId: bakcTokenId, - isPaired: true - }); - //update token status poolState.tokenStatus[apeTokenId] = IParaApeStaking.TokenStatus({ rewardsDebt: accumulatedRewardsPerNft, - isInPool: true + isInPool: true, + bakcTokenId: bakcTokenId, + isPaired: true }); //transfer ape and BAKC @@ -161,11 +157,11 @@ library ApeStakingPairPoolLogic { // check pair status { - IParaApeStaking.PairingStatus memory localPairStatus = poolState - .pairStatus[apeTokenId]; + IParaApeStaking.TokenStatus memory localTokenStatus = poolState + .tokenStatus[apeTokenId]; require( - localPairStatus.tokenId == bakcTokenId && - localPairStatus.isPaired, + localTokenStatus.bakcTokenId == bakcTokenId && + localTokenStatus.isPaired, Errors.NOT_PAIRED_APE_AND_BAKC ); } @@ -224,7 +220,13 @@ library ApeStakingPairPoolLogic { Errors.INVALID_PARAMETER ); - _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); + vars.nApeOwner = _claimPairNFT( + poolState, + vars, + isBAYC, + apeTokenIds, + bakcTokenIds + ); if (isBAYC) { vars.apeStakingPoolId = BAYC_POOL_ID; @@ -252,17 +254,16 @@ library ApeStakingPairPoolLogic { //check ntoken owner { - address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); - address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); address msgSender = msg.sender; - require( - msgSender == nApeOwner || msgSender == nBakcOwner, - Errors.NOT_THE_OWNER - ); + if (vars.nApeOwner != msgSender) { + address nBakcOwner = IERC721(vars.nBakc).ownerOf( + bakcTokenId + ); + require(msgSender == nBakcOwner, Errors.NOT_THE_OWNER); + } } // update pair status - delete poolState.pairStatus[apeTokenId]; delete poolState.tokenStatus[apeTokenId]; // we only need to check pair staking position @@ -403,11 +404,11 @@ library ApeStakingPairPoolLogic { uint32 bakcTokenId = bakcTokenIds[index]; // check pair status - IParaApeStaking.PairingStatus memory localPairStatus = poolState - .pairStatus[apeTokenId]; + IParaApeStaking.TokenStatus memory localTokenStatus = poolState + .tokenStatus[apeTokenId]; require( - localPairStatus.tokenId == bakcTokenId && - localPairStatus.isPaired, + localTokenStatus.bakcTokenId == bakcTokenId && + localTokenStatus.isPaired, Errors.NOT_PAIRED_APE_AND_BAKC ); @@ -498,11 +499,11 @@ library ApeStakingPairPoolLogic { // check pair status { - IParaApeStaking.PairingStatus memory localPairStatus = poolState - .pairStatus[apeTokenId]; + IParaApeStaking.TokenStatus memory localTokenStatus = poolState + .tokenStatus[apeTokenId]; require( - localPairStatus.tokenId == bakcTokenIds[index] && - localPairStatus.isPaired, + localTokenStatus.bakcTokenId == bakcTokenIds[index] && + localTokenStatus.isPaired, Errors.NOT_PAIRED_APE_AND_BAKC ); } @@ -522,7 +523,7 @@ library ApeStakingPairPoolLogic { bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) internal { + ) internal returns (address) { ( address owner, uint256 pendingReward, @@ -551,5 +552,7 @@ library ApeStakingPairPoolLogic { IERC20(vars.cApe).safeTransfer(owner, pendingReward); } + + return owner; } } diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index acad49d47..d764f5ab3 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -104,7 +104,9 @@ library ApeStakingSinglePoolLogic { //update token status tokenStatus[tokenId] = IParaApeStaking.TokenStatus({ rewardsDebt: accumulatedRewardsPerNft, - isInPool: true + isInPool: true, + bakcTokenId: 0, + isPaired: false }); //emit event @@ -425,7 +427,7 @@ library ApeStakingSinglePoolLogic { storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); //claim pending reward - _claimNFT(tokenStatus, vars, nft, tokenIds); + address nApeOwner = _claimNFT(tokenStatus, vars, nft, tokenIds); address nToken; if (nft == vars.bayc) { @@ -440,13 +442,10 @@ library ApeStakingSinglePoolLogic { } //transfer nft back to nToken - address msgSender = msg.sender; + require(msg.sender == nApeOwner, Errors.NOT_THE_OWNER); for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; - address nTokenOwner = IERC721(nToken).ownerOf(tokenId); - require(msgSender == nTokenOwner, Errors.NOT_THE_OWNER); - delete tokenStatus[tokenId]; IERC721(nft).safeTransferFrom(address(this), nToken, tokenId); @@ -801,7 +800,7 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds - ) internal { + ) internal returns (address) { address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) ? vars.nMayc : vars.nBakc; @@ -826,6 +825,8 @@ library ApeStakingSinglePoolLogic { IERC20(vars.cApe).safeTransfer(owner, pendingReward); } + + return owner; } function _calculateRepayAndCompoundBAKC( diff --git a/contracts/interfaces/IApeCoinPool.sol b/contracts/interfaces/IApeCoinPool.sol index 4b5c5412a..7a56fcabb 100644 --- a/contracts/interfaces/IApeCoinPool.sol +++ b/contracts/interfaces/IApeCoinPool.sol @@ -1,4 +1,9 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.8.10; -interface IApeCoinPool {} +interface IApeCoinPool { + function tryUnstakeApeCoinPoolPosition( + bool isBAYC, + uint256[] calldata tokenIds + ) external; +} diff --git a/contracts/interfaces/INTokenApeStaking.sol b/contracts/interfaces/INTokenApeStaking.sol deleted file mode 100644 index ce03375fa..000000000 --- a/contracts/interfaces/INTokenApeStaking.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity 0.8.10; - -import "../dependencies/yoga-labs/ApeCoinStaking.sol"; -import "./INToken.sol"; - -interface INTokenApeStaking { - function getBAKC() external view returns (IERC721); - - function getApeStaking() external view returns (ApeCoinStaking); - - function depositApeCoin(ApeCoinStaking.SingleNft[] calldata _nfts) external; - - function claimApeCoin(uint256[] calldata _nfts, address _recipient) - external; - - function withdrawApeCoin( - ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient, - DataTypes.TimeLockParams memory _timeLockParams - ) external; - - function depositBAKC( - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external; - - function claimBAKC( - ApeCoinStaking.PairNft[] calldata _nftPairs, - address _recipient - ) external; - - function withdrawBAKC( - ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs, - address _apeRecipient, - DataTypes.TimeLockParams memory _timeLockParams - ) external; - - function unstakePositionAndRepay(uint256 tokenId, address unstaker) - external; - - function getUserApeStakingAmount(address user) - external - view - returns (uint256); -} diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index ba4c3d30c..1873cf8b6 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -38,6 +38,8 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { uint256 cApeExchangeRate; uint256 latestBorrowIndex; bool isPaired; + uint16 sApeReserveId; + address nApeOwner; } struct ApeStatus { @@ -47,19 +49,21 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { } struct SApeBalance { - uint128 freeBalance; + //cApe share + uint128 freeShareBalance; + //staked ape coin uint128 stakedBalance; } - struct PairingStatus { - uint248 tokenId; - bool isPaired; - } struct TokenStatus { //record tokenId reward debt position uint128 rewardsDebt; // identify if tokenId is in pool bool isInPool; + // pair bakc token + uint32 bakcTokenId; + // is paird with bakc + bool isPaired; } struct PoolState { @@ -73,8 +77,6 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { uint24 stakingPosition; //tokenId => reward debt position mapping(uint256 => TokenStatus) tokenStatus; - //for pair pool, apeTokenId => PairingStatus - mapping(uint256 => PairingStatus) pairStatus; } struct BAKCPoolState { @@ -110,6 +112,21 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { uint128 maycPairStakingRewardRatio; } + struct ApeCoinActionInfo { + address cashToken; + uint256 cashAmount; + bool isBAYC; + uint32[] tokenIds; + } + + struct ApeCoinPairActionInfo { + address cashToken; + uint256 cashAmount; + bool isBAYC; + uint32[] apeTokenIds; + uint32[] bakcTokenIds; + } + /** * @dev Emitted during setApeStakingBot() * @param oldBot The address of the old compound bot @@ -118,4 +135,18 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { event ApeStakingBotUpdated(address oldBot, address newBot); event CompoundFeeUpdated(uint64 oldValue, uint64 newValue); + + function stakedSApeBalance(address user) external view returns (uint256); + + function freeSApeBalance(address user) external view returns (uint256); + + function totalSApeBalance(address user) external view returns (uint256); + + function transferSApeBalance( + address from, + address to, + uint256 amount + ) external; + + function withdrawFreeSApe(address receiver, uint128 amount) external; } diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index ac62f0898..9cddfac6c 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -9,76 +9,7 @@ import "../dependencies/yoga-labs/ApeCoinStaking.sol"; * @notice Defines the basic interface for an ParaSpace Ape Staking Pool. **/ interface IPoolApeStaking { - struct StakingInfo { - // Contract address of BAYC/MAYC - address nftAsset; - // address of borrowing asset, can be Ape or cApe - address borrowAsset; - // Borrow amount of Ape from lending pool - uint256 borrowAmount; - // Cash amount of Ape from user wallet - uint256 cashAmount; - } - function paraApeStaking() external view returns (address); function borrowPoolCApe(uint256 amount) external returns (uint256); - - /** - * @notice Unstake user Ape coin staking position and repay user debt - * @param nftAsset Contract address of BAYC/MAYC - * @param tokenId Token id of the ape staking position on - * @dev Need check User health factor > 1. - */ - function unstakeApePositionAndRepay(address nftAsset, uint256 tokenId) - external; - - /** - * @notice repay asset and supply asset for user - * @param underlyingAsset Contract address of BAYC/MAYC - * @param onBehalfOf The beneficiary of the repay and supply - * @dev Convenient callback function for unstakeApePositionAndRepay. Only NToken of BAYC/MAYC can call this. - */ - function repayAndSupply( - address underlyingAsset, - address onBehalfOf, - uint256 totalAmount - ) external; - - /** - * @notice Claim user Ape coin reward and deposit to ape compound to get cApe, then deposit cApe to Lending pool for user - * @param nftAsset Contract address of BAYC/MAYC - * @param users array of user address - * @param tokenIds array of user tokenId array - * @param minUsdcApePrice minimum usdc ape relative price - * @param minWethApePrice minimum weth ape relative price - */ - function claimApeAndCompound( - address nftAsset, - address[] calldata users, - uint256[][] calldata tokenIds, - uint256 minUsdcApePrice, - uint256 minWethApePrice - ) external; - - /** - * @notice Claim user BAKC paired Ape coin reward and deposit to ape compound to get cApe, then deposit cApe to Lending pool for user - * @param nftAsset Contract address of BAYC/MAYC - * @param users array of user address - * @param _nftPairs Array of Paired BAYC/MAYC NFT's - * @param minUsdcApePrice minimum usdc ape relative price - * @param minWethApePrice minimum weth ape relative price - */ - function claimPairedApeAndCompound( - address nftAsset, - address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs, - uint256 minUsdcApePrice, - uint256 minWethApePrice - ) external; - - /** - * @notice get current incentive fee rate for claiming ape position reward to compound - */ - function getApeCompoundFeeRate() external returns (uint256); } diff --git a/contracts/protocol/libraries/logic/FlashClaimLogic.sol b/contracts/protocol/libraries/logic/FlashClaimLogic.sol index 0e0cbcc27..9804bbdcd 100644 --- a/contracts/protocol/libraries/logic/FlashClaimLogic.sol +++ b/contracts/protocol/libraries/logic/FlashClaimLogic.sol @@ -7,7 +7,6 @@ import {INToken} from "../../../interfaces/INToken.sol"; import {DataTypes} from "../types/DataTypes.sol"; import {Errors} from "../helpers/Errors.sol"; import {ValidationLogic} from "./ValidationLogic.sol"; -import "../../../interfaces/INTokenApeStaking.sol"; import {XTokenType, IXTokenType} from "../../../interfaces/IXTokenType.sol"; import {GenericLogic} from "./GenericLogic.sol"; import {ReserveConfiguration} from "../configuration/ReserveConfiguration.sol"; diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index 64a44220b..6db699997 100644 --- a/contracts/protocol/libraries/logic/SupplyLogic.sol +++ b/contracts/protocol/libraries/logic/SupplyLogic.sol @@ -7,7 +7,6 @@ import {GPv2SafeERC20} from "../../../dependencies/gnosis/contracts/GPv2SafeERC2 import {IPToken} from "../../../interfaces/IPToken.sol"; import {INonfungiblePositionManager} from "../../../dependencies/uniswap/INonfungiblePositionManager.sol"; import {INToken} from "../../../interfaces/INToken.sol"; -import {INTokenApeStaking} from "../../../interfaces/INTokenApeStaking.sol"; import {ICollateralizableERC721} from "../../../interfaces/ICollateralizableERC721.sol"; import {IAuctionableERC721} from "../../../interfaces/IAuctionableERC721.sol"; import {ITimeLockStrategy} from "../../../interfaces/ITimeLockStrategy.sol"; diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 462cb448e..0072e8751 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -27,7 +27,6 @@ import {IToken} from "../../../interfaces/IToken.sol"; import {XTokenType, IXTokenType} from "../../../interfaces/IXTokenType.sol"; import {Helpers} from "../helpers/Helpers.sol"; import {INonfungiblePositionManager} from "../../../dependencies/uniswap/INonfungiblePositionManager.sol"; -import "../../../interfaces/INTokenApeStaking.sol"; /** * @title ReserveLogic library diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index bf254e771..fe59ab484 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -5,28 +5,11 @@ import "../libraries/paraspace-upgradeability/ParaReentrancyGuard.sol"; import "../libraries/paraspace-upgradeability/ParaVersionedInitializable.sol"; import {PoolStorage} from "./PoolStorage.sol"; import "../../interfaces/IPoolApeStaking.sol"; -import "../../interfaces/IPToken.sol"; -import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; -import "../../interfaces/IXTokenType.sol"; -import "../../interfaces/INTokenApeStaking.sol"; -import {ValidationLogic} from "../libraries/logic/ValidationLogic.sol"; import {IPoolAddressesProvider} from "../../interfaces/IPoolAddressesProvider.sol"; import {IPool} from "../../interfaces/IPool.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; -import {ReserveLogic} from "../libraries/logic/ReserveLogic.sol"; -import {GenericLogic} from "../libraries/logic/GenericLogic.sol"; -import {UserConfiguration} from "../libraries/configuration/UserConfiguration.sol"; -import {ApeStakingLogic} from "../tokenization/libraries/ApeStakingLogic.sol"; +import {DataTypes} from "../libraries/types/DataTypes.sol"; import "../libraries/logic/BorrowLogic.sol"; -import "../libraries/logic/SupplyLogic.sol"; -import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; -import {IAutoCompoundApe} from "../../interfaces/IAutoCompoundApe.sol"; -import {PercentageMath} from "../libraries/math/PercentageMath.sol"; -import {WadRayMath} from "../libraries/math/WadRayMath.sol"; -import {Math} from "../../dependencies/openzeppelin/contracts/Math.sol"; -import {ISwapRouter} from "../../dependencies/univ3/interfaces/ISwapRouter.sol"; -import {IPriceOracleGetter} from "../../interfaces/IPriceOracleGetter.sol"; -import {Helpers} from "../libraries/helpers/Helpers.sol"; contract PoolApeStaking is ParaVersionedInitializable, @@ -34,75 +17,23 @@ contract PoolApeStaking is PoolStorage, IPoolApeStaking { - using ReserveLogic for DataTypes.ReserveData; - using UserConfiguration for DataTypes.UserConfigurationMap; - using SafeERC20 for IERC20; - using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - using SafeCast for uint256; - using PercentageMath for uint256; - using WadRayMath for uint256; - - IPoolAddressesProvider internal immutable ADDRESSES_PROVIDER; - IAutoCompoundApe internal immutable APE_COMPOUND; - IERC20 internal immutable APE_COIN; uint256 internal constant POOL_REVISION = 149; - IERC20 internal immutable USDC; - ISwapRouter internal immutable SWAP_ROUTER; - uint24 internal immutable APE_WETH_FEE; - uint24 internal immutable WETH_USDC_FEE; - address internal immutable WETH; + IPoolAddressesProvider internal immutable ADDRESSES_PROVIDER; + address internal immutable APE_COMPOUND; address internal immutable PARA_APE_STAKING; - event ReserveUsedAsCollateralEnabled( - address indexed reserve, - address indexed user - ); - - struct ApeStakingLocalVars { - address xTokenAddress; - IERC721 bakcContract; - address bakcNToken; - uint256 balanceBefore; - uint256 balanceAfter; - uint256[] amounts; - uint256[] swapAmounts; - address[] transferredTokenOwners; - DataTypes.ApeCompoundStrategy[] options; - uint256 totalAmount; - uint256 compoundFee; - address compoundBot; - uint256 totalUsdcSwapAmount; - uint256 totalWethSwapAmount; - uint256 usdcApePrice; - uint256 wethApePrice; - address pUSDCAddress; - address pWETHAddress; - } - /** * @dev Constructor. * @param provider The address of the PoolAddressesProvider contract */ constructor( IPoolAddressesProvider provider, - IAutoCompoundApe apeCompound, - IERC20 apeCoin, - IERC20 usdc, - ISwapRouter uniswapV3SwapRouter, - address weth, - uint24 apeWethFee, - uint24 wethUsdcFee, + address apeCompound, address apeStakingVault ) { ADDRESSES_PROVIDER = provider; APE_COMPOUND = apeCompound; - APE_COIN = apeCoin; - USDC = IERC20(usdc); - SWAP_ROUTER = ISwapRouter(uniswapV3SwapRouter); - WETH = weth; - APE_WETH_FEE = apeWethFee; - WETH_USDC_FEE = wethUsdcFee; PARA_APE_STAKING = apeStakingVault; } @@ -131,489 +62,4 @@ contract PoolApeStaking is return latestBorrowIndex; } - - /// @inheritdoc IPoolApeStaking - function unstakeApePositionAndRepay(address nftAsset, uint256 tokenId) - external - nonReentrant - { - DataTypes.PoolStorage storage ps = poolStorage(); - DataTypes.ReserveData storage nftReserve = ps._reserves[nftAsset]; - address xTokenAddress = nftReserve.xTokenAddress; - address incentiveReceiver = address(0); - address positionOwner = INToken(xTokenAddress).ownerOf(tokenId); - if (msg.sender != positionOwner) { - DataTypes.UserConfigurationMap memory userConfig = ps._usersConfig[ - positionOwner - ]; - _checkUserHf(ps, userConfig, positionOwner, false); - incentiveReceiver = msg.sender; - } - - INTokenApeStaking(xTokenAddress).unstakePositionAndRepay( - tokenId, - incentiveReceiver - ); - } - - /// @inheritdoc IPoolApeStaking - function repayAndSupply( - address underlyingAsset, - address onBehalfOf, - uint256 totalAmount - ) external { - DataTypes.PoolStorage storage ps = poolStorage(); - require( - msg.sender == ps._reserves[underlyingAsset].xTokenAddress, - Errors.CALLER_NOT_XTOKEN - ); - - // 1, deposit APE as cAPE - APE_COIN.safeTransferFrom(msg.sender, address(this), totalAmount); - APE_COMPOUND.deposit(address(this), totalAmount); - - // 2, repay cAPE and supply cAPE for user - _repayAndSupplyForUser( - ps, - address(APE_COMPOUND), - address(this), - onBehalfOf, - totalAmount - ); - } - - /// @inheritdoc IPoolApeStaking - function claimApeAndCompound( - address nftAsset, - address[] calldata users, - uint256[][] calldata tokenIds, - uint256 minUsdcApePrice, - uint256 minWethApePrice - ) external nonReentrant { - require( - users.length == tokenIds.length, - Errors.INCONSISTENT_PARAMS_LENGTH - ); - DataTypes.PoolStorage storage ps = poolStorage(); - ApeStakingLocalVars memory localVar = _compoundCache( - ps, - nftAsset, - users.length - ); - require(msg.sender == localVar.compoundBot, Errors.CALLER_NOT_OPERATOR); - - for (uint256 i = 0; i < users.length; i++) { - for (uint256 j = 0; j < tokenIds[i].length; j++) { - require( - users[i] == - INToken(localVar.xTokenAddress).ownerOf(tokenIds[i][j]), - Errors.NOT_THE_OWNER - ); - } - - INTokenApeStaking(localVar.xTokenAddress).claimApeCoin( - tokenIds[i], - address(this) - ); - - _addUserToCompoundCache(ps, localVar, i, users[i]); - } - - _compoundForUsers( - ps, - localVar, - users, - minUsdcApePrice, - minWethApePrice - ); - } - - /// @inheritdoc IPoolApeStaking - function claimPairedApeAndCompound( - address nftAsset, - address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs, - uint256 minUsdcApePrice, - uint256 minWethApePrice - ) external nonReentrant { - require( - users.length == _nftPairs.length, - Errors.INCONSISTENT_PARAMS_LENGTH - ); - DataTypes.PoolStorage storage ps = poolStorage(); - ApeStakingLocalVars memory localVar = _compoundCache( - ps, - nftAsset, - users.length - ); - require(msg.sender == localVar.compoundBot, Errors.CALLER_NOT_OPERATOR); - - for (uint256 i = 0; i < _nftPairs.length; i++) { - localVar.transferredTokenOwners = new address[]( - _nftPairs[i].length - ); - for (uint256 j = 0; j < _nftPairs[i].length; j++) { - require( - users[i] == - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[i][j].mainTokenId - ), - Errors.NOT_THE_OWNER - ); - - localVar.transferredTokenOwners[ - j - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[i][j].bakcTokenId, - users[i] - ); - } - - INTokenApeStaking(localVar.xTokenAddress).claimBAKC( - _nftPairs[i], - address(this) - ); - - for (uint256 index = 0; index < _nftPairs[i].length; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - _nftPairs[i][index].bakcTokenId - ); - } - - _addUserToCompoundCache(ps, localVar, i, users[i]); - } - - _compoundForUsers( - ps, - localVar, - users, - minUsdcApePrice, - minWethApePrice - ); - } - - function _generalCache(DataTypes.PoolStorage storage ps, address nftAsset) - internal - view - returns (ApeStakingLocalVars memory localVar) - { - localVar.xTokenAddress = ps._reserves[nftAsset].xTokenAddress; - localVar.bakcContract = INTokenApeStaking(localVar.xTokenAddress) - .getBAKC(); - localVar.bakcNToken = ps - ._reserves[address(localVar.bakcContract)] - .xTokenAddress; - } - - function _compoundCache( - DataTypes.PoolStorage storage ps, - address nftAsset, - uint256 numUsers - ) internal view returns (ApeStakingLocalVars memory localVar) { - localVar = _generalCache(ps, nftAsset); - localVar.balanceBefore = APE_COIN.balanceOf(address(this)); - localVar.amounts = new uint256[](numUsers); - localVar.swapAmounts = new uint256[](numUsers); - localVar.options = new DataTypes.ApeCompoundStrategy[](numUsers); - localVar.compoundFee = ps._apeCompoundFee; - localVar.compoundBot = ps._apeCompoundBot; - } - - function _addUserToCompoundCache( - DataTypes.PoolStorage storage ps, - ApeStakingLocalVars memory localVar, - uint256 i, - address user - ) internal view { - localVar.balanceAfter = APE_COIN.balanceOf(address(this)); - localVar.options[i] = ps._apeCompoundStrategies[user]; - unchecked { - localVar.amounts[i] = (localVar.balanceAfter - - localVar.balanceBefore).percentMul( - PercentageMath.PERCENTAGE_FACTOR - localVar.compoundFee - ); - localVar.balanceBefore = localVar.balanceAfter; - localVar.totalAmount += localVar.amounts[i]; - } - - if (localVar.options[i].swapPercent > 0) { - localVar.swapAmounts[i] = localVar.amounts[i].percentMul( - localVar.options[i].swapPercent - ); - if ( - localVar.options[i].swapTokenOut == - DataTypes.ApeCompoundTokenOut.USDC - ) { - localVar.totalUsdcSwapAmount += localVar.swapAmounts[i]; - } else { - localVar.totalWethSwapAmount += localVar.swapAmounts[i]; - } - } - } - - /// @inheritdoc IPoolApeStaking - function getApeCompoundFeeRate() external view returns (uint256) { - DataTypes.PoolStorage storage ps = poolStorage(); - return uint256(ps._apeCompoundFee); - } - - function _checkUserHf( - DataTypes.PoolStorage storage ps, - DataTypes.UserConfigurationMap memory userConfig, - address user, - bool checkAbove - ) private view { - uint256 healthFactor; - if (!userConfig.isBorrowingAny()) { - healthFactor = type(uint256).max; - } else { - (, , , , , , , healthFactor, , ) = GenericLogic - .calculateUserAccountData( - ps._reserves, - ps._reservesList, - DataTypes.CalculateUserAccountDataParams({ - userConfig: userConfig, - reservesCount: ps._reservesCount, - user: user, - oracle: ADDRESSES_PROVIDER.getPriceOracle() - }) - ); - } - - if (checkAbove) { - require( - healthFactor > DataTypes.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, - Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - } else { - require( - healthFactor < DataTypes.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, - Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD - ); - } - } - - function _checkSApeIsNotPaused(DataTypes.PoolStorage storage ps) - internal - view - { - DataTypes.ReserveData storage reserve = ps._reserves[ - DataTypes.SApeAddress - ]; - - (bool isActive, , , bool isPaused, ) = reserve.configuration.getFlags(); - - require(isActive, Errors.RESERVE_INACTIVE); - require(!isPaused, Errors.RESERVE_PAUSED); - } - - function _compoundForUsers( - DataTypes.PoolStorage storage ps, - ApeStakingLocalVars memory localVar, - address[] calldata users, - uint256 minUsdcApePrice, - uint256 minWethApePrice - ) internal { - uint256 totalSwapAmount = localVar.totalUsdcSwapAmount + - localVar.totalWethSwapAmount; - if (localVar.totalAmount > totalSwapAmount) { - APE_COMPOUND.deposit( - address(this), - localVar.totalAmount - totalSwapAmount - ); - } - - uint256 compoundFee = localVar - .totalAmount - .percentDiv(PercentageMath.PERCENTAGE_FACTOR - localVar.compoundFee) - .percentMul(localVar.compoundFee); - if (compoundFee > 0) { - APE_COIN.safeTransfer(localVar.compoundBot, compoundFee); - } - - if (localVar.totalUsdcSwapAmount > 0) { - bytes memory usdcSwapPath = abi.encodePacked( - APE_COIN, - APE_WETH_FEE, - WETH, - WETH_USDC_FEE, - USDC - ); - localVar.pUSDCAddress = ps._reserves[address(USDC)].xTokenAddress; - localVar.usdcApePrice = _swapAndSupplyForUser( - ps, - address(USDC), - localVar.pUSDCAddress, - localVar.totalUsdcSwapAmount, - usdcSwapPath, - address(this), - minUsdcApePrice - ); - } - - if (localVar.totalWethSwapAmount > 0) { - bytes memory wethSwapPath = abi.encodePacked( - APE_COIN, - APE_WETH_FEE, - WETH - ); - localVar.pWETHAddress = ps._reserves[address(WETH)].xTokenAddress; - localVar.wethApePrice = _swapAndSupplyForUser( - ps, - address(WETH), - localVar.pWETHAddress, - localVar.totalWethSwapAmount, - wethSwapPath, - address(this), - minWethApePrice - ); - } - - for (uint256 i = 0; i < users.length; i++) { - if (localVar.swapAmounts[i] > 0) { - address swapTokenOut; - uint256 price; - if ( - localVar.options[i].swapTokenOut == - DataTypes.ApeCompoundTokenOut.USDC - ) { - swapTokenOut = localVar.pUSDCAddress; - price = localVar.usdcApePrice; - } else { - swapTokenOut = localVar.pWETHAddress; - price = localVar.wethApePrice; - } - IERC20(swapTokenOut).safeTransfer( - users[i], - (localVar.swapAmounts[i] * price) / 1e18 - ); - } - _repayAndSupplyForUser( - ps, - address(APE_COMPOUND), - address(this), - users[i], - localVar.amounts[i] - localVar.swapAmounts[i] - ); - } - } - - function _swapAndSupplyForUser( - DataTypes.PoolStorage storage ps, - address tokenOut, - address xTokenAddress, - uint256 amountIn, - bytes memory swapPath, - address user, - uint256 price - ) internal returns (uint256) { - if (amountIn == 0) { - return price; - } - uint256 amountOut = SWAP_ROUTER.exactInput( - ISwapRouter.ExactInputParams({ - path: swapPath, - recipient: address(this), - deadline: block.timestamp, - amountIn: amountIn, - amountOutMinimum: amountIn.wadMul(price) - }) - ); - uint256 beforeBalance = IERC20(xTokenAddress).balanceOf(address(this)); - _supplyForUser(ps, tokenOut, address(this), user, amountOut); - return - ((IERC20(xTokenAddress).balanceOf(address(this)) - beforeBalance) * - 1e18) / amountIn; - } - - function _repayAndSupplyForUser( - DataTypes.PoolStorage storage ps, - address asset, - address payer, - address onBehalfOf, - uint256 totalAmount - ) internal { - address variableDebtTokenAddress = ps - ._reserves[asset] - .variableDebtTokenAddress; - uint256 repayAmount = Math.min( - IERC20(variableDebtTokenAddress).balanceOf(onBehalfOf), - totalAmount - ); - _repayForUser(ps, asset, payer, onBehalfOf, repayAmount); - _supplyForUser(ps, asset, payer, onBehalfOf, totalAmount - repayAmount); - } - - function _supplyForUser( - DataTypes.PoolStorage storage ps, - address asset, - address payer, - address onBehalfOf, - uint256 amount - ) internal { - if (amount == 0) { - return; - } - DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ - onBehalfOf - ]; - SupplyLogic.executeSupply( - ps._reserves, - userConfig, - DataTypes.ExecuteSupplyParams({ - asset: asset, - amount: amount, - onBehalfOf: onBehalfOf, - payer: payer, - referralCode: 0 - }) - ); - } - - function _repayForUser( - DataTypes.PoolStorage storage ps, - address asset, - address payer, - address onBehalfOf, - uint256 amount - ) internal returns (uint256) { - if (amount == 0) { - return 0; - } - return - BorrowLogic.executeRepay( - ps._reserves, - ps._usersConfig[onBehalfOf], - DataTypes.ExecuteRepayParams({ - asset: asset, - amount: amount, - onBehalfOf: onBehalfOf, - payer: payer, - usePTokens: false - }) - ); - } - - function _validateBAKCOwnerAndTransfer( - ApeStakingLocalVars memory localVar, - uint256 tokenId, - address userAddress - ) internal returns (address bakcOwner) { - bakcOwner = localVar.bakcContract.ownerOf(tokenId); - require( - (userAddress == bakcOwner) || - (bakcOwner == localVar.bakcNToken && - userAddress == - INToken(localVar.bakcNToken).ownerOf(tokenId)), - Errors.NOT_THE_BAKC_OWNER - ); - localVar.bakcContract.safeTransferFrom( - bakcOwner, - localVar.xTokenAddress, - tokenId - ); - } } diff --git a/contracts/protocol/tokenization/NTokenApeStaking.sol b/contracts/protocol/tokenization/NTokenApeStaking.sol index 34a16e6f1..a71eaf424 100644 --- a/contracts/protocol/tokenization/NTokenApeStaking.sol +++ b/contracts/protocol/tokenization/NTokenApeStaking.sol @@ -2,45 +2,28 @@ pragma solidity 0.8.10; import {NToken} from "./NToken.sol"; -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {IPool} from "../../interfaces/IPool.sol"; -import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; import {IRewardController} from "../../interfaces/IRewardController.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; -import "../../interfaces/INTokenApeStaking.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; +import "../../interfaces/IParaApeStaking.sol"; /** * @title ApeCoinStaking NToken * * @notice Implementation of the NToken for the ParaSpace protocol */ -abstract contract NTokenApeStaking is NToken, INTokenApeStaking { - ApeCoinStaking immutable _apeCoinStaking; - - bytes32 constant APE_STAKING_DATA_STORAGE_POSITION = - bytes32( - uint256(keccak256("paraspace.proxy.ntoken.apestaking.storage")) - 1 - ); - - /** - * @dev Default percentage of borrower's ape position to be repaid as incentive in a unstaking transaction. - * @dev Percentage applied when the users ape position got unstaked by others. - * Expressed in bps, a value of 30 results in 0.3% - */ - uint256 internal constant DEFAULT_UNSTAKE_INCENTIVE_PERCENTAGE = 30; +abstract contract NTokenApeStaking is NToken { + IParaApeStaking immutable paraApeStaking; /** * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address apeCoinStaking, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { - _apeCoinStaking = ApeCoinStaking(apeCoinStaking); + constructor(IPool pool, address delegateRegistry) + NToken(pool, false, delegateRegistry) + { + paraApeStaking = IParaApeStaking(pool.paraApeStaking()); } function initialize( @@ -51,27 +34,10 @@ abstract contract NTokenApeStaking is NToken, INTokenApeStaking { string calldata nTokenSymbol, bytes calldata params ) public virtual override initializer { - IERC20 _apeCoin = _apeCoinStaking.apeCoin(); - //approve for apeCoinStaking - uint256 allowance = IERC20(_apeCoin).allowance( - address(this), - address(_apeCoinStaking) + IERC721(underlyingAsset).setApprovalForAll( + address(paraApeStaking), + true ); - if (allowance == 0) { - IERC20(_apeCoin).approve( - address(_apeCoinStaking), - type(uint256).max - ); - } - //approve for Pool contract - allowance = IERC20(_apeCoin).allowance(address(this), address(POOL)); - if (allowance == 0) { - IERC20(_apeCoin).approve(address(POOL), type(uint256).max); - } - getBAKC().setApprovalForAll(address(POOL), true); - - address paraApeStaking = POOL.paraApeStaking(); - IERC721(underlyingAsset).setApprovalForAll(paraApeStaking, true); super.initialize( initializingPool, @@ -81,22 +47,11 @@ abstract contract NTokenApeStaking is NToken, INTokenApeStaking { nTokenSymbol, params ); - - initializeStakingData(); - } - - /** - * @notice Returns the address of BAKC contract address. - **/ - function getBAKC() public view returns (IERC721) { - return _apeCoinStaking.nftContracts(ApeStakingLogic.BAKC_POOL_ID); } - /** - * @notice Returns the address of ApeCoinStaking contract address. - **/ - function getApeStaking() external view returns (ApeCoinStaking) { - return _apeCoinStaking; + function isBayc() internal pure virtual returns (bool) { + // should be overridden + return true; } /** @@ -108,19 +63,9 @@ abstract contract NTokenApeStaking is NToken, INTokenApeStaking { uint256 tokenId, bool validate ) internal override { - ApeStakingLogic.executeUnstakePositionAndRepay( - _ERC721Data.owners, - apeStakingDataStorage(), - ApeStakingLogic.UnstakeAndRepayParams({ - POOL: POOL, - _apeCoinStaking: _apeCoinStaking, - _underlyingAsset: _ERC721Data.underlyingAsset, - poolId: POOL_ID(), - tokenId: tokenId, - incentiveReceiver: address(0), - bakcNToken: getBAKCNTokenAddress() - }) - ); + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = tokenId; + paraApeStaking.tryUnstakeApeCoinPoolPosition(isBayc(), tokenIds); super._transfer(from, to, tokenId, validate); } @@ -133,107 +78,8 @@ abstract contract NTokenApeStaking is NToken, INTokenApeStaking { uint256[] calldata tokenIds, DataTypes.TimeLockParams calldata timeLockParams ) external virtual override onlyPool nonReentrant returns (uint64, uint64) { - for (uint256 index = 0; index < tokenIds.length; index++) { - ApeStakingLogic.executeUnstakePositionAndRepay( - _ERC721Data.owners, - apeStakingDataStorage(), - ApeStakingLogic.UnstakeAndRepayParams({ - POOL: POOL, - _apeCoinStaking: _apeCoinStaking, - _underlyingAsset: _ERC721Data.underlyingAsset, - poolId: POOL_ID(), - tokenId: tokenIds[index], - incentiveReceiver: address(0), - bakcNToken: getBAKCNTokenAddress() - }) - ); - } + paraApeStaking.tryUnstakeApeCoinPoolPosition(isBayc(), tokenIds); return _burn(from, receiverOfUnderlying, tokenIds, timeLockParams); } - - function POOL_ID() internal pure virtual returns (uint256) { - // should be overridden - return 0; - } - - function initializeStakingData() internal { - ApeStakingLogic.APEStakingParameter - storage dataStorage = apeStakingDataStorage(); - ApeStakingLogic.executeSetUnstakeApeIncentive( - dataStorage, - DEFAULT_UNSTAKE_INCENTIVE_PERCENTAGE - ); - } - - function setUnstakeApeIncentive(uint256 incentive) external onlyPoolAdmin { - ApeStakingLogic.executeSetUnstakeApeIncentive( - apeStakingDataStorage(), - incentive - ); - } - - function apeStakingDataStorage() - internal - pure - returns (ApeStakingLogic.APEStakingParameter storage rgs) - { - bytes32 position = APE_STAKING_DATA_STORAGE_POSITION; - assembly { - rgs.slot := position - } - } - - /** - * @notice Unstake Ape coin staking position and repay user debt - * @param tokenId Token id of the ape staking position on - * @param incentiveReceiver address to receive incentive - */ - function unstakePositionAndRepay(uint256 tokenId, address incentiveReceiver) - external - nonReentrant - { - address bakcNToken = getBAKCNTokenAddress(); - require( - msg.sender == address(POOL) || msg.sender == bakcNToken, - "Invalid Caller" - ); - ApeStakingLogic.executeUnstakePositionAndRepay( - _ERC721Data.owners, - apeStakingDataStorage(), - ApeStakingLogic.UnstakeAndRepayParams({ - POOL: POOL, - _apeCoinStaking: _apeCoinStaking, - _underlyingAsset: _ERC721Data.underlyingAsset, - poolId: POOL_ID(), - tokenId: tokenId, - incentiveReceiver: incentiveReceiver, - bakcNToken: bakcNToken - }) - ); - } - - /** - * @notice get user total ape staking position - * @param user user address - */ - function getUserApeStakingAmount(address user) - external - view - returns (uint256) - { - return - ApeStakingLogic.getUserTotalStakingAmount( - _ERC721Data.userState, - _ERC721Data.ownedTokens, - _ERC721Data.underlyingAsset, - user, - POOL_ID(), - _apeCoinStaking - ); - } - - function getBAKCNTokenAddress() internal view returns (address) { - return POOL.getReserveData(address(getBAKC())).xTokenAddress; - } } diff --git a/contracts/protocol/tokenization/NTokenBAKC.sol b/contracts/protocol/tokenization/NTokenBAKC.sol index 68e17a130..1169a2b67 100644 --- a/contracts/protocol/tokenization/NTokenBAKC.sol +++ b/contracts/protocol/tokenization/NTokenBAKC.sol @@ -7,9 +7,6 @@ import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; -import {INTokenApeStaking} from "../../interfaces/INTokenApeStaking.sol"; -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {INToken} from "../../interfaces/INToken.sol"; import {IRewardController} from "../../interfaces/IRewardController.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; @@ -20,25 +17,13 @@ import {DataTypes} from "../libraries/types/DataTypes.sol"; * @notice Implementation of the NTokenBAKC for the ParaSpace protocol */ contract NTokenBAKC is NToken { - ApeCoinStaking immutable _apeCoinStaking; - address private immutable nBAYC; - address private immutable nMAYC; - /** * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address apeCoinStaking, - address _nBAYC, - address _nMAYC, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { - _apeCoinStaking = ApeCoinStaking(apeCoinStaking); - nBAYC = _nBAYC; - nMAYC = _nMAYC; - } + constructor(IPool pool, address delegateRegistry) + NToken(pool, false, delegateRegistry) + {} function initialize( IPool initializingPool, @@ -57,92 +42,10 @@ contract NTokenBAKC is NToken { params ); - IERC20 ape = _apeCoinStaking.apeCoin(); - //approve for nBAYC - uint256 allowance = ape.allowance(address(this), nBAYC); - if (allowance == 0) { - ape.approve(nBAYC, type(uint256).max); - } - //approve for Pool nMAYC - allowance = ape.allowance(address(this), nMAYC); - if (allowance == 0) { - ape.approve(nMAYC, type(uint256).max); - } - IERC721(underlyingAsset).setApprovalForAll(address(POOL), true); - address paraApeStaking = POOL.paraApeStaking(); IERC721(underlyingAsset).setApprovalForAll(paraApeStaking, true); } - function _transfer( - address from, - address to, - uint256 tokenId, - bool validate - ) internal override { - _unStakePairedApePosition(tokenId); - super._transfer(from, to, tokenId, validate); - } - - /** - * @notice Overrides the burn from NToken to withdraw all staked and pending rewards before burning the NToken on liquidation/withdraw - */ - function burn( - address from, - address receiverOfUnderlying, - uint256[] calldata tokenIds, - DataTypes.TimeLockParams calldata timeLockParams - ) external virtual override onlyPool nonReentrant returns (uint64, uint64) { - if (from != receiverOfUnderlying) { - for (uint256 index = 0; index < tokenIds.length; index++) { - _unStakePairedApePosition(tokenIds[index]); - } - } - return _burn(from, receiverOfUnderlying, tokenIds, timeLockParams); - } - - function _unStakePairedApePosition(uint256 tokenId) internal { - //check if have ape pair position - (uint256 bakcStakedAmount, ) = _apeCoinStaking.nftPosition( - ApeStakingLogic.BAKC_POOL_ID, - tokenId - ); - if (bakcStakedAmount > 0) { - bool positionExisted = _tryUnstakeMainTokenPosition( - ApeStakingLogic.BAYC_POOL_ID, - nBAYC, - tokenId - ); - if (!positionExisted) { - _tryUnstakeMainTokenPosition( - ApeStakingLogic.MAYC_POOL_ID, - nMAYC, - tokenId - ); - } - } - } - - function _tryUnstakeMainTokenPosition( - uint256 poolId, - address nToken, - uint256 tokenId - ) internal returns (bool) { - (uint256 mainTokenId, bool positionExisted) = _apeCoinStaking - .bakcToMain(tokenId, poolId); - if (positionExisted) { - bool sameOwner = INToken(nToken).ownerOf(mainTokenId) == - ownerOf(tokenId); - if (sameOwner) { - INTokenApeStaking(nToken).unstakePositionAndRepay( - mainTokenId, - address(0) - ); - } - } - return positionExisted; - } - function getXTokenType() external pure override returns (XTokenType) { return XTokenType.NTokenBAKC; } diff --git a/contracts/protocol/tokenization/NTokenBAYC.sol b/contracts/protocol/tokenization/NTokenBAYC.sol index 1c67db808..7a812095f 100644 --- a/contracts/protocol/tokenization/NTokenBAYC.sol +++ b/contracts/protocol/tokenization/NTokenBAYC.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.10; -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {NTokenApeStaking} from "./NTokenApeStaking.sol"; import {IPool} from "../../interfaces/IPool.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; /** @@ -14,114 +12,12 @@ import {DataTypes} from "../libraries/types/DataTypes.sol"; * @notice Implementation of the NToken for the ParaSpace protocol */ contract NTokenBAYC is NTokenApeStaking { - constructor( - IPool pool, - address apeCoinStaking, - address delegateRegistry - ) NTokenApeStaking(pool, apeCoinStaking, delegateRegistry) {} + constructor(IPool pool, address delegateRegistry) + NTokenApeStaking(pool, delegateRegistry) + {} - /** - * @notice Deposit ApeCoin to the BAYC Pool - * @param _nfts Array of SingleNft structs - * @dev Commits 1 or more BAYC NFTs, each with an ApeCoin amount to the BAYC pool.\ - * Each BAYC committed must attach an ApeCoin amount >= 1 ApeCoin and <= the BAYC pool cap amount. - */ - function depositApeCoin(ApeCoinStaking.SingleNft[] calldata _nfts) - external - onlyPool - nonReentrant - { - _apeCoinStaking.depositBAYC(_nfts); - } - - /** - * @notice Claim rewards for array of BAYC NFTs and send to recipient - * @param _nfts Array of NFTs owned and committed by the msg.sender - * @param _recipient Address to send claim reward to - */ - function claimApeCoin(uint256[] calldata _nfts, address _recipient) - external - onlyPool - nonReentrant - { - _apeCoinStaking.claimBAYC(_nfts, _recipient); - } - - /** - * @notice Withdraw staked ApeCoin from the BAYC pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nfts Array of BAYC NFT's with staked amounts - * @param _recipient Address to send withdraw amount and claim to - */ - function withdrawApeCoin( - ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient, - DataTypes.TimeLockParams memory _timeLockParams - ) external onlyPool nonReentrant { - ApeStakingLogic.executeWithdrawBAYC( - POOL, - _ERC721Data.underlyingAsset, - _apeCoinStaking, - _nfts, - _recipient, - _timeLockParams - ); - } - - /** - * @notice Deposit ApeCoin to the Pair Pool, where Pair = (BAYC + BAKC) - * @param _nftPairs Array of PairNftWithAmount structs - * @dev Commits 1 or more Pairs, each with an ApeCoin amount to the Pair pool.\ - * Each BAKC committed must attach an ApeCoin amount >= 1 ApeCoin and <= the Pair pool cap amount.\ - * Example: BAYC + BAKC + 1 ApeCoin: [[0, 0, "1000000000000000000"]]\ - */ - function depositBAKC( - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external onlyPool nonReentrant { - _apeCoinStaking.depositBAKC( - _nftPairs, - new ApeCoinStaking.PairNftDepositWithAmount[](0) - ); - } - - /** - * @notice Claim rewards for array of Paired NFTs and send to recipient - * @param _nftPairs Array of Paired BAYC/MAYC NFTs owned and committed by the msg.sender - * @param _recipient Address to send claim reward to - */ - function claimBAKC( - ApeCoinStaking.PairNft[] calldata _nftPairs, - address _recipient - ) external onlyPool nonReentrant { - _apeCoinStaking.claimBAKC( - _nftPairs, - new ApeCoinStaking.PairNft[](0), - _recipient - ); - } - - /** - * @notice Withdraw staked ApeCoin from the Pair pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nftPairs Array of Paired BAYC NFT's with staked amounts - * @dev if pairs have split ownership and BAKC is attempting a withdraw, the withdraw must be for the total staked amount - */ - function withdrawBAKC( - ApeCoinStaking.PairNftWithdrawWithAmount[] calldata _nftPairs, - address _apeRecipient, - DataTypes.TimeLockParams memory _timeLockParams - ) external onlyPool nonReentrant { - ApeStakingLogic.withdrawBAKC( - POOL, - _ERC721Data.underlyingAsset, - _apeCoinStaking, - POOL_ID(), - _nftPairs, - _apeRecipient, - _timeLockParams - ); - } - - function POOL_ID() internal pure virtual override returns (uint256) { - return ApeStakingLogic.BAYC_POOL_ID; + function isBayc() internal pure virtual override returns (bool) { + return true; } function getXTokenType() external pure override returns (XTokenType) { diff --git a/contracts/protocol/tokenization/NTokenChromieSquiggle.sol b/contracts/protocol/tokenization/NTokenChromieSquiggle.sol index a0346af51..f9a63eb66 100644 --- a/contracts/protocol/tokenization/NTokenChromieSquiggle.sol +++ b/contracts/protocol/tokenization/NTokenChromieSquiggle.sol @@ -7,9 +7,6 @@ import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; -import {INTokenApeStaking} from "../../interfaces/INTokenApeStaking.sol"; -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {INToken} from "../../interfaces/INToken.sol"; import {IRewardController} from "../../interfaces/IRewardController.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; diff --git a/contracts/protocol/tokenization/NTokenMAYC.sol b/contracts/protocol/tokenization/NTokenMAYC.sol index e702430a0..383f51368 100644 --- a/contracts/protocol/tokenization/NTokenMAYC.sol +++ b/contracts/protocol/tokenization/NTokenMAYC.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.10; -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {NTokenApeStaking} from "./NTokenApeStaking.sol"; import {IPool} from "../../interfaces/IPool.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; /** @@ -14,114 +12,12 @@ import {DataTypes} from "../libraries/types/DataTypes.sol"; * @notice Implementation of the NToken for the ParaSpace protocol */ contract NTokenMAYC is NTokenApeStaking { - constructor( - IPool pool, - address apeCoinStaking, - address delegateRegistry - ) NTokenApeStaking(pool, apeCoinStaking, delegateRegistry) {} + constructor(IPool pool, address delegateRegistry) + NTokenApeStaking(pool, delegateRegistry) + {} - /** - * @notice Deposit ApeCoin to the MAYC Pool - * @param _nfts Array of SingleNft structs - * @dev Commits 1 or more MAYC NFTs, each with an ApeCoin amount to the MAYC pool.\ - * Each MAYC committed must attach an ApeCoin amount >= 1 ApeCoin and <= the MAYC pool cap amount. - */ - function depositApeCoin(ApeCoinStaking.SingleNft[] calldata _nfts) - external - onlyPool - nonReentrant - { - _apeCoinStaking.depositMAYC(_nfts); - } - - /** - * @notice Claim rewards for array of MAYC NFTs and send to recipient - * @param _nfts Array of NFTs owned and committed by the msg.sender - * @param _recipient Address to send claim reward to - */ - function claimApeCoin(uint256[] calldata _nfts, address _recipient) - external - onlyPool - nonReentrant - { - _apeCoinStaking.claimMAYC(_nfts, _recipient); - } - - /** - * @notice Withdraw staked ApeCoin from the MAYC pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nfts Array of MAYC NFT's with staked amounts - * @param _recipient Address to send withdraw amount and claim to - */ - function withdrawApeCoin( - ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient, - DataTypes.TimeLockParams memory _timeLockParams - ) external onlyPool nonReentrant { - ApeStakingLogic.executeWithdrawMAYC( - POOL, - _ERC721Data.underlyingAsset, - _apeCoinStaking, - _nfts, - _recipient, - _timeLockParams - ); - } - - /** - * @notice Deposit ApeCoin to the Pair Pool, where Pair = (MAYC + BAKC) - * @param _nftPairs Array of PairNftWithAmount structs - * @dev Commits 1 or more Pairs, each with an ApeCoin amount to the Pair pool.\ - * Each BAKC committed must attach an ApeCoin amount >= 1 ApeCoin and <= the Pair pool cap amount.\ - * Example: MAYC + BAKC + 1 ApeCoin: [[0, 0, "1000000000000000000"]]\ - */ - function depositBAKC( - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external onlyPool nonReentrant { - _apeCoinStaking.depositBAKC( - new ApeCoinStaking.PairNftDepositWithAmount[](0), - _nftPairs - ); - } - - /** - * @notice Claim rewards for array of Paired NFTs and send to recipient - * @param _nftPairs Array of Paired MAYC NFTs owned and committed by the msg.sender - * @param _recipient Address to send claim reward to - */ - function claimBAKC( - ApeCoinStaking.PairNft[] calldata _nftPairs, - address _recipient - ) external onlyPool nonReentrant { - _apeCoinStaking.claimBAKC( - new ApeCoinStaking.PairNft[](0), - _nftPairs, - _recipient - ); - } - - /** - * @notice Withdraw staked ApeCoin from the Pair pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nftPairs Array of Paired MAYC NFT's with staked amounts - * @dev if pairs have split ownership and BAKC is attempting a withdraw, the withdraw must be for the total staked amount - */ - function withdrawBAKC( - ApeCoinStaking.PairNftWithdrawWithAmount[] calldata _nftPairs, - address _apeRecipient, - DataTypes.TimeLockParams memory _timeLockParams - ) external onlyPool nonReentrant { - ApeStakingLogic.withdrawBAKC( - POOL, - _ERC721Data.underlyingAsset, - _apeCoinStaking, - POOL_ID(), - _nftPairs, - _apeRecipient, - _timeLockParams - ); - } - - function POOL_ID() internal pure virtual override returns (uint256) { - return ApeStakingLogic.MAYC_POOL_ID; + function isBayc() internal pure virtual override returns (bool) { + return false; } function getXTokenType() external pure override returns (XTokenType) { diff --git a/contracts/protocol/tokenization/PTokenSApe.sol b/contracts/protocol/tokenization/PTokenSApe.sol index 700c5ffef..381008f3c 100644 --- a/contracts/protocol/tokenization/PTokenSApe.sol +++ b/contracts/protocol/tokenization/PTokenSApe.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.10; import {IPool} from "../../interfaces/IPool.sol"; import {PToken} from "./PToken.sol"; -import {INTokenApeStaking} from "../../interfaces/INTokenApeStaking.sol"; import {WadRayMath} from "../libraries/math/WadRayMath.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; @@ -13,6 +12,7 @@ import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; import {IScaledBalanceToken} from "../../interfaces/IScaledBalanceToken.sol"; import {IncentivizedERC20} from "./base/IncentivizedERC20.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; +import {IParaApeStaking} from "../../interfaces/IParaApeStaking.sol"; /** * @title sApe PToken @@ -22,17 +22,10 @@ import {DataTypes} from "../libraries/types/DataTypes.sol"; contract PTokenSApe is PToken { using WadRayMath for uint256; - INTokenApeStaking immutable nBAYC; - INTokenApeStaking immutable nMAYC; + IParaApeStaking immutable paraApeStaking; - constructor( - IPool pool, - address _nBAYC, - address _nMAYC - ) PToken(pool) { - require(_nBAYC != address(0) && _nMAYC != address(0)); - nBAYC = INTokenApeStaking(_nBAYC); - nMAYC = INTokenApeStaking(_nMAYC); + constructor(IPool pool) PToken(pool) { + paraApeStaking = IParaApeStaking(pool.paraApeStaking()); } function mint( @@ -55,9 +48,7 @@ contract PTokenSApe is PToken { } function balanceOf(address user) public view override returns (uint256) { - uint256 totalStakedAPE = nBAYC.getUserApeStakingAmount(user) + - nMAYC.getUserApeStakingAmount(user); - return totalStakedAPE; + return paraApeStaking.totalSApeBalance(user); } function scaledBalanceOf(address user) @@ -78,11 +69,11 @@ contract PTokenSApe is PToken { } function transferOnLiquidation( - address, - address, - uint256 - ) external view override onlyPool { - revert("not allowed"); + address from, + address to, + uint256 value + ) external override onlyPool { + return paraApeStaking.transferSApeBalance(from, to, value); } function _transfer( diff --git a/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol b/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol deleted file mode 100644 index 556a0c7a4..000000000 --- a/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol +++ /dev/null @@ -1,387 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.10; -import {ApeCoinStaking} from "../../../dependencies/yoga-labs/ApeCoinStaking.sol"; -import {IERC721} from "../../../dependencies/openzeppelin/contracts/IERC721.sol"; -import {SafeERC20} from "../../../dependencies/openzeppelin/contracts/SafeERC20.sol"; -import {IERC20} from "../../../dependencies/openzeppelin/contracts/IERC20.sol"; -import {ITimeLock} from "../../../interfaces/ITimeLock.sol"; -import "../../../interfaces/IPool.sol"; -import {DataTypes} from "../../libraries/types/DataTypes.sol"; -import {PercentageMath} from "../../libraries/math/PercentageMath.sol"; -import "./MintableERC721Logic.sol"; -import "../../../dependencies/openzeppelin/contracts/SafeCast.sol"; -import "../../../interfaces/INToken.sol"; - -/** - * @title ApeStakingLogic library - * - * @notice Implements the base logic for ApeStaking - */ -library ApeStakingLogic { - using SafeERC20 for IERC20; - using PercentageMath for uint256; - using SafeCast for uint256; - - uint256 constant BAYC_POOL_ID = 1; - uint256 constant MAYC_POOL_ID = 2; - uint256 constant BAKC_POOL_ID = 3; - - struct APEStakingParameter { - uint256 unstakeIncentive; - } - event UnstakeApeIncentiveUpdated(uint256 oldValue, uint256 newValue); - - /** - * @notice Withdraw staked ApeCoin from the BAYC pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nfts Array of BAYC NFT's with staked amounts - * @param _recipient Address to send withdraw amount and claim to - */ - function executeWithdrawBAYC( - IPool POOL, - address underlyingAsset, - ApeCoinStaking _apeCoinStaking, - ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient, - DataTypes.TimeLockParams memory _timeLockParams - ) external { - if (_timeLockParams.releaseTime != 0) { - IERC20 ApeCoin = _apeCoinStaking.apeCoin(); - uint256 beforeBalance = ApeCoin.balanceOf(address(this)); - _apeCoinStaking.withdrawBAYC(_nfts, address(this)); - uint256 afterBalance = ApeCoin.balanceOf(address(this)); - - uint256 totalAmount = afterBalance - beforeBalance; - createApeCoinAgreement( - POOL, - underlyingAsset, - ApeCoin, - totalAmount, - _recipient, - _timeLockParams - ); - } else { - _apeCoinStaking.withdrawBAYC(_nfts, _recipient); - } - } - - /** - * @notice Withdraw staked ApeCoin from the MAYC pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nfts Array of MAYC NFT's with staked amounts - * @param _recipient Address to send withdraw amount and claim to - */ - function executeWithdrawMAYC( - IPool POOL, - address underlyingAsset, - ApeCoinStaking _apeCoinStaking, - ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient, - DataTypes.TimeLockParams memory _timeLockParams - ) external { - if (_timeLockParams.releaseTime != 0) { - IERC20 ApeCoin = _apeCoinStaking.apeCoin(); - uint256 beforeBalance = ApeCoin.balanceOf(address(this)); - _apeCoinStaking.withdrawMAYC(_nfts, address(this)); - uint256 afterBalance = ApeCoin.balanceOf(address(this)); - - uint256 totalAmount = afterBalance - beforeBalance; - createApeCoinAgreement( - POOL, - underlyingAsset, - ApeCoin, - totalAmount, - _recipient, - _timeLockParams - ); - } else { - _apeCoinStaking.withdrawMAYC(_nfts, _recipient); - } - } - - /** - * @notice withdraw Ape coin staking position from ApeCoinStaking - * @param _apeCoinStaking ApeCoinStaking contract address - * @param poolId identify whether BAYC or MAYC paired with BAKC - * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts - * @param _apeRecipient the receiver of ape coin - */ - function withdrawBAKC( - IPool POOL, - address underlyingAsset, - ApeCoinStaking _apeCoinStaking, - uint256 poolId, - ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs, - address _apeRecipient, - DataTypes.TimeLockParams memory _timeLockParams - ) external { - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); - - IERC20 ApeCoin = _apeCoinStaking.apeCoin(); - uint256 beforeBalance = ApeCoin.balanceOf(address(this)); - if (poolId == BAYC_POOL_ID) { - _apeCoinStaking.withdrawBAKC(_nftPairs, _otherPairs); - } else { - _apeCoinStaking.withdrawBAKC(_otherPairs, _nftPairs); - } - uint256 afterBalance = ApeCoin.balanceOf(address(this)); - - uint256 totalAmount = afterBalance - beforeBalance; - if (_timeLockParams.releaseTime != 0) { - createApeCoinAgreement( - POOL, - underlyingAsset, - ApeCoin, - totalAmount, - _apeRecipient, - _timeLockParams - ); - } else { - ApeCoin.safeTransfer(_apeRecipient, totalAmount); - } - } - - function createApeCoinAgreement( - IPool POOL, - address underlyingAsset, - IERC20 ApeCoin, - uint256 amount, - address recipient, - DataTypes.TimeLockParams memory timeLockParams - ) internal { - ITimeLock timeLock = POOL.TIME_LOCK(); - uint256[] memory amounts = new uint256[](1); - amounts[0] = amount; - - timeLock.createAgreement( - DataTypes.AssetType.ERC20, - timeLockParams.actionType, - underlyingAsset, - address(ApeCoin), - amounts, - recipient, - timeLockParams.releaseTime - ); - ApeCoin.safeTransfer(address(timeLock), amount); - } - - /** - * @notice undate incentive percentage for unstakePositionAndRepay - * @param stakingParameter storage for Ape staking - * @param incentive new incentive percentage - */ - function executeSetUnstakeApeIncentive( - APEStakingParameter storage stakingParameter, - uint256 incentive - ) external { - require( - incentive < PercentageMath.HALF_PERCENTAGE_FACTOR, - "Value Too High" - ); - uint256 oldValue = stakingParameter.unstakeIncentive; - if (oldValue != incentive) { - stakingParameter.unstakeIncentive = incentive; - emit UnstakeApeIncentiveUpdated(oldValue, incentive); - } - } - - struct UnstakeAndRepayParams { - IPool POOL; - ApeCoinStaking _apeCoinStaking; - address _underlyingAsset; - uint256 poolId; - uint256 tokenId; - address incentiveReceiver; - address bakcNToken; - } - - /** - * @notice Unstake Ape coin staking position and repay user debt - * @param _owners The state of ownership for nToken - * @param stakingParameter storage for Ape staking - * @param params The additional parameters needed to execute this function - */ - function executeUnstakePositionAndRepay( - mapping(uint256 => address) storage _owners, - APEStakingParameter storage stakingParameter, - UnstakeAndRepayParams memory params - ) external { - if ( - IERC721(params._underlyingAsset).ownerOf(params.tokenId) != - address(this) - ) { - return; - } - address positionOwner = _owners[params.tokenId]; - IERC20 _apeCoin = params._apeCoinStaking.apeCoin(); - uint256 balanceBefore = _apeCoin.balanceOf(address(this)); - - //1 unstake all position - { - //1.1 unstake Main pool position - (uint256 stakedAmount, ) = params._apeCoinStaking.nftPosition( - params.poolId, - params.tokenId - ); - if (stakedAmount > 0) { - ApeCoinStaking.SingleNft[] - memory nfts = new ApeCoinStaking.SingleNft[](1); - nfts[0].tokenId = params.tokenId.toUint32(); - nfts[0].amount = stakedAmount.toUint224(); - if (params.poolId == BAYC_POOL_ID) { - params._apeCoinStaking.withdrawBAYC(nfts, address(this)); - } else { - params._apeCoinStaking.withdrawMAYC(nfts, address(this)); - } - } - //1.2 unstake bakc pool position - (uint256 bakcTokenId, bool isPaired) = params - ._apeCoinStaking - .mainToBakc(params.poolId, params.tokenId); - if (isPaired) { - (stakedAmount, ) = params._apeCoinStaking.nftPosition( - BAKC_POOL_ID, - bakcTokenId - ); - if (stakedAmount > 0) { - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 1 - ); - _nftPairs[0].mainTokenId = params.tokenId.toUint32(); - _nftPairs[0].bakcTokenId = bakcTokenId.toUint32(); - _nftPairs[0].amount = stakedAmount.toUint184(); - _nftPairs[0].isUncommit = true; - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); - - uint256 bakcBeforeBalance = _apeCoin.balanceOf( - params.bakcNToken - ); - if (params.poolId == BAYC_POOL_ID) { - params._apeCoinStaking.withdrawBAKC( - _nftPairs, - _otherPairs - ); - } else { - params._apeCoinStaking.withdrawBAKC( - _otherPairs, - _nftPairs - ); - } - uint256 bakcAfterBalance = _apeCoin.balanceOf( - params.bakcNToken - ); - uint256 balanceDiff = bakcAfterBalance - bakcBeforeBalance; - if (balanceDiff > 0) { - address bakcOwner = INToken(params.bakcNToken).ownerOf( - bakcTokenId - ); - _apeCoin.safeTransferFrom( - params.bakcNToken, - bakcOwner, - balanceDiff - ); - } - } - } - } - - uint256 unstakedAmount = _apeCoin.balanceOf(address(this)) - - balanceBefore; - if (unstakedAmount == 0) { - return; - } - //2 send incentive to caller - if (params.incentiveReceiver != address(0)) { - uint256 unstakeIncentive = stakingParameter.unstakeIncentive; - if (unstakeIncentive > 0) { - uint256 incentiveAmount = unstakedAmount.percentMul( - unstakeIncentive - ); - _apeCoin.safeTransfer( - params.incentiveReceiver, - incentiveAmount - ); - unstakedAmount = unstakedAmount - incentiveAmount; - } - } - - //3 repay and supply - params.POOL.repayAndSupply( - params._underlyingAsset, - positionOwner, - unstakedAmount - ); - } - - /** - * @notice get user total ape staking position - * @param userState The user state of nToken - * @param ownedTokens The ownership mapping state of nNtoken - * @param user User address - * @param poolId identify whether BAYC pool or MAYC pool - * @param _apeCoinStaking ApeCoinStaking contract address - */ - function getUserTotalStakingAmount( - mapping(address => UserState) storage userState, - mapping(address => mapping(uint256 => uint256)) storage ownedTokens, - address _underlyingAsset, - address user, - uint256 poolId, - ApeCoinStaking _apeCoinStaking - ) external view returns (uint256) { - uint256 totalBalance = uint256(userState[user].balance); - uint256 totalAmount; - for (uint256 index = 0; index < totalBalance; index++) { - uint256 tokenId = ownedTokens[user][index]; - totalAmount += getTokenIdStakingAmount( - _underlyingAsset, - poolId, - _apeCoinStaking, - tokenId - ); - } - - return totalAmount; - } - - /** - * @notice get ape staking position for a tokenId - * @param poolId identify whether BAYC pool or MAYC pool - * @param _apeCoinStaking ApeCoinStaking contract address - * @param tokenId specified the tokenId for the position - */ - function getTokenIdStakingAmount( - address _underlyingAsset, - uint256 poolId, - ApeCoinStaking _apeCoinStaking, - uint256 tokenId - ) public view returns (uint256) { - if (IERC721(_underlyingAsset).ownerOf(tokenId) != address(this)) { - return 0; - } - (uint256 apeStakedAmount, ) = _apeCoinStaking.nftPosition( - poolId, - tokenId - ); - - (uint256 bakcTokenId, bool isPaired) = _apeCoinStaking.mainToBakc( - poolId, - tokenId - ); - - if (isPaired) { - (uint256 bakcStakedAmount, ) = _apeCoinStaking.nftPosition( - BAKC_POOL_ID, - bakcTokenId - ); - apeStakedAmount += bakcStakedAmount; - } - - return apeStakedAmount; - } -} diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 07ff04abc..1c19778a8 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -10,8 +10,6 @@ import { ACLManager, ACLManager__factory, ApeCoinStaking__factory, - ApeStakingLogic, - ApeStakingLogic__factory, ATokenDebtToken, ATokenDebtToken__factory, AuctionLogic, @@ -298,7 +296,7 @@ import { ApeStakingPairPoolLogic__factory, ApeStakingPairPoolLogic, ApeStakingSinglePoolLogic__factory, - ApeStakingSinglePoolLogic, + ApeStakingSinglePoolLogic, ApeCoinPoolLogic__factory, ApeCoinPoolLogic, } from "../types"; import {MockContract} from "ethereum-waffle"; import { @@ -834,14 +832,10 @@ export const deployPoolComponents = async ( const apeStakingLibraries = pick(coreLibraries, [ "contracts/protocol/libraries/logic/BorrowLogic.sol:BorrowLogic", - "contracts/protocol/libraries/logic/SupplyLogic.sol:SupplyLogic", ]); const allTokens = await getAllTokens(); - const APE_WETH_FEE = 3000; - const WETH_USDC_FEE = 500; - const { poolCoreSelectors, poolParametersSelectors, @@ -895,12 +889,6 @@ export const deployPoolComponents = async ( [ provider, (await getAutoCompoundApe()).address, - allTokens.APE.address, - allTokens.USDC.address, - (await getUniswapV3SwapRouter()).address, - allTokens.WETH.address, - APE_WETH_FEE, - WETH_USDC_FEE, (await getParaApeStaking()).address, ], verify, @@ -2201,14 +2189,12 @@ export const deployPTokenAStETH = async ( export const deployPTokenSApe = async ( poolAddress: tEthereumAddress, - nBAYC: tEthereumAddress, - nMAYC: tEthereumAddress, verify?: boolean ) => withSaveAndVerify( new PTokenSApe__factory(await getFirstSigner()), eContractid.PTokenSApeImpl, - [poolAddress, nBAYC, nMAYC], + [poolAddress], verify ) as Promise; @@ -2363,31 +2349,17 @@ export const deployApeCoinStaking = async (verify?: boolean) => { return apeCoinStaking; }; -export const deployApeStakingLogic = async (verify?: boolean) => { - return withSaveAndVerify( - new ApeStakingLogic__factory(await getFirstSigner()), - eContractid.ApeStakingLogic, - [], - verify - ) as Promise; -}; - export const deployNTokenBAYCImpl = async ( apeCoinStaking: tEthereumAddress, poolAddress: tEthereumAddress, delegationRegistry: tEthereumAddress, verify?: boolean ) => { - const apeStakingLogic = - (await getContractAddressInDb(eContractid.ApeStakingLogic)) || - (await deployApeStakingLogic(verify)).address; const mintableERC721Logic = (await getContractAddressInDb(eContractid.MintableERC721Logic)) || (await deployMintableERC721Logic(verify)).address; const libraries = { - ["contracts/protocol/tokenization/libraries/ApeStakingLogic.sol:ApeStakingLogic"]: - apeStakingLogic, ["contracts/protocol/tokenization/libraries/MintableERC721Logic.sol:MintableERC721Logic"]: mintableERC721Logic, }; @@ -2395,7 +2367,7 @@ export const deployNTokenBAYCImpl = async ( return withSaveAndVerify( new NTokenBAYC__factory(libraries, await getFirstSigner()), eContractid.NTokenBAYCImpl, - [poolAddress, apeCoinStaking, delegationRegistry], + [poolAddress, delegationRegistry], verify, false, libraries @@ -2408,23 +2380,18 @@ export const deployNTokenMAYCImpl = async ( delegationRegistry: tEthereumAddress, verify?: boolean ) => { - const apeStakingLogic = - (await getContractAddressInDb(eContractid.ApeStakingLogic)) || - (await deployApeStakingLogic(verify)).address; const mintableERC721Logic = (await getContractAddressInDb(eContractid.MintableERC721Logic)) || (await deployMintableERC721Logic(verify)).address; const libraries = { - ["contracts/protocol/tokenization/libraries/ApeStakingLogic.sol:ApeStakingLogic"]: - apeStakingLogic, ["contracts/protocol/tokenization/libraries/MintableERC721Logic.sol:MintableERC721Logic"]: mintableERC721Logic, }; return withSaveAndVerify( new NTokenMAYC__factory(libraries, await getFirstSigner()), eContractid.NTokenMAYCImpl, - [poolAddress, apeCoinStaking, delegationRegistry], + [poolAddress, delegationRegistry], verify, false, libraries @@ -2433,9 +2400,6 @@ export const deployNTokenMAYCImpl = async ( export const deployNTokenBAKCImpl = async ( poolAddress: tEthereumAddress, - apeCoinStaking: tEthereumAddress, - nBAYC: tEthereumAddress, - nMAYC: tEthereumAddress, delegationRegistry: tEthereumAddress, verify?: boolean ) => { @@ -2449,7 +2413,7 @@ export const deployNTokenBAKCImpl = async ( return withSaveAndVerify( new NTokenBAKC__factory(libraries, await getFirstSigner()), eContractid.NTokenBAKCImpl, - [poolAddress, apeCoinStaking, nBAYC, nMAYC, delegationRegistry], + [poolAddress, delegationRegistry], verify, false, libraries @@ -2738,12 +2702,21 @@ export const deployApeStakingSinglePoolLogic = async (verify?: boolean) => verify ) as Promise; +export const deployApeStakingApeCoinPoolLogic = async (verify?: boolean) => + withSaveAndVerify( + new ApeCoinPoolLogic__factory(await getFirstSigner()), + eContractid.ApeStakingApeCoinPoolLogic, + [], + verify + ) as Promise; + export const deployParaApeStakingLibraries = async ( verify?: boolean ): Promise => { const p2pLogic = await deployApeStakingP2PLogic(verify); const pairPoolLogic = await deployApeStakingPairPoolLogic(verify); const singlePoolLogic = await deployApeStakingSinglePoolLogic(verify); + const apeCoinPoolLogic = await deployApeStakingApeCoinPoolLogic(verify); return { ["contracts/apestaking/logic/ApeStakingP2PLogic.sol:ApeStakingP2PLogic"]: @@ -2752,6 +2725,8 @@ export const deployParaApeStakingLibraries = async ( pairPoolLogic.address, ["contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol:ApeStakingSinglePoolLogic"]: singlePoolLogic.address, + ["contracts/apestaking/logic/ApeCoinPoolLogic.sol:ApeCoinPoolLogic"]: + apeCoinPoolLogic.address, }; }; diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index 62e9346ca..398c4b167 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -430,20 +430,8 @@ export const initReservesByHelper = async ( variableDebtTokenToUse = astETHVariableDebtTokenImplementationAddress; } else if (reserveSymbol === ERC20TokenContractId.sAPE) { if (!pTokenSApeImplementationAddress) { - const protocolDataProvider = await getProtocolDataProvider(); - const allTokens = await protocolDataProvider.getAllXTokens(); - const nBAYC = - // eslint-disable-next-line - allTokens.find( - (x) => x.symbol == NTokenContractId.nBAYC - )!.tokenAddress; - const nMAYC = - // eslint-disable-next-line - allTokens.find( - (x) => x.symbol == NTokenContractId.nMAYC - )!.tokenAddress; pTokenSApeImplementationAddress = ( - await deployPTokenSApe(pool.address, nBAYC, nMAYC, verify) + await deployPTokenSApe(pool.address, verify) ).address; } xTokenToUse = pTokenSApeImplementationAddress; @@ -536,27 +524,9 @@ export const initReservesByHelper = async ( xTokenToUse = nTokenMAYCImplementationAddress; } else if (reserveSymbol === ERC721TokenContractId.BAKC) { if (!nTokenBAKCImplementationAddress) { - const apeCoinStaking = - (await getContractAddressInDb(eContractid.ApeCoinStaking)) || - (await deployApeCoinStaking(verify)).address; - const protocolDataProvider = await getProtocolDataProvider(); - const allTokens = await protocolDataProvider.getAllXTokens(); - const nBAYC = - // eslint-disable-next-line - allTokens.find( - (x) => x.symbol == NTokenContractId.nBAYC - )!.tokenAddress; - const nMAYC = - // eslint-disable-next-line - allTokens.find( - (x) => x.symbol == NTokenContractId.nMAYC - )!.tokenAddress; nTokenBAKCImplementationAddress = ( await deployNTokenBAKCImpl( pool.address, - apeCoinStaking, - nBAYC, - nMAYC, delegationRegistryAddress, verify ) diff --git a/helpers/types.ts b/helpers/types.ts index 1efb5403f..563b8c95e 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -242,7 +242,6 @@ export enum eContractid { StETHDebtToken = "StETHDebtToken", CApeDebtToken = "CApeDebtToken", AStETHDebtToken = "AStETHDebtToken", - ApeStakingLogic = "ApeStakingLogic", MintableERC721Logic = "MintableERC721Logic", MerkleVerifier = "MerkleVerifier", ExecutionDelegate = "ExecutionDelegate", @@ -263,6 +262,7 @@ export enum eContractid { ApeStakingP2PLogic = "ApeStakingP2PLogic", ApeStakingPairPoolLogic = "ApeStakingPairPoolLogic", ApeStakingSinglePoolLogic = "ApeStakingSinglePoolLogic", + ApeStakingApeCoinPoolLogic = "ApeStakingApeCoinPoolLogic", ParaApeStakingImpl = "ParaApeStakingImpl", ParaApeStaking = "ParaApeStaking", yAPE = "yAPE", diff --git a/scripts/upgrade/ntoken.ts b/scripts/upgrade/ntoken.ts index 7c1342f37..1e1980dc4 100644 --- a/scripts/upgrade/ntoken.ts +++ b/scripts/upgrade/ntoken.ts @@ -104,23 +104,9 @@ export const upgradeNToken = async (verify = false) => { } else if (xTokenType == XTokenType.NTokenBAKC) { if (!nTokenBAKCImplementationAddress) { console.log("deploy NTokenBAKC implementation"); - const apeCoinStaking = await getApeCoinStaking(); - const nBAYC = - // eslint-disable-next-line - allXTokens.find( - (x) => x.symbol == NTokenContractId.nBAYC - )!.tokenAddress; - const nMAYC = - // eslint-disable-next-line - allXTokens.find( - (x) => x.symbol == NTokenContractId.nMAYC - )!.tokenAddress; nTokenBAKCImplementationAddress = ( await deployNTokenBAKCImpl( pool.address, - apeCoinStaking.address, - nBAYC, - nMAYC, delegationRegistry, verify ) diff --git a/scripts/upgrade/ptoken.ts b/scripts/upgrade/ptoken.ts index bebe3a7a4..6699870c3 100644 --- a/scripts/upgrade/ptoken.ts +++ b/scripts/upgrade/ptoken.ts @@ -86,18 +86,8 @@ export const upgradePToken = async (verify = false) => { } else if (xTokenType == XTokenType.PTokenSApe) { if (!pTokenSApeImplementationAddress) { console.log("deploy PTokenSApe implementation"); - const nBAYC = - // eslint-disable-next-line - allXTokens.find( - (x) => x.symbol == NTokenContractId.nBAYC - )!.tokenAddress; - const nMAYC = - // eslint-disable-next-line - allXTokens.find( - (x) => x.symbol == NTokenContractId.nMAYC - )!.tokenAddress; pTokenSApeImplementationAddress = ( - await deployPTokenSApe(poolAddress, nBAYC, nMAYC, verify) + await deployPTokenSApe(poolAddress, verify) ).address; } newImpl = pTokenSApeImplementationAddress; From 2a509662eaa4654b9e901240e2d86eee864059e6 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 24 Jul 2023 15:52:30 +0800 Subject: [PATCH 62/99] chore: add pool ape staking test case. --- contracts/apestaking/ParaApeStaking.sol | 65 +- .../apestaking/logic/ApeCoinPoolLogic.sol | 210 +- .../logic/ApeStakingSinglePoolLogic.sol | 15 +- .../protocol/libraries/helpers/Errors.sol | 5 +- helpers/contracts-deployments.ts | 18 +- helpers/types.ts | 5 +- scripts/upgrade/ntoken.ts | 6 +- test/_pool_ape_staking.spec.ts | 3307 ----------------- test/para_ape_staking.spec.ts | 14 +- test/para_pool_ape_staking.spec.ts | 1217 ++++++ 10 files changed, 1421 insertions(+), 3441 deletions(-) delete mode 100644 test/_pool_ape_staking.spec.ts create mode 100644 test/para_pool_ape_staking.spec.ts diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index cf78d326d..115ab140d 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -239,7 +239,7 @@ contract ParaApeStaking is address to, uint256 amount ) external { - require(msg.sender == psApe, "invalid"); + require(msg.sender == psApe, Errors.CALLER_NOT_ALLOWED); uint256 shareAmount = ICApe(cApe).getShareByPooledApe(amount); sApeBalance[from].freeShareBalance -= shareAmount.toUint128(); sApeBalance[to].freeShareBalance += shareAmount.toUint128(); @@ -284,10 +284,9 @@ contract ParaApeStaking is ); } - function CompoundApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) + function compoundApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external - whenNotPaused - nonReentrant + onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; @@ -303,6 +302,24 @@ contract ParaApeStaking is ); } + function apeCoinPoolPendingReward(bool isBAYC, uint32[] calldata tokenIds) + external + view + returns (uint256) + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + (, uint256 pendingReward, ) = ApeCoinPoolLogic.calculatePendingReward( + apeCoinPoolStates[poolId], + vars, + isBAYC, + tokenIds + ); + return pendingReward; + } + function claimApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external whenNotPaused @@ -359,11 +376,11 @@ contract ParaApeStaking is ); } - function CompoundApeCoinPairPool( + function compoundApeCoinPairPool( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds - ) external whenNotPaused nonReentrant { + ) external onlyApeStakingBot { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC @@ -379,6 +396,40 @@ contract ParaApeStaking is ); } + function apeCoinPairPoolPendingReward( + bool isBAYC, + uint32[] calldata apeTokenIds + ) external view returns (uint256) { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + (, uint256 pendingReward, ) = ApeCoinPoolLogic.calculatePendingReward( + apeCoinPoolStates[poolId], + vars, + isBAYC, + apeTokenIds + ); + return pendingReward; + } + + function claimApeCoinPairPool(bool isBAYC, uint32[] calldata apeTokenIds) + external + whenNotPaused + nonReentrant + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + uint256 poolId = isBAYC + ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ApeCoinPoolLogic.claimApeCoinPairPool( + apeCoinPoolStates[poolId], + vars, + isBAYC, + apeTokenIds + ); + } + function withdrawApeCoinPairPool( ApeCoinPairActionInfo calldata withdrawInfo ) external whenNotPaused nonReentrant { @@ -408,7 +459,7 @@ contract ParaApeStaking is : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; uint256 PairPoolId = isBAYC ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID; + : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; ApeCoinPoolLogic.tryUnstakeApeCoinPoolPosition( apeCoinPoolStates[singlePoolId], diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index b288051a6..bc3a2a911 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -76,7 +76,7 @@ library ApeCoinPoolLogic { uint256 shareAmount = ICApe(cApe).getShareByPooledApe(amount); require( sApeBalanceCache.freeShareBalance >= shareAmount, - "balance not enough" + Errors.SAPE_FREE_BALANCE_NOT_ENOUGH ); sApeBalanceCache.freeShareBalance -= shareAmount.toUint128(); @@ -116,15 +116,12 @@ library ApeCoinPoolLogic { Errors.NOT_THE_OWNER ); - uint256 currentMatchCount = apeMatchedCount[vars.apeToken][tokenId]; - if (currentMatchCount == 0) { - IERC721(vars.apeToken).safeTransferFrom( - vars.nApe, - address(this), - tokenId - ); - } - apeMatchedCount[vars.apeToken][tokenId] = currentMatchCount + 1; + _handleApeTransferIn( + apeMatchedCount, + vars.apeToken, + vars.nApe, + tokenId + ); //update status poolState @@ -179,7 +176,7 @@ library ApeCoinPoolLogic { require( poolState.tokenStatus[tokenId].isInPool, - "not in ape coin pool" + Errors.NFT_NOT_IN_POOL ); // construct staking data @@ -246,26 +243,17 @@ library ApeCoinPoolLogic { vars.positionCap = vars.maycMatchedCap; } address msgSender = msg.sender; - require(msgSender == nApeOwner, Errors.NOT_THE_OWNER); + require( + msgSender == nApeOwner || msgSender == vars.nApe, + Errors.NOT_THE_OWNER + ); ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = withdrawInfo.tokenIds[index]; - require( - poolState.tokenStatus[tokenId].isInPool, - "not in ape coin pool" - ); + // we don't need check pair is in pool here again - uint256 matchedCount = apeMatchedCount[vars.apeToken][tokenId]; - if (matchedCount == 1) { - IERC721(vars.apeToken).safeTransferFrom( - address(this), - vars.nApe, - tokenId - ); - } - apeMatchedCount[vars.apeToken][tokenId] = matchedCount - 1; delete poolState.tokenStatus[tokenId]; // construct staking data @@ -273,9 +261,6 @@ library ApeCoinPoolLogic { tokenId: tokenId, amount: vars.positionCap.toUint224() }); - - //emit event - emit ApeCoinPoolWithdrew(withdrawInfo.isBAYC, tokenId); } //withdraw from ApeCoinStaking @@ -288,7 +273,7 @@ library ApeCoinPoolLogic { vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - uint128 totalApeCoinAmount = (vars.bakcMatchedCap * arrayLength) + uint128 totalApeCoinAmount = (vars.positionCap * arrayLength) .toUint128(); _handleApeCoin( sApeBalance, @@ -296,7 +281,7 @@ library ApeCoinPoolLogic { totalApeCoinAmount, withdrawInfo.cashToken, withdrawInfo.cashAmount, - msgSender + nApeOwner ); //distribute reward @@ -312,6 +297,21 @@ library ApeCoinPoolLogic { ); } poolState.totalPosition = totalPosition; + + //transfer ape and BAKC back to nToken + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = withdrawInfo.tokenIds[index]; + + _handleApeTransferOut( + apeMatchedCount, + vars.apeToken, + vars.nApe, + tokenId + ); + + //emit event + emit ApeCoinPoolWithdrew(withdrawInfo.isBAYC, tokenId); + } } function depositApeCoinPairPool( @@ -353,17 +353,17 @@ library ApeCoinPoolLogic { ); } - uint256 currentMatchCount = apeMatchedCount[vars.apeToken][ + _handleApeTransferIn( + apeMatchedCount, + vars.apeToken, + vars.nApe, apeTokenId - ]; - if (currentMatchCount == 0) { - IERC721(vars.apeToken).safeTransferFrom( - vars.nApe, - address(this), - apeTokenId - ); - } - apeMatchedCount[vars.apeToken][apeTokenId] = currentMatchCount + 1; + ); + IERC721(vars.bakc).safeTransferFrom( + vars.nBakc, + address(this), + bakcTokenId + ); //update status poolState.tokenStatus[apeTokenId].rewardsDebt = vars @@ -371,12 +371,6 @@ library ApeCoinPoolLogic { poolState.tokenStatus[apeTokenId].isInPool = true; poolState.tokenStatus[apeTokenId].bakcTokenId = bakcTokenId; - IERC721(vars.bakc).safeTransferFrom( - vars.nBakc, - address(this), - bakcTokenId - ); - // construct staking data _nftPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ mainTokenId: apeTokenId, @@ -439,7 +433,7 @@ library ApeCoinPoolLogic { require( poolState.tokenStatus[apeTokenId].isInPool, - "not in ape coin pool" + Errors.NFT_NOT_IN_POOL ); // construct staking data @@ -530,30 +524,7 @@ library ApeCoinPoolLogic { } } - require( - poolState.tokenStatus[apeTokenId].isInPool, - "not in ape coin pool" - ); - - //transfer nft - { - uint256 matchedCount = apeMatchedCount[vars.apeToken][ - apeTokenId - ]; - if (matchedCount == 1) { - IERC721(vars.apeToken).safeTransferFrom( - address(this), - vars.apeToken, - apeTokenId - ); - } - apeMatchedCount[vars.apeToken][apeTokenId] = matchedCount - 1; - IERC721(vars.bakc).safeTransferFrom( - address(this), - vars.nBakc, - bakcTokenId - ); - } + // we don't need check pair is in pool here again delete poolState.tokenStatus[apeTokenId]; @@ -564,13 +535,6 @@ library ApeCoinPoolLogic { amount: vars.bakcMatchedCap.toUint184(), isUncommit: true }); - - //emit event - emit ApeCoinPairPoolWithdrew( - withdrawInfo.isBAYC, - apeTokenId, - bakcTokenId - ); } //withdraw from ApeCoinStaking @@ -597,7 +561,7 @@ library ApeCoinPoolLogic { totalApeCoinAmount, withdrawInfo.cashToken, withdrawInfo.cashAmount, - msg.sender + nApeOwner ); //distribute reward @@ -613,6 +577,31 @@ library ApeCoinPoolLogic { ); } poolState.totalPosition = totalPosition; + + //transfer ape and BAKC back to nToken + for (uint256 index = 0; index < arrayLength; index++) { + uint32 apeTokenId = withdrawInfo.apeTokenIds[index]; + uint32 bakcTokenId = withdrawInfo.bakcTokenIds[index]; + + _handleApeTransferOut( + apeMatchedCount, + vars.apeToken, + vars.nApe, + apeTokenId + ); + IERC721(vars.bakc).safeTransferFrom( + address(this), + vars.nBakc, + bakcTokenId + ); + + //emit event + emit ApeCoinPairPoolWithdrew( + withdrawInfo.isBAYC, + apeTokenId, + bakcTokenId + ); + } } function tryUnstakeApeCoinPoolPosition( @@ -742,7 +731,7 @@ library ApeCoinPoolLogic { //check is in pool require( poolState.tokenStatus[tokenId].isInPool, - "not in ape coin pool" + Errors.NFT_NOT_IN_POOL ); //update reward, to save gas we don't claim pending reward in ApeCoinStaking. @@ -754,6 +743,33 @@ library ApeCoinPoolLogic { return (claimFor, pendingReward, accumulatedRewardsPerNft); } + function _handleApeTransferIn( + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + address ape, + address nApe, + uint32 tokenId + ) internal { + uint256 currentMatchCount = apeMatchedCount[ape][tokenId]; + if (currentMatchCount == 0) { + IERC721(ape).safeTransferFrom(nApe, address(this), tokenId); + } + apeMatchedCount[ape][tokenId] = currentMatchCount + 1; + } + + function _handleApeTransferOut( + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + address ape, + address nApe, + uint32 tokenId + ) internal { + uint256 matchedCount = apeMatchedCount[ape][tokenId]; + matchedCount -= 1; + if (matchedCount == 0) { + IERC721(ape).safeTransferFrom(address(this), nApe, tokenId); + } + apeMatchedCount[ape][tokenId] = matchedCount; + } + function _prepareApeCoin( mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, @@ -762,8 +778,11 @@ library ApeCoinPoolLogic { uint256 cashAmount, address user ) internal { - require(cashToken == vars.cApe || cashToken == vars.apeCoin, ""); - require(cashAmount <= totalApeCoinNeeded, ""); + require( + cashToken == vars.cApe || cashToken == vars.apeCoin, + Errors.INVALID_TOKEN + ); + require(cashAmount <= totalApeCoinNeeded, Errors.INVALID_CASH_AMOUNT); if (cashAmount != 0) { IERC20(cashToken).safeTransferFrom(user, address(this), cashAmount); @@ -779,7 +798,7 @@ library ApeCoinPoolLogic { .getShareByPooledApe(freeSApeBalanceNeeded); require( sApeBalanceCache.freeShareBalance >= freeShareBalanceNeeded, - "free balance not enough" + Errors.SAPE_FREE_BALANCE_NOT_ENOUGH ); sApeBalanceCache.freeShareBalance -= freeShareBalanceNeeded .toUint128(); @@ -802,12 +821,11 @@ library ApeCoinPoolLogic { uint256 cashAmount, address user ) internal { - require(cashToken == vars.cApe || cashToken == vars.apeCoin, ""); - require(cashAmount <= totalApeCoinWithdrew, ""); - - if (cashAmount != 0) { - IERC20(cashToken).safeTransfer(user, cashAmount); - } + require( + cashToken == vars.cApe || cashToken == vars.apeCoin, + Errors.INVALID_TOKEN + ); + require(cashAmount <= totalApeCoinWithdrew, Errors.INVALID_CASH_AMOUNT); uint256 cApeDepositAmount = (cashToken == vars.apeCoin) ? 0 @@ -826,6 +844,7 @@ library ApeCoinPoolLogic { if (cashAmount > 0) { _validateDropSApeBalance(vars.pool, vars.sApeReserveId, user); + IERC20(cashToken).safeTransfer(user, cashAmount); } if (cApeDepositAmount > 0) { @@ -881,11 +900,14 @@ library ApeCoinPoolLogic { IAutoCompoundApe(vars.cApe).deposit(address(this), rewardAmount); uint256 cApeShare = ICApe(vars.cApe).getShareByPooledApe(rewardAmount); - uint256 compoundFee = cApeShare.percentMul(vars.compoundFee); - cApeShare -= compoundFee; - poolState.accumulatedRewardsPerNft += - cApeShare.toUint128() / - totalPosition; + uint256 compoundFee = cApeShare; + if (totalPosition != 0) { + compoundFee = cApeShare.percentMul(vars.compoundFee); + cApeShare -= compoundFee; + poolState.accumulatedRewardsPerNft += + cApeShare.toUint128() / + totalPosition; + } if (compoundFee > 0) { cApeShareBalance[address(this)] += compoundFee; diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index d764f5ab3..7105e6166 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -145,7 +145,7 @@ library ApeStakingSinglePoolLogic { require( poolState.tokenStatus[tokenId].isInPool, - Errors.NFT_NOT_IN_SINGLE_POOL + Errors.NFT_NOT_IN_POOL ); // construct staking data @@ -200,11 +200,11 @@ library ApeStakingSinglePoolLogic { require( apePoolState.tokenStatus[apeTokenId].isInPool, - Errors.NFT_NOT_IN_SINGLE_POOL + Errors.NFT_NOT_IN_POOL ); require( bakcPoolState.tokenStatus[bakcTokenId].isInPool, - Errors.NFT_NOT_IN_SINGLE_POOL + Errors.NFT_NOT_IN_POOL ); // construct staking data @@ -257,7 +257,7 @@ library ApeStakingSinglePoolLogic { require( poolState.tokenStatus[tokenId].isInPool, - Errors.NFT_NOT_IN_SINGLE_POOL + Errors.NFT_NOT_IN_POOL ); // construct staking data @@ -335,7 +335,7 @@ library ApeStakingSinglePoolLogic { // we just need to check bakc is in the pool require( bakcPoolState.tokenStatus[bakcTokenId].isInPool, - Errors.NFT_NOT_IN_SINGLE_POOL + Errors.NFT_NOT_IN_POOL ); // construct staking data @@ -781,10 +781,7 @@ library ApeStakingSinglePoolLogic { } } - require( - tokenStatus[tokenId].isInPool, - Errors.NFT_NOT_IN_SINGLE_POOL - ); + require(tokenStatus[tokenId].isInPool, Errors.NFT_NOT_IN_POOL); //update reward, to save gas we don't claim pending reward in ApeCoinStaking. rewardShares += (accumulatedRewardsPerNft - diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index b20bbba60..8f9b4c33e 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -143,8 +143,8 @@ library Errors { string public constant NOT_APE_STAKING_BOT = "175"; //not ape staking bot string public constant NOT_THE_SAME_OWNER = "176"; //not the same owner string public constant NFT_NOT_ALLOWED = "177"; //nft now allowed - string public constant NFT_NOT_IN_SINGLE_POOL = "178"; //nft not in single pool - + string public constant NFT_NOT_IN_POOL = "178"; //nft not in the pool + string public constant SAPE_FREE_BALANCE_NOT_ENOUGH = "179"; //sape free balance not enough string public constant NOT_ORDER_OFFERER = "180"; //not order offerer string public constant ORDER_ALREADY_CANCELLED = "181"; //order already cancelled string public constant ORDER_NOT_STARTED = "182"; //order not started @@ -155,4 +155,5 @@ library Errors { string public constant ORDER_TYPE_MATCH_FAILED = "187"; //orders type match failed string public constant ORDER_SHARE_MATCH_FAILED = "188"; //orders share match failed string public constant NO_BREAK_UP_PERMISSION = "189"; //no permission to break up + string public constant INVALID_CASH_AMOUNT = "190"; //invalid cash amount } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 1c19778a8..e3418278e 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -296,7 +296,9 @@ import { ApeStakingPairPoolLogic__factory, ApeStakingPairPoolLogic, ApeStakingSinglePoolLogic__factory, - ApeStakingSinglePoolLogic, ApeCoinPoolLogic__factory, ApeCoinPoolLogic, + ApeStakingSinglePoolLogic, + ApeCoinPoolLogic__factory, + ApeCoinPoolLogic, } from "../types"; import {MockContract} from "ethereum-waffle"; import { @@ -2703,12 +2705,12 @@ export const deployApeStakingSinglePoolLogic = async (verify?: boolean) => ) as Promise; export const deployApeStakingApeCoinPoolLogic = async (verify?: boolean) => - withSaveAndVerify( - new ApeCoinPoolLogic__factory(await getFirstSigner()), - eContractid.ApeStakingApeCoinPoolLogic, - [], - verify - ) as Promise; + withSaveAndVerify( + new ApeCoinPoolLogic__factory(await getFirstSigner()), + eContractid.ApeStakingApeCoinPoolLogic, + [], + verify + ) as Promise; export const deployParaApeStakingLibraries = async ( verify?: boolean @@ -2726,7 +2728,7 @@ export const deployParaApeStakingLibraries = async ( ["contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol:ApeStakingSinglePoolLogic"]: singlePoolLogic.address, ["contracts/apestaking/logic/ApeCoinPoolLogic.sol:ApeCoinPoolLogic"]: - apeCoinPoolLogic.address, + apeCoinPoolLogic.address, }; }; diff --git a/helpers/types.ts b/helpers/types.ts index 563b8c95e..39708e377 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -423,8 +423,8 @@ export enum ProtocolErrors { NOT_PAIRED_APE_AND_BAKC = "174", //not paired ape and bakc NOT_APE_STAKING_BOT = "175", //not ape staking bot NOT_THE_SAME_OWNER = "176", //not the same owner - NFT_NOT_IN_SINGLE_POOL = "178", //nft not in single pool - + NFT_NOT_IN_POOL = "178", //nft not in single pool + SAPE_FREE_BALANCE_NOT_ENOUGH = "179", //sape free balance not enough NOT_ORDER_OFFERER = "180", //not order offerer ORDER_ALREADY_CANCELLED = "181", //order already cancelled ORDER_NOT_STARTED = "182", //order not started @@ -435,6 +435,7 @@ export enum ProtocolErrors { ORDER_TYPE_MATCH_FAILED = "187", //orders type match failed ORDER_SHARE_MATCH_FAILED = "188", //orders share match failed NO_BREAK_UP_PERMISSION = "189", //no permission to break up + INVALID_CASH_AMOUNT = "190", //invalid cash amount // SafeCast SAFECAST_UINT128_OVERFLOW = "SafeCast: value doesn't fit in 128 bits", diff --git a/scripts/upgrade/ntoken.ts b/scripts/upgrade/ntoken.ts index 1e1980dc4..8f0d39b85 100644 --- a/scripts/upgrade/ntoken.ts +++ b/scripts/upgrade/ntoken.ts @@ -105,11 +105,7 @@ export const upgradeNToken = async (verify = false) => { if (!nTokenBAKCImplementationAddress) { console.log("deploy NTokenBAKC implementation"); nTokenBAKCImplementationAddress = ( - await deployNTokenBAKCImpl( - pool.address, - delegationRegistry, - verify - ) + await deployNTokenBAKCImpl(pool.address, delegationRegistry, verify) ).address; } newImpl = nTokenBAKCImplementationAddress; diff --git a/test/_pool_ape_staking.spec.ts b/test/_pool_ape_staking.spec.ts deleted file mode 100644 index 0df9070b4..000000000 --- a/test/_pool_ape_staking.spec.ts +++ /dev/null @@ -1,3307 +0,0 @@ -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; -import {expect} from "chai"; -import {MAX_UINT_AMOUNT, ZERO_ADDRESS, ONE_ADDRESS} from "../helpers/constants"; -import { - getAutoCompoundApe, - getPoolConfiguratorProxy, - getPToken, - getPTokenSApe, - getTimeLockProxy, - getVariableDebtToken, -} from "../helpers/contracts-getters"; -import { - convertToCurrencyDecimals, - isUsingAsCollateral, -} from "../helpers/contracts-helpers"; -import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; -import {VariableDebtToken, PTokenSApe, PToken, AutoCompoundApe} from "../types"; -import {TestEnv} from "./helpers/make-suite"; -import {testEnvFixture} from "./helpers/setup-env"; - -import { - borrowAndValidate, - changePriceAndValidate, - changeSApePriceAndValidate, - mintAndValidate, - supplyAndValidate, -} from "./helpers/validated-steps"; -import {almostEqual} from "./helpers/uniswapv3-helper"; -import {eContractid, ProtocolErrors} from "../helpers/types"; -import {parseEther} from "ethers/lib/utils"; -import { - executeAcceptBidWithCredit, - executeSeaportBuyWithCredit, -} from "./helpers/marketplace-helper"; -import {BigNumber} from "ethers"; -import {deployReserveTimeLockStrategy} from "../helpers/contracts-deployments"; - -describe("APE Coin Staking Test", () => { - let testEnv: TestEnv; - let variableDebtApeCoin: VariableDebtToken; - let variableDebtCApeCoin: VariableDebtToken; - let pApeCoin: PToken; - let cApe: AutoCompoundApe; - let pcApeCoin: PToken; - let pSApeCoin: PTokenSApe; - const sApeAddress = ONE_ADDRESS; - const InitialNTokenApeBalance = parseEther("100"); - - const fixture = async () => { - testEnv = await loadFixture(testEnvFixture); - const { - ape, - mayc, - bayc, - users, - bakc, - protocolDataProvider, - pool, - apeCoinStaking, - nMAYC, - nBAYC, - } = testEnv; - const user1 = users[0]; - const depositor = users[1]; - const user4 = users[5]; - - const { - xTokenAddress: pApeCoinAddress, - variableDebtTokenAddress: variableDebtApeCoinAddress, - } = await protocolDataProvider.getReserveTokensAddresses(ape.address); - const {xTokenAddress: pSApeCoinAddress} = - await protocolDataProvider.getReserveTokensAddresses(sApeAddress); - - cApe = await getAutoCompoundApe(); - const { - xTokenAddress: pcApeCoinAddress, - variableDebtTokenAddress: variableDebtCApeCoinAddress, - } = await protocolDataProvider.getReserveTokensAddresses(cApe.address); - - variableDebtApeCoin = await getVariableDebtToken( - variableDebtApeCoinAddress - ); - variableDebtCApeCoin = await getVariableDebtToken( - variableDebtCApeCoinAddress - ); - pApeCoin = await getPToken(pApeCoinAddress); - pSApeCoin = await getPTokenSApe(pSApeCoinAddress); - pcApeCoin = await getPToken(pcApeCoinAddress); - - await supplyAndValidate(ape, "20000", depositor, true); - await changePriceAndValidate(ape, "0.001"); - await changePriceAndValidate(cApe, "0.001"); - await changeSApePriceAndValidate(sApeAddress, "0.001"); - - await changePriceAndValidate(mayc, "50"); - await changePriceAndValidate(bayc, "50"); - - await waitForTx(await bakc["mint(uint256,address)"]("2", user1.address)); - - await waitForTx( - await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await bakc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - - // send extra tokens to the apestaking contract for rewards - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"]( - apeCoinStaking.address, - parseEther("100000000000") - ) - ); - - // send extra tokens to the nToken contract for testing ape balance check - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](nMAYC.address, InitialNTokenApeBalance) - ); - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](nBAYC.address, InitialNTokenApeBalance) - ); - - await mintAndValidate(ape, "1", user4); - await waitForTx( - await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - // user4 deposit MINIMUM_LIQUIDITY to make test case easy - const MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); - await waitForTx( - await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) - ); - - return testEnv; - }; - - it("TC-pool-ape-staking-01 test borrowApeAndStake: failed when borrow + cash < staking amount (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "16000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "16000"); - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ).to.be.revertedWith(ProtocolErrors.TOTAL_STAKING_AMOUNT_WRONG); - }); - - it("TC-pool-ape-staking-02 test borrowApeAndStake: failed when borrow + cash > staking amount (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "16000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "16000"); - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ).to.be.revertedWith(ProtocolErrors.TOTAL_STAKING_AMOUNT_WRONG); - }); - - it("TC-pool-ape-staking-03 test borrowApeAndStake: use 100% cash", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(0); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - expect(userAccount.totalDebtBase).equal(0); - //50 * 0.325 + 15 * 0.2 = 19.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "19.25") - ); - }); - - it("TC-pool-ape-staking-04 test borrowApeAndStake: part cash, part debt", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - // 50 * 0.3250 + 7000 * 0.001 * 0.2 = 17.65 - // 17.65 / 0.001 = 17650 - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount2); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - //8000*0.001 = 8 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "8") - ); - //50 * 0.325 + 15 * 0.2 - 8=11.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "11.25") - ); - }); - - it("TC-pool-ape-staking-05 test borrowApeAndStake: use 100% debt", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - //15000*0.001 = 15 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "15") - ); - //50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "4.25") - ); - }); - - it("TC-pool-ape-staking-06 test withdrawBAKC fails when hf < 1 (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - let withdrawAmount = await convertToCurrencyDecimals(ape.address, "3000"); - expect( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: withdrawAmount}]) - ); - withdrawAmount = await convertToCurrencyDecimals(ape.address, "4000"); - expect( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: withdrawAmount}]) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount2); - - await expect( - pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ).to.be.revertedWith( - ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - }); - - it("TC-pool-ape-staking-07 test withdrawApeCoin fails when hf < 1 (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - nMAYC, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - const withdrawAmount = await convertToCurrencyDecimals(ape.address, "4000"); - expect( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: withdrawAmount, - isUncommit: false, - }, - ]) - ); - expect( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: withdrawAmount, - isUncommit: true, - }, - ]) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount1); - - await expect( - pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) - ).to.be.revertedWith( - ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - - const apeBalanceForNToken = await ape.balanceOf(nMAYC.address); - expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); - }); - - it("TC-pool-ape-staking-08 test withdrawBAKC fails when hf < 1 (revert expected)", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await expect( - pool - .connect(user2.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-09 test withdrawApeCoin fails when user is not the owner (revert expected)", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await expect( - pool - .connect(user2.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-10 test claimBAKC success when hf > 1", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - nMAYC, - apeCoinStaking, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}], - true - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - expect(pendingRewardsPool2).to.be.gt(0); - - const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( - 3, - nMAYC.address, - "1" - ); - expect(pendingRewardsPool3).to.be.gt(0); - - const userBalance = await ape.balanceOf(user1.address); - - expect( - await pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 1}]) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - //reward is not as collateral - expect(totalStake).equal(amount); - - expect(await ape.balanceOf(user1.address)).to.be.eq( - userBalance.add(pendingRewardsPool3) - ); - - const apeBalanceForNToken = await ape.balanceOf(nMAYC.address); - expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); - }); - - it("TC-pool-ape-staking-11 test claimBAKC success when hf < 1 (ape reward for bakc pool is not used as collateral)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - apeCoinStaking, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}], - true - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - expect(pendingRewardsPool2).to.be.gt(0); - - const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( - 3, - nMAYC.address, - "1" - ); - expect(pendingRewardsPool3).to.be.gt(0); - - const userBalance = await ape.balanceOf(user1.address); - - // drop HF to liquidation levels - await changePriceAndValidate(mayc, "3"); - - expect( - await pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 1}]) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - //reward is not as collateral - expect(totalStake).equal(amount); - - expect(await ape.balanceOf(user1.address)).to.be.eq( - userBalance.add(pendingRewardsPool3) - ); - }); - - it("TC-pool-ape-staking-12 test claimApeCoin succeeds when hf > 1", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - nMAYC, - apeCoinStaking, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - - const userBalance = await ape.balanceOf(user1.address); - - expect(await pool.connect(user1.signer).claimApeCoin(mayc.address, [0])); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - expect(await ape.balanceOf(user1.address)).to.be.eq( - userBalance.add(pendingRewardsPool2) - ); - }); - - it("TC-pool-ape-staking-13 test claimApeCoin should success when hf < 1", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - // drop HF to liquidation levels - await changePriceAndValidate(mayc, "3"); - - //ape coin reward is not used as collateral - expect(await pool.connect(user1.signer).claimApeCoin(mayc.address, [0])); - }); - - it("TC-pool-ape-staking-14 test unstakeApePositionAndRepay repays cape debt - no excess", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await mintAndValidate(ape, "20000", user2); - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("20000")) - ); - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, parseEther("20000"), user2.address, 0) - ); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(0); - - const pApeBalance = await pApeCoin.balanceOf(user1.address); - expect(pApeBalance).equal(0); - - const cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - const limit = await convertToCurrencyDecimals(cApe.address, "0.1"); - expect(cApeDebt.lt(limit)).equal(true); - }); - - it("TC-pool-ape-staking-15 test unstakeApePositionAndRepay repays cape debt", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user2); - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("10000")) - ); - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, parseEther("10000"), user2.address, 0) - ); - - const amount1 = parseEther("7000"); - const amount2 = parseEther("8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount1, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [], - true - ) - ); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: amount2, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - almostEqual(apeDebt, amount1); - - let capeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - almostEqual(capeDebt, amount2); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(0); - - const pCApeBalance = await pcApeCoin.balanceOf(user1.address); - almostEqual(pCApeBalance, amount1); - - capeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - expect(capeDebt).equal(0); - }); - - it("TC-pool-ape-staking-16 test unstakeApePositionAndRepay bakc reward should transfer to user wallet", async () => { - const { - users: [user1], - ape, - mayc, - pool, - apeCoinStaking, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsMaycPool = await apeCoinStaking.pendingRewards( - 2, - ZERO_ADDRESS, - "0" - ); - expect(pendingRewardsMaycPool).to.be.gt(0); - const pendingRewardsBakcPool = await apeCoinStaking.pendingRewards( - 3, - ZERO_ADDRESS, - "0" - ); - expect(pendingRewardsBakcPool).to.be.gt(0); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const userBalance = await ape.balanceOf(user1.address); - - expect(userBalance).to.be.eq(pendingRewardsBakcPool); - }); - - it("TC-pool-ape-staking-17 test unstakeApePositionAndRepay by others fails when hf > 1(revert expected)", async () => { - const { - users: [user1, unstaker], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await expect( - pool.connect(unstaker.signer).unstakeApePositionAndRepay(mayc.address, 0) - ).to.be.revertedWith(ProtocolErrors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD); - }); - - it("TC-pool-ape-staking-18 test unstakeApePositionAndRepay by others succeeds when hf < 1", async () => { - const { - users: [user1, unstaker, , , user2], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await mintAndValidate(ape, "20000", user2); - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("20000")) - ); - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, parseEther("20000"), user2.address, 0) - ); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(cApe, "0.08"); - - expect( - await pool - .connect(unstaker.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(0); - - const pcApeBalance = await pcApeCoin.balanceOf(user1.address); - expect(pcApeBalance).equal(0); - - const cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - const target = await convertToCurrencyDecimals(cApe.address, "45"); - almostEqual(cApeDebt, target); - }); - - it("TC-pool-ape-staking-19 test can stake multiple times and partially unstake afterwards", async () => { - const { - users: [user1, unstaker, , , user2], - ape, - mayc, - bayc, - pool, - nMAYC, - nBAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await mintAndValidate(ape, "30000", user2); - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("30000")) - ); - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, parseEther("30000"), user2.address, 0) - ); - - await supplyAndValidate(mayc, "2", user1, true); - await supplyAndValidate(bayc, "2", user1, true); - - const amount = await convertToCurrencyDecimals(cApe.address, "3000"); - const halfAmount = await convertToCurrencyDecimals(cApe.address, "9000"); - const totalAmount = await convertToCurrencyDecimals(cApe.address, "18000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: halfAmount, - cashAmount: 0, - }, - [ - {tokenId: 0, amount: amount}, - {tokenId: 1, amount: amount}, - ], - [{mainTokenId: 1, bakcTokenId: 0, amount: amount}], - true - ) - ); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: cApe.address, - borrowAmount: halfAmount, - cashAmount: 0, - }, - [ - {tokenId: 0, amount: amount}, - {tokenId: 1, amount: amount}, - ], - [{mainTokenId: 1, bakcTokenId: 1, amount: amount}], - true - ) - ); - - let maycStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(maycStake).equal(halfAmount); - - let baycStake = await nBAYC.getUserApeStakingAmount(user1.address); - expect(baycStake).equal(halfAmount); - - let pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(totalAmount); - - let cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - almostEqual(cApeDebt, totalAmount); - - let bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - let userAccount = await pool.getUserAccountData(user1.address); - //50 * 4 + 18000*0.001 = 218 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "218") - ); - //18000*0.001 = 18 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "18") - ); - //50 * 2 * 0.4 + 50 * 2 * 0.325 + 18 * 0.2 - 18 = 58.1 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "58.1") - ); - - await changePriceAndValidate(mayc, "10"); - await changePriceAndValidate(bayc, "10"); - await changePriceAndValidate(cApe, "0.01"); - await changeSApePriceAndValidate(sApeAddress, "0.01"); - - expect( - await pool - .connect(unstaker.signer) - .unstakeApePositionAndRepay(mayc.address, 1) - ); - - maycStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(maycStake).equal(amount); - - baycStake = await nBAYC.getUserApeStakingAmount(user1.address); - expect(baycStake).equal(halfAmount); - - pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount.add(halfAmount)); - - cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - //12000 + 6000*3/1000 - almostEqual( - cApeDebt, - amount - .add(halfAmount) - .add(await convertToCurrencyDecimals(weth.address, "18")) - ); - - bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - userAccount = await pool.getUserAccountData(user1.address); - //10 * 4 + 12000*0.01 = 160 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "160") - ); - //12018*0.01 = 120.18 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "120.18") - ); - - let apeBalanceForNToken = await ape.balanceOf(nMAYC.address); - expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); - apeBalanceForNToken = await ape.balanceOf(nBAYC.address); - expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); - }); - - it("TC-pool-ape-staking-20 test can liquidate NFT with existing staking positions", async () => { - const { - users: [user1, liquidator], - ape, - mayc, - pool, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8"); - const amount = await convertToCurrencyDecimals(ape.address, "7008"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const borrowAmount = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool - .connect(user1.signer) - .borrow(ape.address, borrowAmount, 0, user1.address) - ); - - await supplyAndValidate(weth, "91", liquidator, true, "200000"); - - // drop HF and ERC-721_HF below 1 - await changePriceAndValidate(mayc, "3"); - - // start auction - await waitForTx( - await pool - .connect(liquidator.signer) - .startAuction(user1.address, mayc.address, 0) - ); - - const apeDebtBefore = await variableDebtApeCoin.balanceOf(user1.address); - - // try to liquidate the NFT - expect( - await pool - .connect(liquidator.signer) - .liquidateERC721( - mayc.address, - user1.address, - 0, - await convertToCurrencyDecimals(weth.address, "13"), - false, - {gasLimit: 5000000} - ) - ); - - expect(await ape.balanceOf(user1.address)).to.be.eq(borrowAmount); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).to.be.eq(0); // whole position unstaked - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - almostEqual(apeDebt, apeDebtBefore); // no debt repaid - - expect(await bakc.ownerOf("0")).to.be.eq(user1.address); - expect(await mayc.ownerOf("0")).to.be.eq(liquidator.address); - }); - - it("TC-pool-ape-staking-21 test cannot borrow and stake an amount over user's available to borrow (revert expected)", async () => { - const { - users: [user1, depositor], - ape, - mayc, - pool, - weth, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(weth, "5", depositor, true); - await changePriceAndValidate(mayc, "10"); - await borrowAndValidate(weth, "3", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ).to.be.revertedWith(ProtocolErrors.COLLATERAL_CANNOT_COVER_NEW_BORROW); - }); - - it("TC-pool-ape-staking-22 test can transfer NFT with existing staking positions", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - expect(await nMAYC.balanceOf(user1.address)).to.be.equal(1); - expect(await nMAYC.balanceOf(user2.address)).to.be.equal(0); - expect(await pSApeCoin.balanceOf(user1.address)).equal(amount); - expect(await pSApeCoin.balanceOf(user2.address)).equal(0); - - expect( - await nMAYC - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user2.address, - 0, - {gasLimit: 5000000} - ) - ); - - expect(await nMAYC.balanceOf(user1.address)).to.be.equal(0); - expect(await nMAYC.balanceOf(user2.address)).to.be.equal(1); - expect(await pSApeCoin.balanceOf(user1.address)).equal(0); - expect(await pSApeCoin.balanceOf(user2.address)).equal(0); - }); - - it("TC-pool-ape-staking-23 test market accept bid offer should success", async () => { - const { - bayc, - nBAYC, - usdc, - pool, - ape, - users: [taker, maker, middleman], - } = await loadFixture(fixture); - const makerInitialBalance = "800"; - const middlemanInitialBalance = "200"; - const payNowAmount = await convertToCurrencyDecimals(usdc.address, "800"); - const creditAmount = await convertToCurrencyDecimals(usdc.address, "200"); - - const startAmount = payNowAmount.add(creditAmount); - const endAmount = startAmount; // fixed price but offerer cannot afford this - const nftId = 0; - - // 1, mint USDC to maker - await mintAndValidate(usdc, makerInitialBalance, maker); - - // 2, middleman supplies USDC to pool to be borrowed by maker later - await supplyAndValidate(usdc, middlemanInitialBalance, middleman, true); - - // 3, mint ntoken for taker - await mintAndValidate(ape, "15000", taker); - await supplyAndValidate(bayc, "1", taker, true); - - // 4, ape staking for ntoken - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(taker.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - expect(await nBAYC.balanceOf(taker.address)).to.be.equal(1); - expect(await nBAYC.balanceOf(maker.address)).to.be.equal(0); - expect(await pSApeCoin.balanceOf(taker.address)).equal(amount); - expect(await pSApeCoin.balanceOf(maker.address)).equal(0); - - // 5, accept order - await executeAcceptBidWithCredit( - nBAYC, - usdc, - startAmount, - endAmount, - creditAmount, - nftId, - maker, - taker - ); - - // taker bayc should reduce - expect(await nBAYC.balanceOf(taker.address)).to.be.equal(0); - expect(await nBAYC.balanceOf(maker.address)).to.be.equal(1); - expect(await pSApeCoin.balanceOf(taker.address)).equal(0); - expect(await pSApeCoin.balanceOf(maker.address)).equal(0); - }); - - it("TC-pool-ape-staking-24 test market buy with credit should success", async () => { - const { - bayc, - nBAYC, - usdc, - pool, - ape, - users: [maker, taker, middleman], - } = await loadFixture(fixture); - const makerInitialBalance = "800"; - const middlemanInitialBalance = "200"; - const payNowAmount = await convertToCurrencyDecimals(usdc.address, "800"); - const creditAmount = await convertToCurrencyDecimals(usdc.address, "200"); - - const startAmount = payNowAmount.add(creditAmount); - const endAmount = startAmount; // fixed price but offerer cannot afford this - const nftId = 0; - - // 1, mint USDC to taker - await mintAndValidate(usdc, makerInitialBalance, taker); - - // 2, middleman supplies USDC to pool to be borrowed by taker later - await supplyAndValidate(usdc, middlemanInitialBalance, middleman, true); - - // 3, mint ntoken for maker - await mintAndValidate(ape, "15000", maker); - await supplyAndValidate(bayc, "1", maker, true); - - // 4, ape staking for ntoken - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(maker.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - expect(await nBAYC.balanceOf(maker.address)).to.be.equal(1); - expect(await nBAYC.balanceOf(taker.address)).to.be.equal(0); - expect(await pSApeCoin.balanceOf(maker.address)).equal(amount); - expect(await pSApeCoin.balanceOf(taker.address)).equal(0); - - // 5, buy with credit - await waitForTx( - await usdc.connect(taker.signer).approve(pool.address, startAmount) - ); - await executeSeaportBuyWithCredit( - nBAYC, - usdc, - startAmount, - endAmount, - creditAmount, - nftId, - maker, - taker - ); - - // taker bayc should reduce - expect(await nBAYC.balanceOf(maker.address)).to.be.equal(0); - expect(await nBAYC.balanceOf(taker.address)).to.be.equal(1); - expect(await pSApeCoin.balanceOf(maker.address)).equal(0); - expect(await pSApeCoin.balanceOf(taker.address)).equal(0); - }); - - it("TC-pool-ape-staking-25 unstakeApePositionAndRepay should set cApe as collateral", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - const apeData = await pool.getReserveData(cApe.address); - await supplyAndValidate(ape, "1", user1, true); - await waitForTx( - await pool - .connect(user1.signer) - .setUserUseERC20AsCollateral(ape.address, false) - ); - let userConfig = BigNumber.from( - (await pool.getUserConfiguration(user1.address)).data - ); - expect(isUsingAsCollateral(userConfig, apeData.id)).to.be.false; - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - userConfig = BigNumber.from( - (await pool.getUserConfiguration(user1.address)).data - ); - expect(isUsingAsCollateral(userConfig, apeData.id)).to.be.true; - }); - - it("TC-pool-ape-staking-26 test borrowApeAndStake: User tries to staking on not Supplying (revert expected)", async () => { - const { - users: [user1], - ape, - bayc, - pool, - } = await loadFixture(fixture); - - await mintAndValidate(bayc, "1", user1); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-27 test borrowApeAndStake: User tries to staking 0 ape icon for BAYC (revert expected)", async () => { - const { - users: [user1], - ape, - bayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "0"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = amount1.add(amount2); - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ).to.be.revertedWith("DepositMoreThanOneAPE()"); - }); - - it("TC-pool-ape-staking-28 test borrowApeAndStake: only staking BAKC", async () => { - const { - users: [user1], - ape, - bayc, - nBAYC, - pool, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "0"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = amount1.add(amount2); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - - // User 1 - totalStake should increased in Stake amount - const totalStake = await nBAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - // User 1 - Debt should increased in borrowAmount - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount2); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 8000*0.001 = 58 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "58") - ); - //8000*0.001 = 8 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "8") - ); - - //50 * 0.4 + 8 * 0.2 - 8=13.6 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "13.6") - ); - }); - - it("TC-pool-ape-staking-29 test borrowApeAndStake: BAYC staked Add BAKC after first Pairing", async () => { - const { - users: [user1], - bayc, - ape, - pool, - weth, - nBAYC, - bakc, - } = await loadFixture(fixture); - await supplyAndValidate(bayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = amount1.add(amount2); - - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](user1.address, amount) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [], - true - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - // User 1 - totalStake should increased in Stake amount - const totalStake = await nBAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - // User 1 - Debt should increased in borrowAmount - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount2); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - //8000*0.001 = 8 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "8") - ); - //50 * 0.4 + 15 * 0.2 - 8=15 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "15") - ); - }); - - it("TC-pool-ape-staking-30 test borrowApeAndStake: MAYC staked Add BAKC after first Pairing", async () => { - const { - users: [user1], - mayc, - weth, - nMAYC, - ape, - pool, - bakc, - } = await loadFixture(fixture); - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = amount1.add(amount2); - - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](user1.address, amount) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [], - true - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - // User 1 - totalStake should increased in Stake amount - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - // User 1 - Debt should increased in borrowAmount - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount2); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - //8000*0.001 = 8 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "8") - ); - //50 * 0.325 + 15 * 0.2 - 8=11.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "11.25") - ); - }); - - it("TC-pool-ape-staking-31 test borrowApeAndStake: Insufficient liquidity of borrow ape (revert expected)", async () => { - const { - users: [user1], - bayc, - ape, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - - // reduce pool liquidity - await borrowAndValidate(ape, "13000", user1); - const amount1 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount = amount1.add(amount2); - - await ape - .connect(user1.signer) - ["mint(address,uint256)"](user1.address, amount); - - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: amount1, - cashAmount: amount2, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ).to.be.revertedWith("ERC20: transfer amount exceeds balance"); - }); - - it("TC-pool-ape-staking-32 test borrowApeAndStake: success use 100% cash when hf < 1", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - usdt, - nMAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(usdt, "1000", user2, true); - await borrowAndValidate(ape, "5000", user1); - await borrowAndValidate(usdt, "800", user1); - await mintAndValidate(ape, "7000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "7000"); - - await changePriceAndValidate(mayc, "20"); - await changePriceAndValidate(usdt, "0.0009"); - await changePriceAndValidate(ape, "0.005"); - await changeSApePriceAndValidate(sApeAddress, "0.005"); - - const healthFactor = (await pool.getUserAccountData(user1.address)) - .healthFactor; - - expect(healthFactor).to.be.lt(parseEther("1")); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - const healthFactorAfter = (await pool.getUserAccountData(user1.address)) - .healthFactor; - - // health factor should improve greater than 1 - expect(healthFactorAfter).to.be.gt(parseEther("1")); - - // User 1 - totalStake should increased in Stake amount - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - - //20 + 7000*0.005 = 55 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "55") - ); - - //5000*0.005 + 800 * 0.0009 = 25.72 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "25.72") - ); - - //availableBorrowsInBaseCurrency < totalDebtInBaseCurrency = 0 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "0") - ); - }); - - it("TC-pool-ape-staking-33 test safeTransferFrom BAKC: original owner withdraws all", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - const userBalance = await ape.balanceOf(user1.address); - const user3Balance = await ape.balanceOf(user3.address); - - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - "0" - ) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(1); - - const bakcBalanceUser3 = await bakc.balanceOf(user3.address); - expect(bakcBalanceUser3).equal(1); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ); - - // User 1 - totalStake should have decreased in BAKC amount - const totalStakeAfter = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStakeAfter).equal(totalStake.sub(amount2)); - - // User 1 - totalStake should have increased in BAKC amount - const pSApeBalanceAfter = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalanceAfter).equal(pSApeBalance.sub(amount2)); - - // User 1 - Ape Balance should have increased in BAKC amount - const userBalanceAfter = await ape.balanceOf(user1.address); - expect(userBalanceAfter).equal(userBalance.add(amount2)); - - // User 3 - Ape Balance should remain the same - const user3BalanceAfter = await ape.balanceOf(user3.address); - expect(user3BalanceAfter).equal(user3Balance); - }); - - it("TC-pool-ape-staking-34 test safeTransferFrom BAKC: original owner withdraws part ape (revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - "0" - ) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(1); - - const bakcBalanceUser3 = await bakc.balanceOf(user3.address); - expect(bakcBalanceUser3).equal(1); - - // Only withdraw all - await expect( - pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount1, isUncommit: false}, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_BAKC_OWNER); - }); - - it("TC-pool-ape-staking-35 test safeTransferFrom BAKC: original owner claim bakc reward (revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - "0" - ) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(1); - - const bakcBalanceUser3 = await bakc.balanceOf(user3.address); - expect(bakcBalanceUser3).equal(1); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - await expect( - pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 0}]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_BAKC_OWNER); - }); - - it("TC-pool-ape-staking-36 test safeTransferFrom BAKC: new owner withdraw all (revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - "0" - ) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(1); - - const bakcBalanceUser3 = await bakc.balanceOf(user3.address); - expect(bakcBalanceUser3).equal(1); - - // New owner - await expect( - pool - .connect(user3.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-37 test safeTransferFrom: transfer fails when hf < 1 (revert expected)", async () => { - const { - users: [user1, user2, user3], - ape, - mayc, - nMAYC, - pool, - usdt, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(usdt, "1000", user2, true); - await borrowAndValidate(ape, "5000", user1); - await borrowAndValidate(usdt, "800", user1); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "100"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - await changePriceAndValidate(mayc, "0.001"); - await changePriceAndValidate(ape, "0.1"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - - await expect( - nMAYC - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - 0, - {gasLimit: 5000000} - ) - ).to.be.revertedWith( - ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - }); - - it("TC-pool-ape-staking-38 test withdrawBAKC success when hf > 1 after withdrawBAKC", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - bakc, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - const healthFactor = (await pool.getUserAccountData(user1.address)) - .healthFactor; - - expect(healthFactor.gt(parseEther("1"))).to.be.true; - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in Stake amount - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const withdrawAmount = await convertToCurrencyDecimals(ape.address, "8000"); - await waitForTx( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: withdrawAmount, - isUncommit: true, - }, - ]) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - // User 1 - bakc balanace should increased 2 - expect(bakcBalance).equal(2); - // User1 - ape balance should increased amount2 - expect(await ape.balanceOf(user1.address)).eq(amount2); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User1 - total stake should increased amount1 - expect(totalStake).equal(amount1); - }); - - it("TC-pool-ape-staking-39 test withdrawApeCoin success when hf > 1 after withdrawApeCoin", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - // supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // borrow and stake 15000 - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - const healthFactor = (await pool.getUserAccountData(user1.address)) - .healthFactor; - - expect(healthFactor.gt(parseEther("1"))).to.be.true; - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in Stake amount - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) - ); - const apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).equal(amount1); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in amount2 - expect(totalStake).equal(amount2); - }); - - it("TC-pool-ape-staking-40 test withdrawBAKC fails when sender is not NFT owner (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in Stake amount - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await expect( - pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 1, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-41 test withdrawBAKC fails when amount != total staking, the sender is the NFT owner, but the sender is not the BAKC owner(revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - bakc, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in Stake amount - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await waitForTx( - await bakc - .connect(user1.signer) - .transferFrom(user1.address, user3.address, 0) - ); - // User 3 - The NFT owner with bakc id 0 should be changed to user3 - expect(await bakc.balanceOf(user3.address)).equal(1); - expect(await bakc.ownerOf(0)).eq(user3.address); - - const withdrawAmount = await convertToCurrencyDecimals(ape.address, "6000"); - - await expect( - pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: withdrawAmount, - isUncommit: false, - }, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_BAKC_OWNER); - }); - - it("TC-pool-ape-staking-42 test withdrawBAKC success when withdraw amount == bakc staking amount, it will automatically claim and transfer the reward to the BACK owner", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - apeCoinStaking, - bakc, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - // User 1 - totalStake should increased in Stake amount - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await advanceTimeAndBlock(parseInt("86400")); - - // bayc rewards - // const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - // 2, - // nMAYC.address, - // "0" - // ); - // bakc rewards - const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( - 3, - nMAYC.address, - "0" - ); - - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - 0 - ) - ); - // User 3 - The NFT owner with bakc id 0 should be changed to user3 - expect(await bakc.ownerOf(0)).eq(user3.address); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ); - - // User1 - ape balance should increased amount2 - expect(await ape.balanceOf(user1.address)).eq(amount2); - // User 3 - ape balance should increased pendingRewardsPool3 - expect(await ape.balanceOf(user3.address)).eq(pendingRewardsPool3); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User1 - total stake should increased amount1 - expect(totalStake).equal(amount1); - }); - - it("TC-pool-ape-staking-43 test withdrawBAKC success when withdraw amount == bakc staking amount, and the sender is not the BAKC owner, it will automatically claim and transfer the reward to the BACK owner", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - apeCoinStaking, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - // User 1 - totalStake should increased in Stake amount - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await advanceTimeAndBlock(parseInt("86400")); - - // bayc rewards - // const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - // 2, - // nMAYC.address, - // "0" - // ); - // bakc rewards - const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( - 3, - nMAYC.address, - "0" - ); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ); - - // User1 - ape balance should increased amount2 + pendingRewardsPool3 - expect(await ape.balanceOf(user1.address)).eq( - amount2.add(pendingRewardsPool3) - ); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User1 - total stake should increased amount1 - expect(totalStake).equal(amount1); - }); - - it("TC-pool-ape-staking-44 test withdrawApeCoin fails when the sender is not the NFT owner(revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - // supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "1000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "1000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount}], - [], - true - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 1000*0.001 = 51 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "51") - ); - // User1 - debt amount should increased 0 - almostEqual(userAccount.totalDebtBase, 0); - // User1 - available borrow should increased amount * baseLTVasCollateral = 50 * 0.325 + 1 * 0.2=16.45 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "16.45") - ); - // User 1 - totalStake should increased in Stake amount - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await waitForTx( - await nMAYC - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - 0 - ) - ); - expect(await nMAYC.balanceOf(user3.address)).eq(1); - expect(await nMAYC.ownerOf(0)).eq(user3.address); - - await expect( - pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 1, amount: amount}]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-45 test withdrawApeCoin success when withdraw amount == NFT staking amount, it will automatically claim and transfer the reward to the user account", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - apeCoinStaking, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - // User 1 - totalStake should increased in Stake amount - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - await waitForTx( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) - ); - - // User1 - ape balance should increased amount1 + pendingRewardsPool2 - expect(await ape.balanceOf(user1.address)).to.be.eq( - amount1.add(pendingRewardsPool2) - ); - // User1 - total stake should increased amount2 - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount2); - }); - - it("TC-pool-ape-staking-46 test borrowApeAndStake will turn on the sAPE collateral", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user3, true); - // transfer mayc#0 to user1 who hasn't collateralized sAPE - await waitForTx( - await nMAYC - .connect(user3.signer) - ["safeTransferFrom(address,address,uint256)"]( - user3.address, - user1.address, - "0" - ) - ); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - const sApeReserveData = await pool.getReserveData(sApeAddress); - const configDataBefore = (await pool.getUserConfiguration(user1.address)) - .data; - expect(isUsingAsCollateral(configDataBefore, sApeReserveData.id)).false; - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - true - ) - ); - - const configDataAfter = (await pool.getUserConfiguration(user1.address)) - .data; - expect(isUsingAsCollateral(configDataAfter, sApeReserveData.id)).true; - }); - - it("TC-pool-ape-staking-47 test mayc withdrawApeCoin should transfer ape coin to timelock contract", async () => { - const { - users: [user1], - ape, - mayc, - pool, - poolAdmin, - } = await loadFixture(fixture); - - //setup timelock strategy - const minThreshold = await convertToCurrencyDecimals(ape.address, "100000"); - const midThreshold = await convertToCurrencyDecimals(ape.address, "200000"); - const minTime = 5; - const midTime = 300; - const maxTime = 3600; - - const timeLockProxy = await getTimeLockProxy(); - const defaultStrategy = await deployReserveTimeLockStrategy( - eContractid.DefaultTimeLockStrategy + "ERC20", - pool.address, - minThreshold.toString(), - midThreshold.toString(), - minTime.toString(), - midTime.toString(), - maxTime.toString(), - midThreshold.mul(10).toString(), - (12 * 3600).toString(), - (24 * 3600).toString() - ); - const poolConfigurator = await getPoolConfiguratorProxy(); - await waitForTx( - await poolConfigurator - .connect(poolAdmin.signer) - .setReserveTimeLockStrategyAddress(ape.address, defaultStrategy.address) - ); - - await supplyAndValidate(mayc, "1", user1, true); - const amount = parseEther("10000"); - const totalAmount = parseEther("20000"); - await mintAndValidate(ape, "20000", user1); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: totalAmount, - }, - [{tokenId: 0, amount: amount}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount}]) - ); - expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(amount); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount, isUncommit: true}, - ]) - ); - expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(totalAmount); - - await advanceTimeAndBlock(10); - - await waitForTx(await timeLockProxy.connect(user1.signer).claim(["0"])); - expect(await ape.balanceOf(user1.address)).to.be.eq(amount); - - await waitForTx(await timeLockProxy.connect(user1.signer).claim(["1"])); - expect(await ape.balanceOf(user1.address)).to.be.eq(totalAmount); - expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(0); - }); - - it("TC-pool-ape-staking-48 test bayc withdrawApeCoin should transfer ape coin to timelock contract", async () => { - const { - users: [user1], - ape, - bayc, - pool, - poolAdmin, - } = await loadFixture(fixture); - - //setup timelock strategy - const minThreshold = await convertToCurrencyDecimals(ape.address, "100000"); - const midThreshold = await convertToCurrencyDecimals(ape.address, "200000"); - const minTime = 5; - const midTime = 300; - const maxTime = 3600; - - const timeLockProxy = await getTimeLockProxy(); - const defaultStrategy = await deployReserveTimeLockStrategy( - eContractid.DefaultTimeLockStrategy + "ERC20", - pool.address, - minThreshold.toString(), - midThreshold.toString(), - minTime.toString(), - midTime.toString(), - maxTime.toString(), - midThreshold.mul(10).toString(), - (12 * 3600).toString(), - (24 * 3600).toString() - ); - const poolConfigurator = await getPoolConfiguratorProxy(); - await waitForTx( - await poolConfigurator - .connect(poolAdmin.signer) - .setReserveTimeLockStrategyAddress(ape.address, defaultStrategy.address) - ); - - await supplyAndValidate(bayc, "1", user1, true); - const amount = parseEther("10000"); - const totalAmount = parseEther("20000"); - await mintAndValidate(ape, "20000", user1); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: totalAmount, - }, - [{tokenId: 0, amount: amount}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawApeCoin(bayc.address, [{tokenId: 0, amount: amount}]) - ); - expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(amount); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawBAKC(bayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount, isUncommit: true}, - ]) - ); - expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(totalAmount); - - await advanceTimeAndBlock(10); - - await waitForTx(await timeLockProxy.connect(user1.signer).claim(["0"])); - expect(await ape.balanceOf(user1.address)).to.be.eq(amount); - - await waitForTx(await timeLockProxy.connect(user1.signer).claim(["1"])); - expect(await ape.balanceOf(user1.address)).to.be.eq(totalAmount); - expect(await ape.balanceOf(timeLockProxy.address)).to.be.eq(0); - }); - - it("TC-pool-ape-staking-49 test borrowApeAndStake with 100% debt failed if don't use sape as collateral", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - - await changePriceAndValidate(ape, "0.003"); - - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - false - ) - ).to.be.revertedWith(ProtocolErrors.COLLATERAL_CANNOT_COVER_NEW_BORROW); - - await changePriceAndValidate(ape, "0.0001"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - false - ) - ); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const userConfig = BigNumber.from( - (await pool.getUserConfiguration(user1.address)).data - ); - const apeData = await pool.getReserveData(ape.address); - expect(isUsingAsCollateral(userConfig, apeData.id)).to.be.false; - }); - - it("TC-pool-ape-staking-50 test withdrawApeCoin should success when hf < 1 and sApe is not used as collateral", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - await changePriceAndValidate(mayc, "100"); - await changePriceAndValidate(ape, "0.001"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}], - false - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - - const healthFactor = (await pool.getUserAccountData(user1.address)) - .healthFactor; - expect(healthFactor.lt(parseEther("1"))).to.be.true; - - expect( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) - ); - - expect( - await pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ); - }); -}); diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 31ba0e243..56b301b7c 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -922,7 +922,7 @@ describe("Para Ape Staking Test", () => { await expect( paraApeStaking.connect(user4.signer).stakingApe(true, [2]) - ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( await paraApeStaking.connect(user1.signer).stakingApe(true, [0, 1]) @@ -952,11 +952,11 @@ describe("Para Ape Staking Test", () => { await expect( paraApeStaking.connect(user4.signer).stakingBAKC(true, [2], [0]) - ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await expect( paraApeStaking.connect(user4.signer).stakingBAKC(true, [0], [2]) - ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( await paraApeStaking @@ -987,7 +987,7 @@ describe("Para Ape Staking Test", () => { await expect( paraApeStaking.connect(user4.signer).compoundApe(true, [2]) - ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( await paraApeStaking.connect(user4.signer).compoundApe(true, [0, 1]) @@ -1029,7 +1029,7 @@ describe("Para Ape Staking Test", () => { await expect( paraApeStaking.connect(user4.signer).compoundBAKC(true, [1], [2]) - ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( await paraApeStaking @@ -1100,7 +1100,7 @@ describe("Para Ape Staking Test", () => { await expect( paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [2]) - ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await expect( paraApeStaking.connect(user1.signer).claimNFT(bakc.address, [0, 1]) @@ -1108,7 +1108,7 @@ describe("Para Ape Staking Test", () => { await expect( paraApeStaking.connect(user1.signer).claimNFT(bakc.address, [2]) - ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_SINGLE_POOL); + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( await paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [0]) diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts new file mode 100644 index 000000000..6bbecbd5b --- /dev/null +++ b/test/para_pool_ape_staking.spec.ts @@ -0,0 +1,1217 @@ +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect} from "chai"; +import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; +import { + getAutoCompoundApe, + getParaApeStaking, + getPTokenSApe, +} from "../helpers/contracts-getters"; +import { + advanceBlock, + advanceTimeAndBlock, + waitForTx, +} from "../helpers/misc-utils"; +import {PTokenSApe, AutoCompoundApe, ParaApeStaking} from "../types"; +import {TestEnv} from "./helpers/make-suite"; +import {testEnvFixture} from "./helpers/setup-env"; + +import { + changePriceAndValidate, + changeSApePriceAndValidate, + mintAndValidate, + supplyAndValidate, +} from "./helpers/validated-steps"; +import {ProtocolErrors} from "../helpers/types"; +import {parseEther} from "ethers/lib/utils"; +import {BigNumber} from "ethers"; + +describe("Para Ape staking ape coin pool test", () => { + let testEnv: TestEnv; + let paraApeStaking: ParaApeStaking; + let cApe: AutoCompoundApe; + let MINIMUM_LIQUIDITY; + let pSApeCoin: PTokenSApe; + const sApeAddress = ONE_ADDRESS; + + const fixture = async () => { + testEnv = await loadFixture(testEnvFixture); + const { + ape, + users: [user1, user2, user3, user4, , user6], + apeCoinStaking, + pool, + protocolDataProvider, + configurator, + poolAdmin, + } = testEnv; + + paraApeStaking = await getParaApeStaking(); + + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .setApeStakingBot(user4.address) + ); + + cApe = await getAutoCompoundApe(); + MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + + const {xTokenAddress: pSApeCoinAddress} = + await protocolDataProvider.getReserveTokensAddresses(sApeAddress); + pSApeCoin = await getPTokenSApe(pSApeCoinAddress); + + // send extra tokens to the apestaking contract for rewards + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"]( + apeCoinStaking.address, + parseEther("100000000000") + ) + ); + + // user6 deposit MINIMUM_LIQUIDITY to make test case easy + await mintAndValidate(ape, "1", user6); + await waitForTx( + await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) + ); + + // user4 deposit and supply cApe to MM + expect( + await configurator + .connect(poolAdmin.signer) + .setSupplyCap(cApe.address, "20000000000") + ); + await mintAndValidate(ape, "10000000000", user4); + await waitForTx( + await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user4.signer) + .deposit(user4.address, parseEther("10000000000")) + ); + await waitForTx( + await cApe.connect(user4.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await pool + .connect(user4.signer) + .supply(cApe.address, parseEther("10000000000"), user4.address, 0) + ); + + // user approve ape coin to Para ape staking + await waitForTx( + await ape + .connect(user1.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await ape + .connect(user2.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await ape + .connect(user3.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + + return testEnv; + }; + + it("test BAYC + ApeCoin pool logic", async () => { + const { + users: [user1, user2, , user4], + ape, + bayc, + bakc, + nBAYC, + nBAKC, + poolAdmin, + apeCoinStaking, + } = await loadFixture(fixture); + + //mint ape + await mintAndValidate(ape, "1000000", user1); + await mintAndValidate(ape, "1000000", user2); + + await waitForTx( + await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(1000) + ); + + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("400000"), + isBAYC: true, + tokenIds: [0, 1], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [2], + }) + ); + expect(await bayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await bayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await bayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: true, + apeTokenIds: [0, 1], + bakcTokenIds: [0, 1], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [2], + bakcTokenIds: [2], + }) + ); + expect(await bakc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); + + expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(1, 1)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(1, 2)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq( + parseEther("50000") + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundApeCoinPool(true, [0, 1, 2]) + ); + let compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("360"), parseEther("1")); + + let user1PendingReward = await paraApeStaking.apeCoinPoolPendingReward( + true, + [0, 1] + ); + let user2PendingReward = await paraApeStaking.apeCoinPoolPendingReward( + true, + [2] + ); + expect(user1PendingReward).to.be.closeTo( + parseEther("2160"), + parseEther("1") + ); + expect(user2PendingReward).to.be.closeTo( + parseEther("1080"), + parseEther("1") + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimApeCoinPool(true, [0, 1]) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).claimApeCoinPool(true, [2]) + ); + let user1Balance = await cApe.balanceOf(user1.address); + let user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); + expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundApeCoinPairPool(true, [0, 1, 2], [0, 1, 2]) + ); + compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("1")); + + user1PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( + true, + [0, 1] + ); + user2PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( + true, + [2] + ); + expect(user1PendingReward).to.be.closeTo( + parseEther("2160"), + parseEther("1") + ); + expect(user2PendingReward).to.be.closeTo( + parseEther("1080"), + parseEther("1") + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimApeCoinPairPool(true, [0, 1]) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).claimApeCoinPairPool(true, [2]) + ); + user1Balance = await cApe.balanceOf(user1.address); + user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo( + user1PendingReward.mul(2), + parseEther("1") + ); + expect(user2Balance).to.be.closeTo( + user2PendingReward.mul(2), + parseEther("1") + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("400000"), + isBAYC: true, + tokenIds: [0, 1], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).withdrawApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [2], + }) + ); + expect(await bayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await bayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await bayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: true, + apeTokenIds: [0, 1], + bakcTokenIds: [0, 1], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).withdrawApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [2], + bakcTokenIds: [2], + }) + ); + expect(await bayc.ownerOf(0)).to.be.equal(nBAYC.address); + expect(await bayc.ownerOf(1)).to.be.equal(nBAYC.address); + expect(await bayc.ownerOf(2)).to.be.equal(nBAYC.address); + expect(await bakc.ownerOf(0)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + + compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + //720 + 720 + 3600 * 0.9 / 3 * 2 + expect(compoundFee).to.be.closeTo(parseEther("3600"), parseEther("1")); + await waitForTx( + await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) + ); + const compoundFeeBalance = await cApe.balanceOf(user4.address); + expect(compoundFeeBalance).to.be.closeTo(compoundFee, parseEther("1")); + + user1Balance = await cApe.balanceOf(user1.address); + user2Balance = await cApe.balanceOf(user2.address); + //2160 * 2 + 0 + expect(user1Balance).to.be.closeTo(parseEther("4320"), parseEther("1")); + //1080 * 2 + 2160 * 2 + expect(user2Balance).to.be.closeTo(parseEther("6480"), parseEther("1")); + + expect(await ape.balanceOf(user1.address)).to.be.equal( + parseEther("1000000") + ); + expect(await ape.balanceOf(user2.address)).to.be.equal( + parseEther("1000000") + ); + }); + + it("test MAYC + ApeCoin pool logic", async () => { + const { + users: [user1, user2, , user4], + ape, + mayc, + bakc, + nMAYC, + nBAKC, + poolAdmin, + apeCoinStaking, + } = await loadFixture(fixture); + + //mint ape + await mintAndValidate(ape, "1000000", user1); + await mintAndValidate(ape, "1000000", user2); + + await waitForTx( + await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(1000) + ); + + await supplyAndValidate(mayc, "3", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await nMAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: false, + tokenIds: [0, 1], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + tokenIds: [2], + }) + ); + expect(await mayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + apeTokenIds: [0, 1], + bakcTokenIds: [0, 1], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: false, + apeTokenIds: [2], + bakcTokenIds: [2], + }) + ); + expect(await bakc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await bakc.ownerOf(2)).to.be.equal(paraApeStaking.address); + + expect((await apeCoinStaking.nftPosition(2, 0)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(2, 1)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(2, 2)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq( + parseEther("50000") + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundApeCoinPool(false, [0, 1, 2]) + ); + let compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("360"), parseEther("1")); + + let user1PendingReward = await paraApeStaking.apeCoinPoolPendingReward( + false, + [0, 1] + ); + let user2PendingReward = await paraApeStaking.apeCoinPoolPendingReward( + false, + [2] + ); + expect(user1PendingReward).to.be.closeTo( + parseEther("2160"), + parseEther("1") + ); + expect(user2PendingReward).to.be.closeTo( + parseEther("1080"), + parseEther("1") + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimApeCoinPool(false, [0, 1]) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).claimApeCoinPool(false, [2]) + ); + let user1Balance = await cApe.balanceOf(user1.address); + let user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); + expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); + + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundApeCoinPairPool(false, [0, 1, 2], [0, 1, 2]) + ); + compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("1")); + + user1PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( + false, + [0, 1] + ); + user2PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( + false, + [2] + ); + expect(user1PendingReward).to.be.closeTo( + parseEther("2160"), + parseEther("1") + ); + expect(user2PendingReward).to.be.closeTo( + parseEther("1080"), + parseEther("1") + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimApeCoinPairPool(false, [0, 1]) + ); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .claimApeCoinPairPool(false, [2]) + ); + user1Balance = await cApe.balanceOf(user1.address); + user2Balance = await cApe.balanceOf(user2.address); + expect(user1Balance).to.be.closeTo( + user1PendingReward.mul(2), + parseEther("1") + ); + expect(user2Balance).to.be.closeTo( + user2PendingReward.mul(2), + parseEther("1") + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: false, + tokenIds: [0, 1], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).withdrawApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + tokenIds: [2], + }) + ); + expect(await mayc.ownerOf(0)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(1)).to.be.equal(paraApeStaking.address); + expect(await mayc.ownerOf(2)).to.be.equal(paraApeStaking.address); + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + apeTokenIds: [0, 1], + bakcTokenIds: [0, 1], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).withdrawApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: false, + apeTokenIds: [2], + bakcTokenIds: [2], + }) + ); + expect(await mayc.ownerOf(0)).to.be.equal(nMAYC.address); + expect(await mayc.ownerOf(1)).to.be.equal(nMAYC.address); + expect(await mayc.ownerOf(2)).to.be.equal(nMAYC.address); + expect(await bakc.ownerOf(0)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(1)).to.be.equal(nBAKC.address); + expect(await bakc.ownerOf(2)).to.be.equal(nBAKC.address); + + compoundFee = await paraApeStaking.pendingCApeReward( + paraApeStaking.address + ); + //720 + 720 + 3600 * 0.9 / 3 * 2 + expect(compoundFee).to.be.closeTo(parseEther("3600"), parseEther("1")); + await waitForTx( + await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) + ); + const compoundFeeBalance = await cApe.balanceOf(user4.address); + expect(compoundFeeBalance).to.be.closeTo(compoundFee, parseEther("1")); + + user1Balance = await cApe.balanceOf(user1.address); + user2Balance = await cApe.balanceOf(user2.address); + //2160 * 2 + 0 + expect(user1Balance).to.be.closeTo(parseEther("4320"), parseEther("1")); + //1080 * 2 + 2160 * 2 + expect(user2Balance).to.be.closeTo(parseEther("6480"), parseEther("1")); + + expect(await ape.balanceOf(user1.address)).to.be.equal( + parseEther("1000000") + ); + expect(await ape.balanceOf(user2.address)).to.be.equal( + parseEther("1000000") + ); + }); + + it("sApe test", async () => { + const { + users: [user1, user2, liquidator], + ape, + weth, + bayc, + mayc, + bakc, + pool, + nBAYC, + nMAYC, + } = await loadFixture(fixture); + + await changePriceAndValidate(bayc, "100"); + await changePriceAndValidate(mayc, "50"); + await changePriceAndValidate(bakc, "25"); + await changePriceAndValidate(ape, "0.00001"); + + //user1 collateral 200eth + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(bakc, "2", user1, true); + + await supplyAndValidate(ape, "2000000", user2, true); + + //user1 borrow value 0.00001 * 1000000 = 10eth + await waitForTx( + await pool + .connect(user1.signer) + .borrow(ape.address, parseEther("1000000"), 0, user1.address) + ); + expect(await ape.balanceOf(user1.address)).to.be.equal( + parseEther("1000000") + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + tokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: false, + apeTokenIds: [0], + bakcTokenIds: [1], + }) + ); + expect(await ape.balanceOf(user1.address)).to.be.equal( + parseEther("600000") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.equal( + parseEther("400000") + ); + expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.equal( + parseEther("400000") + ); + expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.equal( + parseEther("0") + ); + + //user1 borrow value = 200 eth + await changePriceAndValidate(ape, "0.0002"); + await changeSApePriceAndValidate(sApeAddress, "0.0002"); + + await mintAndValidate(weth, "200", liquidator); + await waitForTx( + await weth + .connect(liquidator.signer) + .approve(pool.address, MAX_UINT_AMOUNT) + ); + + // start auction + await waitForTx( + await pool + .connect(liquidator.signer) + .startAuction(user1.address, bayc.address, 0) + ); + let auctionData = await pool.getAuctionData(nBAYC.address, 0); + await advanceBlock( + auctionData.startTime + .add(auctionData.tickLength.mul(BigNumber.from(40))) + .toNumber() + ); + + // try to liquidate the NFT + expect( + await pool + .connect(liquidator.signer) + .liquidateERC721( + bayc.address, + user1.address, + 0, + parseEther("100"), + false, + {gasLimit: 5000000} + ) + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("400000"), + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.closeTo( + parseEther("150000"), + parseEther("1") + ); + expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.closeTo( + parseEther("250000"), + parseEther("1") + ); + + await waitForTx( + await pool + .connect(liquidator.signer) + .startAuction(user1.address, mayc.address, 0) + ); + auctionData = await pool.getAuctionData(nMAYC.address, 0); + await advanceBlock( + auctionData.startTime + .add(auctionData.tickLength.mul(BigNumber.from(40))) + .toNumber() + ); + + expect( + await pool + .connect(liquidator.signer) + .liquidateERC721( + mayc.address, + user1.address, + 0, + parseEther("50"), + false, + {gasLimit: 5000000} + ) + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("400000"), + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.closeTo( + parseEther("0"), + parseEther("1") + ); + expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.closeTo( + parseEther("400000"), + parseEther("1") + ); + + const accountData0 = await pool.getUserAccountData(user1.address); + await waitForTx( + await pool + .connect(user1.signer) + .setUserUseERC20AsCollateral(sApeAddress, true) + ); + const accountData1 = await pool.getUserAccountData(user1.address); + //400000 * 0.0002 = 80 + expect( + accountData1.totalCollateralBase.sub(accountData0.totalCollateralBase) + ).to.be.closeTo(parseEther("80"), parseEther("1")); + + await changePriceAndValidate(ape, "0.0004"); + await changeSApePriceAndValidate(sApeAddress, "0.0004"); + + //liquidate sApe + await mintAndValidate(ape, "1000000", liquidator); + await waitForTx( + await ape + .connect(liquidator.signer) + .approve(pool.address, MAX_UINT_AMOUNT) + ); + + await waitForTx( + await pool + .connect(liquidator.signer) + .liquidateERC20( + sApeAddress, + ape.address, + user1.address, + parseEther("400000"), + true + ) + ); + const user1Balance = await pSApeCoin.balanceOf(user1.address); + const liquidatorBalance = await pSApeCoin.balanceOf(liquidator.address); + expect(user1Balance).to.be.closeTo("0", parseEther("1")); + expect(liquidatorBalance).to.be.closeTo( + parseEther("400000"), + parseEther("1") + ); + }); + + it("depositApeCoinPool revert test", async () => { + const { + users: [user1, user2], + ape, + bayc, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "2000000", user1); + await supplyAndValidate(bayc, "1", user1, true); + + await expect( + paraApeStaking.connect(user2.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: true, + tokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.SAPE_FREE_BALANCE_NOT_ENOUGH); + }); + + it("compoundApeCoinPool revert test", async () => { + const { + users: [user1, , , user4], + ape, + bayc, + bakc, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "2000000", user1); + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) + ); + + await expect( + paraApeStaking.connect(user4.signer).compoundApeCoinPool(true, [0]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); + }); + + it("claimApeCoinPool revert test", async () => { + const { + users: [user1, user2, , user4], + ape, + bayc, + bakc, + nBAYC, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "2000000", user1); + await mintAndValidate(ape, "2000000", user2); + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [1], + }) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [2], + bakcTokenIds: [0], + }) + ); + + await advanceTimeAndBlock(parseInt("3600")); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundApeCoinPool(true, [0, 1]) + ); + + await expect( + paraApeStaking.connect(user1.signer).claimApeCoinPool(true, [0, 1]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).claimApeCoinPool(true, [2]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); + }); + + it("withdrawApeCoinPool revert test", async () => { + const { + users: [user1, user2], + ape, + bayc, + bakc, + nBAYC, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "2000000", user1); + await mintAndValidate(ape, "2000000", user2); + await supplyAndValidate(bayc, "2", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [1], + }) + ); + + await expect( + paraApeStaking.connect(user1.signer).withdrawApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [1], + }) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).withdrawApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("300000"), + isBAYC: true, + tokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.INVALID_CASH_AMOUNT); + }); + + it("depositApeCoinPairPool revert test", async () => { + const { + users: [user1, user2], + ape, + bayc, + bakc, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "2000000", user1); + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await expect( + paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("1"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.SAPE_FREE_BALANCE_NOT_ENOUGH); + }); + + it("compoundApeCoinPairPool revert test", async () => { + const { + users: [user1, , , user4], + ape, + bayc, + bakc, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "2000000", user1); + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ); + + await expect( + paraApeStaking + .connect(user4.signer) + .compoundApeCoinPairPool(true, [0], [0]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); + }); + + it("claimApeCoinPairPool revert test", async () => { + const { + users: [user1, user2, , user4], + ape, + bayc, + bakc, + nBAYC, + nBAKC, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "2000000", user1); + await mintAndValidate(ape, "2000000", user2); + await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bakc, "2", user1, true); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [1], + bakcTokenIds: [1], + }) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [2], + }) + ); + + await advanceTimeAndBlock(parseInt("3600")); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundApeCoinPairPool(true, [0, 1], [0, 1]) + ); + + await expect( + paraApeStaking.connect(user1.signer).claimApeCoinPairPool(true, [0, 1]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).claimApeCoinPairPool(true, [2]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); + }); + + it("withdrawApeCoinPairPool revert test", async () => { + const { + users: [user1, user2], + ape, + bayc, + bakc, + nBAYC, + nBAKC, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "2000000", user1); + await mintAndValidate(ape, "2000000", user2); + await supplyAndValidate(bayc, "2", user1, true); + await supplyAndValidate(bakc, "2", user1, true); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [1], + bakcTokenIds: [1], + }) + ); + + await expect( + paraApeStaking.connect(user1.signer).withdrawApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [1], + bakcTokenIds: [1], + }) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + + await expect( + paraApeStaking.connect(user1.signer).withdrawApeCoinPairPool({ + cashToken: ape.address, + cashAmount: parseEther("300000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.INVALID_CASH_AMOUNT); + }); +}); From 63e524443d648f7a20d94ece2a103bcc16fc7de4 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 24 Jul 2023 19:14:21 +0800 Subject: [PATCH 63/99] chore: small optimization --- .../apestaking/logic/ApeCoinPoolLogic.sol | 15 ++++-- .../logic/ApeStakingPairPoolLogic.sol | 20 ++++---- .../logic/ApeStakingSinglePoolLogic.sol | 33 ++++++------- .../protocol/libraries/helpers/Errors.sol | 1 - .../libraries/logic/ValidationLogic.sol | 9 ---- test/_pool_core_erc20_repay.spec.ts | 26 +++++----- test/para_ape_staking.spec.ts | 48 +++++++++++++++++-- test/para_pool_ape_staking.spec.ts | 42 ++++++++++++++++ 8 files changed, 136 insertions(+), 58 deletions(-) diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index bc3a2a911..9e96f76fc 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -211,7 +211,7 @@ library ApeCoinPoolLogic { ) external { ApeStakingCommonLogic.validateTokenIdArray(tokenIds); - _claimApeCoinPool(poolState, vars, isBAYC, true, tokenIds); + _claimApeCoinPool(poolState, vars, isBAYC, true, true, tokenIds); } function withdrawApeCoinPool( @@ -230,6 +230,7 @@ library ApeCoinPoolLogic { vars, withdrawInfo.isBAYC, true, + false, withdrawInfo.tokenIds ); @@ -473,7 +474,7 @@ library ApeCoinPoolLogic { ) external { ApeStakingCommonLogic.validateTokenIdArray(tokenIds); - _claimApeCoinPool(poolState, vars, isBAYC, false, tokenIds); + _claimApeCoinPool(poolState, vars, isBAYC, false, true, tokenIds); } function withdrawApeCoinPairPool( @@ -495,6 +496,7 @@ library ApeCoinPoolLogic { vars, withdrawInfo.isBAYC, false, + false, withdrawInfo.apeTokenIds ); @@ -860,6 +862,7 @@ library ApeCoinPoolLogic { IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, bool isSinglePool, + bool needUpdateStatus, uint32[] memory apeTokenIds ) internal returns (address) { ( @@ -873,9 +876,11 @@ library ApeCoinPoolLogic { for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; - poolState - .tokenStatus[apeTokenId] - .rewardsDebt = accumulatedRewardsPerNft; + if (needUpdateStatus) { + poolState + .tokenStatus[apeTokenId] + .rewardsDebt = accumulatedRewardsPerNft; + } //emit event if (isSinglePool) { diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 2dce47584..12b1fc271 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -223,6 +223,7 @@ library ApeStakingPairPoolLogic { vars.nApeOwner = _claimPairNFT( poolState, vars, + false, isBAYC, apeTokenIds, bakcTokenIds @@ -254,12 +255,11 @@ library ApeStakingPairPoolLogic { //check ntoken owner { - address msgSender = msg.sender; - if (vars.nApeOwner != msgSender) { + if (vars.nApeOwner != msg.sender) { address nBakcOwner = IERC721(vars.nBakc).ownerOf( bakcTokenId ); - require(msgSender == nBakcOwner, Errors.NOT_THE_OWNER); + require(msg.sender == nBakcOwner, Errors.NOT_THE_OWNER); } } @@ -379,7 +379,7 @@ library ApeStakingPairPoolLogic { Errors.INVALID_PARAMETER ); - _claimPairNFT(poolState, vars, isBAYC, apeTokenIds, bakcTokenIds); + _claimPairNFT(poolState, vars, true, isBAYC, apeTokenIds, bakcTokenIds); } function compoundPairNFT( @@ -520,6 +520,7 @@ library ApeStakingPairPoolLogic { function _claimPairNFT( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool needUpdateStatus, bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds @@ -537,14 +538,15 @@ library ApeStakingPairPoolLogic { ); if (pendingReward > 0) { - uint256 arrayLength = apeTokenIds.length; - for (uint256 index = 0; index < arrayLength; index++) { + for (uint256 index = 0; index < apeTokenIds.length; index++) { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; - poolState - .tokenStatus[apeTokenId] - .rewardsDebt = accumulatedRewardsPerNft; + if (needUpdateStatus) { + poolState + .tokenStatus[apeTokenId] + .rewardsDebt = accumulatedRewardsPerNft; + } //emit event emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 7105e6166..e90ac1494 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -68,7 +68,7 @@ library ApeStakingSinglePoolLogic { apeStakingPoolId = MAYC_POOL_ID; } else { nToken = vars.nBakc; - apeStakingPoolId = 0; + apeStakingPoolId = BAKC_POOL_ID; } address msgSender = msg.sender; for (uint256 index = 0; index < arrayLength; index++) { @@ -79,19 +79,12 @@ library ApeStakingSinglePoolLogic { Errors.NOT_THE_OWNER ); - if (nft == vars.bakc) { - (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( - BAKC_POOL_ID, - tokenId - ); - require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); - } else { - (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( - apeStakingPoolId, - tokenId - ); - require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); - + (uint256 stakedAmount, ) = vars.apeCoinStaking.nftPosition( + apeStakingPoolId, + tokenId + ); + require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); + if (nft != vars.bakc) { (, bool isPaired) = vars.apeCoinStaking.mainToBakc( apeStakingPoolId, tokenId @@ -113,6 +106,7 @@ library ApeStakingSinglePoolLogic { emit NFTDeposited(nft, tokenId); } + //update state if (nft == vars.bayc) { vaultStorage .poolStates[BAYC_SINGLE_POOL_ID] @@ -405,7 +399,7 @@ library ApeStakingSinglePoolLogic { mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); - _claimNFT(tokenStatus, vars, nft, tokenIds); + _claimNFT(tokenStatus, vars, true, nft, tokenIds); } function withdrawNFT( @@ -427,7 +421,7 @@ library ApeStakingSinglePoolLogic { storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); //claim pending reward - address nApeOwner = _claimNFT(tokenStatus, vars, nft, tokenIds); + address nApeOwner = _claimNFT(tokenStatus, vars, false, nft, tokenIds); address nToken; if (nft == vars.bayc) { @@ -795,6 +789,7 @@ library ApeStakingSinglePoolLogic { function _claimNFT( mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus, IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool needUpdateStatus, address nft, uint32[] calldata tokenIds ) internal returns (address) { @@ -813,8 +808,10 @@ library ApeStakingSinglePoolLogic { for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; - tokenStatus[tokenId].rewardsDebt = vars - .accumulatedRewardsPerNft; + if (needUpdateStatus) { + tokenStatus[tokenId].rewardsDebt = vars + .accumulatedRewardsPerNft; + } //emit event emit NFTClaimed(nft, tokenId); diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index 8f9b4c33e..49e3ab415 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -49,7 +49,6 @@ library Errors { string public constant HEALTH_FACTOR_NOT_BELOW_THRESHOLD = "45"; // 'Health factor is not below the threshold' string public constant COLLATERAL_CANNOT_BE_AUCTIONED_OR_LIQUIDATED = "46"; // 'The collateral chosen cannot be auctioned OR liquidated' string public constant SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER = "47"; // 'User did not borrow the specified currency' - string public constant SAME_BLOCK_BORROW_REPAY = "48"; // 'Borrow and repay in same block is not allowed' string public constant BORROW_CAP_EXCEEDED = "50"; // 'Borrow cap is exceeded' string public constant SUPPLY_CAP_EXCEEDED = "51"; // 'Supply cap is exceeded' string public constant XTOKEN_SUPPLY_NOT_ZERO = "54"; // 'PToken supply is not zero' diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 0072e8751..7d2ace304 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -401,15 +401,6 @@ library ValidationLogic { Errors.INVALID_ASSET_TYPE ); - uint256 variableDebtPreviousIndex = IScaledBalanceToken( - reserveCache.variableDebtTokenAddress - ).getPreviousIndex(onBehalfOf); - - require( - (variableDebtPreviousIndex < reserveCache.nextVariableBorrowIndex), - Errors.SAME_BLOCK_BORROW_REPAY - ); - require((variableDebt != 0), Errors.NO_DEBT_OF_SELECTED_TYPE); } diff --git a/test/_pool_core_erc20_repay.spec.ts b/test/_pool_core_erc20_repay.spec.ts index db603f2d4..ce9e7133c 100644 --- a/test/_pool_core_erc20_repay.spec.ts +++ b/test/_pool_core_erc20_repay.spec.ts @@ -299,13 +299,13 @@ describe("pToken Repay Event Accounting", () => { .borrow(dai.address, utils.parseEther("500"), 0, user.address); // Turn on automining, but not mine a new block until next tx - await setAutomineEvm(true); + // await setAutomineEvm(true); - await expect( - pool - .connect(user.signer) - .repay(dai.address, utils.parseEther("500"), user.address) - ).to.be.revertedWith(SAME_BLOCK_BORROW_REPAY); + // await expect( + // pool + // .connect(user.signer) + // .repay(dai.address, utils.parseEther("500"), user.address) + // ).to.be.revertedWith(SAME_BLOCK_BORROW_REPAY); }); it("TC-erc20-repay-11 validateRepay() when variable borrowing and repaying in same block using credit delegation (revert expected)", async () => { @@ -367,13 +367,13 @@ describe("pToken Repay Event Accounting", () => { .borrow(dai.address, utils.parseEther("2"), 0, user1.address); // Turn on automining, but not mine a new block until next tx - await setAutomineEvm(true); - - await expect( - pool - .connect(user1.signer) - .repay(dai.address, utils.parseEther("2"), user1.address) - ).to.be.revertedWith(SAME_BLOCK_BORROW_REPAY); + // await setAutomineEvm(true); + // + // await expect( + // pool + // .connect(user1.signer) + // .repay(dai.address, utils.parseEther("2"), user1.address) + // ).to.be.revertedWith(SAME_BLOCK_BORROW_REPAY); }); }); }); diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 56b301b7c..193a254d7 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -212,6 +212,19 @@ describe("Para Ape Staking Test", () => { expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); + const newUser1PendingReward = await paraApeStaking.pairNFTPendingReward( + true, + [0, 1], + [0, 1] + ); + const newUser2PendingReward = await paraApeStaking.pairNFTPendingReward( + true, + [2], + [2] + ); + expect(newUser1PendingReward).to.be.equal(0); + expect(newUser2PendingReward).to.be.equal(0); + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( @@ -376,6 +389,19 @@ describe("Para Ape Staking Test", () => { expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); + const newUser1PendingReward = await paraApeStaking.pairNFTPendingReward( + false, + [0, 1], + [0, 1] + ); + const newUser2PendingReward = await paraApeStaking.pairNFTPendingReward( + false, + [2], + [2] + ); + expect(newUser1PendingReward).to.be.equal(0); + expect(newUser2PendingReward).to.be.equal(0); + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( @@ -538,15 +564,15 @@ describe("Para Ape Staking Test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("1080"), parseEther("10")); - const user1PendingReward = await paraApeStaking.nftPendingReward( + let user1PendingReward = await paraApeStaking.nftPendingReward( bayc.address, [0, 1, 2] ); - const user2PendingReward = await paraApeStaking.nftPendingReward( + let user2PendingReward = await paraApeStaking.nftPendingReward( mayc.address, [0, 1, 2] ); - const user3PendingReward = await paraApeStaking.nftPendingReward( + let user3PendingReward = await paraApeStaking.nftPendingReward( bakc.address, [0, 1, 2] ); @@ -588,6 +614,22 @@ describe("Para Ape Staking Test", () => { expect(user1Balance).to.be.closeTo(user2Balance, parseEther("50")); expect(user1Balance).to.be.closeTo(user3Balance, parseEther("50")); + let newUser1PendingReward = await paraApeStaking.nftPendingReward( + bayc.address, + [0, 1, 2] + ); + let newUser2PendingReward = await paraApeStaking.nftPendingReward( + mayc.address, + [0, 1, 2] + ); + let newUser3PendingReward = await paraApeStaking.nftPendingReward( + bakc.address, + [0, 1, 2] + ); + expect(newUser1PendingReward).to.be.equal(0); + expect(newUser2PendingReward).to.be.equal(0); + expect(newUser3PendingReward).to.be.equal(0); + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 6bbecbd5b..6608d500f 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -258,6 +258,16 @@ describe("Para Ape staking ape coin pool test", () => { expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); + user1PendingReward = await paraApeStaking.apeCoinPoolPendingReward( + true, + [0, 1] + ); + user2PendingReward = await paraApeStaking.apeCoinPoolPendingReward(true, [ + 2, + ]); + expect(user1PendingReward).to.be.equal(0); + expect(user2PendingReward).to.be.equal(0); + await waitForTx( await paraApeStaking .connect(user4.signer) @@ -304,6 +314,17 @@ describe("Para Ape staking ape coin pool test", () => { parseEther("1") ); + user1PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( + true, + [0, 1] + ); + user2PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( + true, + [2] + ); + expect(user1PendingReward).to.be.equal(0); + expect(user2PendingReward).to.be.equal(0); + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( @@ -511,6 +532,16 @@ describe("Para Ape staking ape coin pool test", () => { expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); + user1PendingReward = await paraApeStaking.apeCoinPoolPendingReward( + false, + [0, 1] + ); + user2PendingReward = await paraApeStaking.apeCoinPoolPendingReward(false, [ + 2, + ]); + expect(user1PendingReward).to.be.equal(0); + expect(user2PendingReward).to.be.equal(0); + await waitForTx( await paraApeStaking .connect(user4.signer) @@ -559,6 +590,17 @@ describe("Para Ape staking ape coin pool test", () => { parseEther("1") ); + user1PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( + false, + [0, 1] + ); + user2PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( + false, + [2] + ); + expect(user1PendingReward).to.be.equal(0); + expect(user2PendingReward).to.be.equal(0); + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( From 82e7a24171991300a32c23b7f6e4c4e8fa950257 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 24 Jul 2023 22:17:42 +0800 Subject: [PATCH 64/99] chore: add test case and fix lint --- contracts/apestaking/ParaApeStaking.sol | 2 +- contracts/interfaces/IParaApeStaking.sol | 2 +- .../libraries/logic/ValidationLogic.sol | 8 - .../protocol/tokenization/PTokenSApe.sol | 2 +- helpers/contracts-getters.ts | 12 - helpers/init-helpers.ts | 1 - helpers/types.ts | 4 + scripts/upgrade/ptoken.ts | 6 +- test/_pool_core_erc20_repay.spec.ts | 3 +- test/_sape_pool_operation.spec.ts | 134 +--- test/auto_compound_ape.spec.ts | 720 +----------------- test/para_ape_staking.spec.ts | 12 +- test/para_pool_ape_staking.spec.ts | 146 +++- test/xtoken_ntoken_bakc.spec.ts | 410 ---------- 14 files changed, 184 insertions(+), 1278 deletions(-) delete mode 100644 test/xtoken_ntoken_bakc.spec.ts diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 115ab140d..53cc4ddf5 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -234,7 +234,7 @@ contract ParaApeStaking is cache.stakedBalance; } - function transferSApeBalance( + function transferFreeSApeBalance( address from, address to, uint256 amount diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index 1873cf8b6..cbbd22c48 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -142,7 +142,7 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { function totalSApeBalance(address user) external view returns (uint256); - function transferSApeBalance( + function transferFreeSApeBalance( address from, address to, uint256 amount diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 7d2ace304..20c01da90 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -519,14 +519,6 @@ library ValidationLogic { Errors.LIQUIDATION_AMOUNT_NOT_ENOUGH ); - IXTokenType xToken = IXTokenType( - params.liquidationAssetReserveCache.xTokenAddress - ); - require( - xToken.getXTokenType() != XTokenType.PTokenSApe, - Errors.SAPE_NOT_ALLOWED - ); - ( vars.principalReserveActive, , diff --git a/contracts/protocol/tokenization/PTokenSApe.sol b/contracts/protocol/tokenization/PTokenSApe.sol index 381008f3c..b92452756 100644 --- a/contracts/protocol/tokenization/PTokenSApe.sol +++ b/contracts/protocol/tokenization/PTokenSApe.sol @@ -73,7 +73,7 @@ contract PTokenSApe is PToken { address to, uint256 value ) external override onlyPool { - return paraApeStaking.transferSApeBalance(from, to, value); + return paraApeStaking.transferFreeSApeBalance(from, to, value); } function _transfer( diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index cb03d56bd..bd335ca81 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -74,7 +74,6 @@ import { AutoCompoundApe__factory, InitializableAdminUpgradeabilityProxy__factory, StETHDebtToken__factory, - ApeStakingLogic__factory, MintableERC721Logic__factory, NTokenBAKC__factory, P2PPairStaking__factory, @@ -960,17 +959,6 @@ export const getApeCoinStaking = async (address?: tEthereumAddress) => await getFirstSigner() ); -export const getApeStakingLogic = async (address?: tEthereumAddress) => - await ApeStakingLogic__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.ApeStakingLogic}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - export const getMintableERC721Logic = async (address?: tEthereumAddress) => await MintableERC721Logic__factory.connect( address || diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index 398c4b167..1f9cc28cd 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -3,7 +3,6 @@ import { ERC20TokenContractId, ERC721TokenContractId, IReserveParams, - NTokenContractId, tEthereumAddress, } from "./types"; import {chunk, waitForTx} from "./misc-utils"; diff --git a/helpers/types.ts b/helpers/types.ts index 39708e377..904669aaa 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -13,6 +13,7 @@ import {NTokenMoonBirdsLibraryAddresses} from "../types/factories/contracts/prot import {NTokenUniswapV3LibraryAddresses} from "../types/factories/contracts/protocol/tokenization/NTokenUniswapV3__factory"; import {NTokenLibraryAddresses} from "../types/factories/contracts/protocol/tokenization/NToken__factory"; import {PoolPositionMoverLibraryAddresses} from "../types/factories/contracts/protocol/pool/PoolPositionMover__factory"; +import {PoolApeStakingLibraryAddresses} from "../types/factories/contracts/protocol/pool/PoolApeStaking__factory"; export enum AssetType { ERC20 = 0, @@ -82,6 +83,7 @@ export type ParaSpaceLibraryAddresses = | PoolMarketplaceLibraryAddresses | PoolParametersLibraryAddresses | PoolConfiguratorLibraryAddresses + | PoolApeStakingLibraryAddresses | BlurExchangeLibraryAddresses | NTokenBAYCLibraryAddresses | NTokenMAYCLibraryAddresses @@ -416,6 +418,8 @@ export enum ProtocolErrors { INVALID_TOKEN_ID = "135", //invalid token id + CALLER_NOT_ALLOWED = "141", //The caller of the function is not allowed + INVALID_PARAMETER = "170", //invalid parameter APE_POSITION_EXISTED = "171", //ape staking position already existed BAKC_POSITION_EXISTED = "172", //bakc staking position already existed diff --git a/scripts/upgrade/ptoken.ts b/scripts/upgrade/ptoken.ts index 6699870c3..6c5b725af 100644 --- a/scripts/upgrade/ptoken.ts +++ b/scripts/upgrade/ptoken.ts @@ -13,11 +13,7 @@ import { getProtocolDataProvider, getPToken, } from "../../helpers/contracts-getters"; -import { - NTokenContractId, - PTokenContractId, - XTokenType, -} from "../../helpers/types"; +import {PTokenContractId, XTokenType} from "../../helpers/types"; import dotenv from "dotenv"; import {DRY_RUN, GLOBAL_OVERRIDES} from "../../helpers/hardhat-constants"; diff --git a/test/_pool_core_erc20_repay.spec.ts b/test/_pool_core_erc20_repay.spec.ts index ce9e7133c..6173e442d 100644 --- a/test/_pool_core_erc20_repay.spec.ts +++ b/test/_pool_core_erc20_repay.spec.ts @@ -6,7 +6,6 @@ import {convertToCurrencyDecimals} from "../helpers/contracts-helpers"; import { advanceTimeAndBlock, setAutomine, - setAutomineEvm, waitForTx, } from "../helpers/misc-utils"; import {ProtocolErrors} from "../helpers/types"; @@ -22,7 +21,7 @@ import {almostEqual} from "./helpers/uniswapv3-helper"; import {utils} from "ethers"; import {getVariableDebtToken} from "../helpers/contracts-getters"; -const {RESERVE_INACTIVE, SAME_BLOCK_BORROW_REPAY} = ProtocolErrors; +const {RESERVE_INACTIVE} = ProtocolErrors; const fixture = async () => { const testEnv = await loadFixture(testEnvFixture); diff --git a/test/_sape_pool_operation.spec.ts b/test/_sape_pool_operation.spec.ts index e7e91fdb5..6958cea31 100644 --- a/test/_sape_pool_operation.spec.ts +++ b/test/_sape_pool_operation.spec.ts @@ -6,19 +6,16 @@ import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; import {ProtocolErrors} from "../helpers/types"; import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; import {testEnvFixture} from "./helpers/setup-env"; -import { - changePriceAndValidate, - mintAndValidate, - supplyAndValidate, -} from "./helpers/validated-steps"; -import {convertToCurrencyDecimals} from "../helpers/contracts-helpers"; -import {PTokenSApe} from "../types"; -import {getPTokenSApe} from "../helpers/contracts-getters"; +import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; +import {ParaApeStaking, PTokenSApe} from "../types"; +import {getParaApeStaking, getPTokenSApe} from "../helpers/contracts-getters"; +import {parseEther} from "ethers/lib/utils"; describe("SApe Pool Operation Test", () => { let testEnv: TestEnv; const sApeAddress = ONE_ADDRESS; let pSApeCoin: PTokenSApe; + let paraApeStaking: ParaApeStaking; const fixture = async () => { testEnv = await loadFixture(testEnvFixture); @@ -30,6 +27,8 @@ describe("SApe Pool Operation Test", () => { pool, } = testEnv; + paraApeStaking = await getParaApeStaking(); + const {xTokenAddress: pSApeCoinAddress} = await protocolDataProvider.getReserveTokensAddresses(sApeAddress); pSApeCoin = await getPTokenSApe(pSApeCoinAddress); @@ -65,21 +64,21 @@ describe("SApe Pool Operation Test", () => { } = await loadFixture(fixture); await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "5000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount}], - [], - true - ) + await mintAndValidate(ape, "100000", user1); + + await waitForTx( + await ape + .connect(user1.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + tokenIds: [0], + }) ); const balance = await pSApeCoin.balanceOf(user1.address); @@ -105,93 +104,4 @@ describe("SApe Pool Operation Test", () => { }) ).to.be.revertedWith(ProtocolErrors.BORROWING_NOT_ENABLED); }); - - it("liquidate sApe is not allowed", async () => { - const { - users: [user1, liquidator], - ape, - mayc, - pool, - weth, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "5000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount}], - [], - true - ) - ); - - await supplyAndValidate(weth, "100", liquidator, true, "200000"); - - // BorrowLimit: (51 * 0.325 + 5000 * 0.0036906841286 * 0.2 - 5000 * 0.0036906841286) = 1.8122634856 - const borrowAmount = await convertToCurrencyDecimals(weth.address, "1"); - expect( - await pool - .connect(user1.signer) - .borrow(weth.address, borrowAmount, 0, user1.address) - ); - - // drop HF and ERC-721_HF below 1 - await changePriceAndValidate(mayc, "5"); - - await expect( - pool - .connect(liquidator.signer) - .liquidateERC20( - weth.address, - sApeAddress, - user1.address, - amount, - false, - {gasLimit: 5000000} - ) - ).to.be.revertedWith(ProtocolErrors.SAPE_NOT_ALLOWED); - }); - - // it("set sApe not as collateral is not allowed", async () => { - // const { - // users: [user1], - // ape, - // mayc, - // pool, - // } = await loadFixture(fixture); - // - // await supplyAndValidate(mayc, "1", user1, true); - // await mintAndValidate(ape, "10000", user1); - // - // const amount = await convertToCurrencyDecimals(ape.address, "5000"); - // expect( - // await pool.connect(user1.signer).borrowApeAndStake( - // { - // nftAsset: mayc.address, - // borrowAsset: ape.address, - // borrowAmount: amount, - // cashAmount: 0, - // }, - // [{tokenId: 0, amount: amount}], - // [], - // true - // ) - // ); - // - // await expect( - // pool - // .connect(user1.signer) - // .setUserUseERC20AsCollateral(sApeAddress, false, { - // gasLimit: 12_450_000, - // }) - // ).to.be.revertedWith(ProtocolErrors.SAPE_NOT_ALLOWED); - // }); }); diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index fb1838d07..fd87eea42 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -1,6 +1,6 @@ import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; import {expect} from "chai"; -import {AutoCompoundApe, PToken, PTokenSApe, VariableDebtToken} from "../types"; +import {AutoCompoundApe} from "../types"; import {TestEnv} from "./helpers/make-suite"; import {testEnvFixture} from "./helpers/setup-env"; import {mintAndValidate} from "./helpers/validated-steps"; @@ -12,13 +12,8 @@ import { fund, mintNewPosition, } from "./helpers/uniswapv3-helper"; -import { - getAutoCompoundApe, - getPToken, - getPTokenSApe, - getVariableDebtToken, -} from "../helpers/contracts-getters"; -import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; +import {getAutoCompoundApe} from "../helpers/contracts-getters"; +import {MAX_UINT_AMOUNT} from "../helpers/constants"; import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {deployMockedDelegateRegistry} from "../helpers/contracts-deployments"; import {ETHERSCAN_VERIFICATION} from "../helpers/hardhat-constants"; @@ -29,10 +24,6 @@ import {ProtocolErrors} from "../helpers/types"; describe("Auto Compound Ape Test", () => { let testEnv: TestEnv; let cApe: AutoCompoundApe; - let pCApe: PToken; - let variableDebtCAPE: VariableDebtToken; - let pSApeCoin: PTokenSApe; - const sApeAddress = ONE_ADDRESS; let user1Amount; let user2Amount; let user3Amount; @@ -49,7 +40,6 @@ describe("Auto Compound Ape Test", () => { users: [user1, user2, , , user3, user4, user5], apeCoinStaking, pool, - protocolDataProvider, poolAdmin, nftPositionManager, } = testEnv; @@ -57,16 +47,6 @@ describe("Auto Compound Ape Test", () => { cApe = await getAutoCompoundApe(); MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); - const { - xTokenAddress: pCApeAddress, - variableDebtTokenAddress: variableDebtPsApeAddress, - } = await protocolDataProvider.getReserveTokensAddresses(cApe.address); - pCApe = await getPToken(pCApeAddress); - variableDebtCAPE = await getVariableDebtToken(variableDebtPsApeAddress); - const {xTokenAddress: pSApeCoinAddress} = - await protocolDataProvider.getReserveTokensAddresses(sApeAddress); - pSApeCoin = await getPTokenSApe(pSApeCoinAddress); - await mintAndValidate(ape, "1000", user1); await mintAndValidate(ape, "2000", user2); await mintAndValidate(ape, "4000", user3); @@ -411,597 +391,6 @@ describe("Auto Compound Ape Test", () => { almostEqual(user1ApeBalance, parseEther("5923.8")); }); - it("claimApeAndCompound function work as expected 1", async () => { - const { - pUsdc, - usdc, - users: [user1, user2, , , user3], - mayc, - pool, - ape, - } = await loadFixture(fixture); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user2.signer)["mint(address)"](user2.address) - ); - await waitForTx( - await mayc.connect(user3.signer)["mint(address)"](user3.address) - ); - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await mayc.connect(user2.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await mayc.connect(user3.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool - .connect(user1.signer) - .supplyERC721( - mayc.address, - [{tokenId: 0, useAsCollateral: true}], - user1.address, - "0" - ) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supplyERC721( - mayc.address, - [{tokenId: 1, useAsCollateral: true}], - user2.address, - "0" - ) - ); - await waitForTx( - await pool - .connect(user3.signer) - .supplyERC721( - mayc.address, - [{tokenId: 2, useAsCollateral: true}], - user3.address, - "0" - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user1Amount, - }, - [{tokenId: 0, amount: user1Amount}], - [], - true - ) - ); - - await waitForTx( - await pool.connect(user2.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user2Amount, - }, - [{tokenId: 1, amount: user2Amount}], - [], - true - ) - ); - - await waitForTx( - await pool.connect(user3.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user3Amount, - }, - [{tokenId: 2, amount: user3Amount}], - [], - true - ) - ); - - await advanceTimeAndBlock(3600); - - // repay then supply - await waitForTx( - await pool.connect(user1.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 0, - }) - ); - - // repay then supply - await waitForTx( - await pool.connect(user2.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 0, - }) - ); - - // swap half then supply - await waitForTx( - await pool.connect(user3.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 5000, - }) - ); - const beforeBalance = await ape.balanceOf(user2.address); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address, user2.address, user3.address], - [[0], [1], [2]], - 3827874, - 3506149922170000, - {gasLimit: 5000000} - ) - ); - - // 3600 / 7 * 99.7% = 512.74 - const user1Balance = await pCApe.balanceOf(user1.address); - almostEqual(user1Balance, parseEther("512.7428")); - - // 3600 * 2 / 7 * 99.7% = 1025.48 - const user2Balance = await pCApe.balanceOf(user2.address); - almostEqual(user2Balance, parseEther("1025.48")); - - // 3600 * 4 / 7 * 99.7% * 50% = 1025.4857142857142858 - const user3Balance = await pCApe.balanceOf(user3.address); - almostEqual(user3Balance, parseEther("1025.48571")); - - almostEqual( - await pUsdc.balanceOf(user3.address), - await convertToCurrencyDecimals(usdc.address, "4059.235479") - ); - - // 3600 * 0.003 - almostEqual( - (await ape.balanceOf(user2.address)).sub(beforeBalance), - parseEther("10.8") - ); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address, user2.address, user3.address], - [[0], [1], [2]], - 3827874, - 3506149922170000, - {gasLimit: 5000000} - ) - ); - }); - - it("claimApeAndCompound function work as expected 2", async () => { - const { - users: [user1, user2], - mayc, - pool, - ape, - } = await loadFixture(fixture); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool.connect(user1.signer).supplyERC721( - mayc.address, - [ - {tokenId: 0, useAsCollateral: true}, - {tokenId: 1, useAsCollateral: true}, - {tokenId: 2, useAsCollateral: true}, - ], - user1.address, - "0" - ) - ); - - const totalAmount = parseEther("900"); - const userAmount = parseEther("300"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: totalAmount, - }, - [ - {tokenId: 0, amount: userAmount}, - {tokenId: 1, amount: userAmount}, - {tokenId: 2, amount: userAmount}, - ], - [], - true - ) - ); - - await advanceTimeAndBlock(3600); - - const beforeBalance = await ape.balanceOf(user2.address); - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address], - [[0, 1, 2]], - 3827874, - 3506149922170000, - { - gasLimit: 5000000, - } - ) - ); - - //3600 * 0.997 = 3589.2 - const user1Balance = await pCApe.balanceOf(user1.address); - almostEqual(user1Balance, parseEther("3589.2")); - - // 3600 * 0.003 - almostEqual( - (await ape.balanceOf(user2.address)).sub(beforeBalance), - parseEther("10.8") - ); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address], - [[0, 1, 2]], - 3827874, - 3506149922170000, - { - gasLimit: 5000000, - } - ) - ); - }); - - it("claimApeAndCompound function work as expected 3", async () => { - const { - pWETH, - weth, - users: [user1, user2, , , user3], - mayc, - pool, - ape, - } = await loadFixture(fixture); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user2.signer)["mint(address)"](user2.address) - ); - await waitForTx( - await mayc.connect(user3.signer)["mint(address)"](user3.address) - ); - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await mayc.connect(user2.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await mayc.connect(user3.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool - .connect(user1.signer) - .supplyERC721( - mayc.address, - [{tokenId: 0, useAsCollateral: true}], - user1.address, - "0" - ) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supplyERC721( - mayc.address, - [{tokenId: 1, useAsCollateral: true}], - user2.address, - "0" - ) - ); - await waitForTx( - await pool - .connect(user3.signer) - .supplyERC721( - mayc.address, - [{tokenId: 2, useAsCollateral: true}], - user3.address, - "0" - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user1Amount, - }, - [{tokenId: 0, amount: user1Amount}], - [], - true - ) - ); - - await waitForTx( - await pool.connect(user2.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user2Amount, - }, - [{tokenId: 1, amount: user2Amount}], - [], - true - ) - ); - - await waitForTx( - await pool.connect(user3.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user3Amount, - }, - [{tokenId: 2, amount: user3Amount}], - [], - true - ) - ); - - await advanceTimeAndBlock(3600); - - // repay then supply - await waitForTx( - await pool.connect(user1.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 0, - }) - ); - - // repay then supply - await waitForTx( - await pool.connect(user2.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 0, - }) - ); - - // swap half then supply - await waitForTx( - await pool.connect(user3.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 1, - swapPercent: 5000, - }) - ); - - const beforeBalance = await ape.balanceOf(user2.address); - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address, user2.address, user3.address], - [[0], [1], [2]], - 3827874, - 3506149922170000, - {gasLimit: 5000000} - ) - ); - - // 3600 / 7 * 99.7% = 512.74 - const user1Balance = await pCApe.balanceOf(user1.address); - almostEqual(user1Balance, parseEther("512.7428")); - - // 3600 * 2 / 7 * 99.7% = 1025.48 - const user2Balance = await pCApe.balanceOf(user2.address); - almostEqual(user2Balance, parseEther("1025.48")); - - // 3600 * 4 / 7 * 99.7% * 50% = 1025.4857142857142858 - const user3Balance = await pCApe.balanceOf(user3.address); - almostEqual(user3Balance, parseEther("1025.48571")); - - almostEqual( - await pWETH.balanceOf(user3.address), - await convertToCurrencyDecimals(weth.address, "3.732876") - ); - - // 3600 * 0.003 - almostEqual( - (await ape.balanceOf(user2.address)).sub(beforeBalance), - parseEther("10.8") - ); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address, user2.address, user3.address], - [[0], [1], [2]], - 3827874, - 3506149922170000, - {gasLimit: 5000000} - ) - ); - }); - - it("claimPairedApeRewardAndCompound function work as expected", async () => { - const { - users: [user1, user2], - mayc, - pool, - ape, - bakc, - } = await loadFixture(fixture); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await bakc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await bakc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await bakc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await bakc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool.connect(user1.signer).supplyERC721( - mayc.address, - [ - {tokenId: 0, useAsCollateral: true}, - {tokenId: 1, useAsCollateral: true}, - {tokenId: 2, useAsCollateral: true}, - ], - user1.address, - "0" - ) - ); - await waitForTx( - await pool.connect(user1.signer).supplyERC721( - bakc.address, - [ - {tokenId: 0, useAsCollateral: true}, - {tokenId: 1, useAsCollateral: true}, - {tokenId: 2, useAsCollateral: true}, - ], - user1.address, - "0" - ) - ); - - const totalAmount = parseEther("900"); - const userAmount = parseEther("300"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: totalAmount, - }, - [], - [ - {mainTokenId: 0, bakcTokenId: 0, amount: userAmount}, - {mainTokenId: 1, bakcTokenId: 1, amount: userAmount}, - {mainTokenId: 2, bakcTokenId: 2, amount: userAmount}, - ], - true - ) - ); - - await advanceTimeAndBlock(3600); - - const beforeBalance = await ape.balanceOf(user2.address); - await waitForTx( - await pool.connect(user2.signer).claimPairedApeAndCompound( - mayc.address, - [user1.address], - [ - [ - {mainTokenId: 0, bakcTokenId: 0}, - {mainTokenId: 1, bakcTokenId: 1}, - {mainTokenId: 2, bakcTokenId: 2}, - ], - ], - 0, - 0 - ) - ); - - //3600 * 0.997 = 3589.2 - const user1Balance = await pCApe.balanceOf(user1.address); - almostEqual(user1Balance, parseEther("3589.2")); - - // 3600 * 0.003 - almostEqual( - (await ape.balanceOf(user2.address)).sub(beforeBalance), - parseEther("10.8") - ); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool.connect(user2.signer).claimPairedApeAndCompound( - mayc.address, - [user1.address], - [ - [ - {mainTokenId: 0, bakcTokenId: 0}, - {mainTokenId: 1, bakcTokenId: 1}, - {mainTokenId: 2, bakcTokenId: 2}, - ], - ], - 0, - 0 - ) - ); - }); - it("bufferBalance work as expected", async () => { const { users: [user1, user2], @@ -1077,109 +466,6 @@ describe("Auto Compound Ape Test", () => { almostEqual(await ape.balanceOf(user2.address), user2Amount); }); - it("borrow cape and stake function work as expected: use 100% debt", async () => { - const { - users: [user1, user2], - mayc, - pool, - ape, - } = await loadFixture(fixture); - - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, user2Amount) - ); - - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, user2Amount) - ); - - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, user2Amount, user2.address, 0) - ); - - almostEqual(await pCApe.balanceOf(user2.address), user2Amount); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool - .connect(user1.signer) - .supplyERC721( - mayc.address, - [{tokenId: 0, useAsCollateral: true}], - user1.address, - "0" - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: user1Amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: user1Amount}], - [], - true - ) - ); - - const user2pCApeBalance = await pCApe.balanceOf(user2.address); - almostEqual(user2pCApeBalance, user2Amount); - let user1CApeDebtBalance = await variableDebtCAPE.balanceOf(user1.address); - almostEqual(user1CApeDebtBalance, user1Amount); - almostEqual(await pSApeCoin.balanceOf(user1.address), user1Amount); - almostEqual(await pCApe.balanceOf(user2.address), user2Amount); - almostEqual(await cApe.totalSupply(), user2Amount.sub(user1Amount)); - - const hourRewardAmount = parseEther("3600"); - await advanceTimeAndBlock(3600); - await waitForTx(await cApe.connect(user2.signer).harvestAndCompound()); - //this is a edge case here, because Ape single pool only got deposited by user2 - almostEqual( - await pCApe.balanceOf(user2.address), - user2Amount.add(hourRewardAmount.mul(2)) - ); - - user1CApeDebtBalance = await variableDebtCAPE.balanceOf(user1.address); - almostEqual(user1CApeDebtBalance, user1Amount.add(hourRewardAmount)); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: user1Amount}]) - ); - const apeBalance = await ape.balanceOf(user1.address); - //user1Amount + borrow user1Amount + hourRewardAmount - almostEqual(apeBalance, user1Amount.mul(2).add(hourRewardAmount)); - - await waitForTx( - await cApe.connect(user1.signer).deposit(user1.address, apeBalance) - ); - - await waitForTx( - await cApe.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user1.signer) - .repay(cApe.address, apeBalance, user1.address) - ); - user1CApeDebtBalance = await variableDebtCAPE.balanceOf(user1.address); - expect(user1CApeDebtBalance).to.be.equal(0); - - almostEqual(await cApe.balanceOf(user1.address), user1Amount); - }); - it("test vote delegation", async () => { const { users: [user1], diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 193a254d7..df3392d47 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -564,15 +564,15 @@ describe("Para Ape Staking Test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("1080"), parseEther("10")); - let user1PendingReward = await paraApeStaking.nftPendingReward( + const user1PendingReward = await paraApeStaking.nftPendingReward( bayc.address, [0, 1, 2] ); - let user2PendingReward = await paraApeStaking.nftPendingReward( + const user2PendingReward = await paraApeStaking.nftPendingReward( mayc.address, [0, 1, 2] ); - let user3PendingReward = await paraApeStaking.nftPendingReward( + const user3PendingReward = await paraApeStaking.nftPendingReward( bakc.address, [0, 1, 2] ); @@ -614,15 +614,15 @@ describe("Para Ape Staking Test", () => { expect(user1Balance).to.be.closeTo(user2Balance, parseEther("50")); expect(user1Balance).to.be.closeTo(user3Balance, parseEther("50")); - let newUser1PendingReward = await paraApeStaking.nftPendingReward( + const newUser1PendingReward = await paraApeStaking.nftPendingReward( bayc.address, [0, 1, 2] ); - let newUser2PendingReward = await paraApeStaking.nftPendingReward( + const newUser2PendingReward = await paraApeStaking.nftPendingReward( mayc.address, [0, 1, 2] ); - let newUser3PendingReward = await paraApeStaking.nftPendingReward( + const newUser3PendingReward = await paraApeStaking.nftPendingReward( bakc.address, [0, 1, 2] ); diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 6608d500f..08b01790f 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -533,7 +533,7 @@ describe("Para Ape staking ape coin pool test", () => { expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); user1PendingReward = await paraApeStaking.apeCoinPoolPendingReward( - false, + false, [0, 1] ); user2PendingReward = await paraApeStaking.apeCoinPoolPendingReward(false, [ @@ -595,7 +595,7 @@ describe("Para Ape staking ape coin pool test", () => { [0, 1] ); user2PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( - false, + false, [2] ); expect(user1PendingReward).to.be.equal(0); @@ -1256,4 +1256,146 @@ describe("Para Ape staking ape coin pool test", () => { }) ).to.be.revertedWith(ProtocolErrors.INVALID_CASH_AMOUNT); }); + + it("sApe revert test", async () => { + const { + users: [user1, user2, liquidator], + ape, + weth, + bayc, + pool, + nBAYC, + } = await loadFixture(fixture); + + await changePriceAndValidate(bayc, "100"); + await changePriceAndValidate(ape, "0.00001"); + + //user1 collateral 200eth + await supplyAndValidate(bayc, "1", user1, true); + + await supplyAndValidate(ape, "2000000", user2, true); + + //user1 borrow value 0.00001 * 2000000 = 20eth + await waitForTx( + await pool + .connect(user1.signer) + .borrow(ape.address, parseEther("2000000"), 0, user1.address) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("200000"), + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.equal( + parseEther("200000") + ); + expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.equal( + parseEther("0") + ); + + await waitForTx( + await pool + .connect(user1.signer) + .setUserUseERC20AsCollateral(sApeAddress, true) + ); + + //user1 borrow value = 2000 eth, collateral value = 100 + 200 = 300 + await changePriceAndValidate(ape, "0.001"); + await changeSApePriceAndValidate(sApeAddress, "0.001"); + + await waitForTx( + await pool + .connect(user1.signer) + .setUserUseERC20AsCollateral(sApeAddress, true) + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(user1.address, parseEther("200000")) + ).to.be.revertedWith(ProtocolErrors.SAPE_FREE_BALANCE_NOT_ENOUGH); + + await mintAndValidate(weth, "200", liquidator); + await waitForTx( + await weth + .connect(liquidator.signer) + .approve(pool.address, MAX_UINT_AMOUNT) + ); + + // start auction + await waitForTx( + await pool + .connect(liquidator.signer) + .startAuction(user1.address, bayc.address, 0) + ); + const auctionData = await pool.getAuctionData(nBAYC.address, 0); + await advanceBlock( + auctionData.startTime + .add(auctionData.tickLength.mul(BigNumber.from(40))) + .toNumber() + ); + + // try to liquidate the NFT + expect( + await pool + .connect(liquidator.signer) + .liquidateERC721( + bayc.address, + user1.address, + 0, + parseEther("100"), + false, + {gasLimit: 5000000} + ) + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("200000"), + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.eq(0); + expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.closeTo( + parseEther("200000"), + parseEther("1") + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(user1.address, parseEther("200000")) + ).to.be.revertedWith( + ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .transferFreeSApeBalance( + user1.address, + user2.address, + parseEther("200000") + ) + ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_ALLOWED); + + await changePriceAndValidate(ape, "0.00001"); + await changeSApePriceAndValidate(sApeAddress, "0.00001"); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(user1.address, parseEther("200000")) + ); + + expect(await cApe.balanceOf(user1.address)).to.be.closeTo( + parseEther("200000"), + parseEther("1") + ); + }); }); diff --git a/test/xtoken_ntoken_bakc.spec.ts b/test/xtoken_ntoken_bakc.spec.ts deleted file mode 100644 index bfe5ab253..000000000 --- a/test/xtoken_ntoken_bakc.spec.ts +++ /dev/null @@ -1,410 +0,0 @@ -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; -import {expect} from "chai"; -import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; -import {convertToCurrencyDecimals} from "../helpers/contracts-helpers"; -import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; -import {TestEnv} from "./helpers/make-suite"; -import {testEnvFixture} from "./helpers/setup-env"; -import { - changePriceAndValidate, - changeSApePriceAndValidate, - mintAndValidate, - supplyAndValidate, -} from "./helpers/validated-steps"; -import {parseEther} from "ethers/lib/utils"; -import {getAutoCompoundApe} from "../helpers/contracts-getters"; - -describe("APE Coin Staking Test", () => { - let testEnv: TestEnv; - const sApeAddress = ONE_ADDRESS; - - const fixture = async () => { - testEnv = await loadFixture(testEnvFixture); - const { - ape, - mayc, - bayc, - users: [user1, depositor, , , , user4], - pool, - apeCoinStaking, - bakc, - } = testEnv; - - const cApe = await getAutoCompoundApe(); - - await supplyAndValidate(ape, "20000", depositor, true); - await changePriceAndValidate(ape, "0.001"); - await changeSApePriceAndValidate(sApeAddress, "0.001"); - - await changePriceAndValidate(mayc, "50"); - await changePriceAndValidate(bayc, "50"); - - await waitForTx( - await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await bakc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - - // send extra tokens to the apestaking contract for rewards - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"]( - apeCoinStaking.address, - parseEther("100000000000") - ) - ); - - // user4 deposit MINIMUM_LIQUIDITY to make test case easy - await mintAndValidate(ape, "1", user4); - await waitForTx( - await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - const MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); - await waitForTx( - await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) - ); - - return testEnv; - }; - - it("user can supply bakc first and stake paired nft", async () => { - const { - users: [user1], - ape, - mayc, - pool, - bakc, - nMAYC, - nBAKC, - } = await loadFixture(fixture); - await supplyAndValidate(bakc, "1", user1, true); - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "10000"); - const halfAmount = await convertToCurrencyDecimals(ape.address, "5000"); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // advance in time - await advanceTimeAndBlock(parseInt("3600")); - await waitForTx( - await pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 0}]) - ); - let apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).to.be.equal(parseEther("3600")); - - await advanceTimeAndBlock(parseInt("3600")); - await waitForTx( - await pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 0}]) - ); - apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).to.be.equal(parseEther("7200")); - - await waitForTx( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: halfAmount, - isUncommit: false, - }, - ]) - ); - - apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).to.be.equal(parseEther("12200")); - - await waitForTx( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: halfAmount, - isUncommit: true, - }, - ]) - ); - - apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).to.be.equal(parseEther("17200")); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawERC721(bakc.address, [0], user1.address) - ); - expect(await nBAKC.balanceOf(user1.address)).to.be.equal(0); - expect(await bakc.balanceOf(user1.address)).to.be.equal(1); - }); - - it("unstakeApePositionAndRepay when bakc in user wallet: bakc reward should transfer to user wallet", async () => { - const { - users: [user1], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await waitForTx(await bakc["mint(uint256,address)"]("1", user1.address)); - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - const amount = await convertToCurrencyDecimals(ape.address, "10000"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const userBalance = await ape.balanceOf(user1.address); - expect(userBalance).to.be.eq(parseEther("86400")); - }); - - it("unstakeApePositionAndRepay when bakc has been supplied: bakc reward should transfer to user wallet", async () => { - const { - users: [user1], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(bakc, "1", user1, true); - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - const amount = await convertToCurrencyDecimals(ape.address, "10000"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const userBalance = await ape.balanceOf(user1.address); - expect(userBalance).to.be.eq(parseEther("86400")); - }); - - it("liquidate bakc will unstake user ape staking position", async () => { - const { - users: [user1, liquidator], - ape, - mayc, - pool, - weth, - bakc, - nMAYC, - } = await loadFixture(fixture); - await supplyAndValidate(ape, "20000", liquidator, true); - await changePriceAndValidate(ape, "0.001"); - await changeSApePriceAndValidate(sApeAddress, "0.001"); - await changePriceAndValidate(mayc, "50"); - await changePriceAndValidate(bakc, "5"); - - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user1, true); - - const amount = parseEther("10000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - await supplyAndValidate(weth, "91", liquidator, true, "200000"); - - await advanceTimeAndBlock(parseInt("3600")); - - // drop HF and ERC-721_HF below 1 - await changePriceAndValidate(ape, "1"); - await changeSApePriceAndValidate(sApeAddress, "1"); - await changePriceAndValidate(mayc, "1"); - await changePriceAndValidate(bakc, "1"); - - // start auction - await waitForTx( - await pool - .connect(liquidator.signer) - .startAuction(user1.address, bakc.address, 0) - ); - - expect(await ape.balanceOf(user1.address)).to.be.equal(0); - // try to liquidate the NFT - expect( - await pool - .connect(liquidator.signer) - .liquidateERC721( - bakc.address, - user1.address, - 0, - parseEther("10"), - false, - {gasLimit: 5000000} - ) - ); - expect(await bakc.ownerOf("0")).to.be.eq(liquidator.address); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - expect(await ape.balanceOf(user1.address)).to.be.equal(parseEther("3600")); - }); - - it("transfer nbakc will unstake user ape staking position", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - bakc, - nBAKC, - nMAYC, - } = await loadFixture(fixture); - await mintAndValidate(ape, "10000", user1); - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user1, true); - - const amount = parseEther("10000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - await advanceTimeAndBlock(parseInt("3600")); - - expect(await nBAKC.ownerOf("0")).to.be.eq(user1.address); - expect(await ape.balanceOf(user1.address)).to.be.equal(0); - expect( - await nBAKC - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user2.address, - 0, - {gasLimit: 5000000} - ) - ); - expect(await nBAKC.ownerOf("0")).to.be.eq(user2.address); - expect(await ape.balanceOf(user1.address)).to.be.equal(parseEther("3600")); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - }); - - it("withdraw bakc will not unstake user ape staking position", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - bakc, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(ape, "20000", user2, true); - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user1, true); - - const amount = parseEther("10000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}], - true - ) - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // start auction - await waitForTx( - await pool - .connect(user1.signer) - .withdrawERC721(bakc.address, [0], user1.address) - ); - - expect(await bakc.ownerOf("0")).to.be.eq(user1.address); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - }); -}); From 08c01a8dc9b6cc596b9e9b7d8974156f91fe037d Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 25 Jul 2023 09:11:29 +0800 Subject: [PATCH 65/99] chore: remove ApeCoinPoolState --- contracts/apestaking/ParaApeStaking.sol | 2 +- .../apestaking/logic/ApeCoinPoolLogic.sol | 42 +++++++++---------- contracts/interfaces/IParaApeStaking.sol | 9 ---- 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 53cc4ddf5..cdc27ba65 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -264,7 +264,7 @@ contract ParaApeStaking is /* *Ape Coin Staking Pool Logic */ - mapping(uint256 => ApeCoinPoolState) internal apeCoinPoolStates; + mapping(uint256 => PoolState) internal apeCoinPoolStates; function depositApeCoinPool(ApeCoinActionInfo calldata depositInfo) external diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 9e96f76fc..882484222 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -86,7 +86,7 @@ library ApeCoinPoolLogic { } function depositApeCoinPool( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, @@ -157,11 +157,11 @@ library ApeCoinPoolLogic { vars.apeCoinStaking.depositMAYC(_nfts); } - poolState.totalPosition += arrayLength.toUint32(); + poolState.totalPosition += arrayLength.toUint24(); } function compoundApeCoinPool( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, @@ -204,7 +204,7 @@ library ApeCoinPoolLogic { } function claimApeCoinPool( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata tokenIds @@ -215,7 +215,7 @@ library ApeCoinPoolLogic { } function withdrawApeCoinPool( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, mapping(address => uint256) storage cApeShareBalance, @@ -286,8 +286,8 @@ library ApeCoinPoolLogic { ); //distribute reward - uint32 totalPosition = poolState.totalPosition; - totalPosition -= arrayLength.toUint32(); + uint24 totalPosition = poolState.totalPosition; + totalPosition -= arrayLength.toUint24(); if (vars.totalClaimedApe > totalApeCoinAmount) { _distributePoolReward( poolState, @@ -316,7 +316,7 @@ library ApeCoinPoolLogic { } function depositApeCoinPairPool( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, @@ -409,11 +409,11 @@ library ApeCoinPoolLogic { vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); } - poolState.totalPosition += arrayLength.toUint32(); + poolState.totalPosition += arrayLength.toUint24(); } function compoundApeCoinPairPool( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, @@ -467,7 +467,7 @@ library ApeCoinPoolLogic { } function claimApeCoinPairPool( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] calldata tokenIds @@ -478,7 +478,7 @@ library ApeCoinPoolLogic { } function withdrawApeCoinPairPool( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, mapping(address => uint256) storage cApeShareBalance, @@ -567,8 +567,8 @@ library ApeCoinPoolLogic { ); //distribute reward - uint32 totalPosition = poolState.totalPosition; - totalPosition -= arrayLength.toUint32(); + uint24 totalPosition = poolState.totalPosition; + totalPosition -= arrayLength.toUint24(); if (vars.totalClaimedApe > totalApeCoinAmount) { _distributePoolReward( poolState, @@ -607,8 +607,8 @@ library ApeCoinPoolLogic { } function tryUnstakeApeCoinPoolPosition( - IParaApeStaking.ApeCoinPoolState storage singlePoolState, - IParaApeStaking.ApeCoinPoolState storage pairPoolState, + IParaApeStaking.PoolState storage singlePoolState, + IParaApeStaking.PoolState storage pairPoolState, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, mapping(address => uint256) storage cApeShareBalance, @@ -700,7 +700,7 @@ library ApeCoinPoolLogic { } function calculatePendingReward( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, uint32[] memory tokenIds @@ -858,7 +858,7 @@ library ApeCoinPoolLogic { } function _claimApeCoinPool( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, bool isSinglePool, @@ -896,11 +896,11 @@ library ApeCoinPoolLogic { } function _distributePoolReward( - IParaApeStaking.ApeCoinPoolState storage poolState, + IParaApeStaking.PoolState storage poolState, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint256 rewardAmount, - uint32 totalPosition + uint24 totalPosition ) internal { IAutoCompoundApe(vars.cApe).deposit(address(this), rewardAmount); @@ -910,7 +910,7 @@ library ApeCoinPoolLogic { compoundFee = cApeShare.percentMul(vars.compoundFee); cApeShare -= compoundFee; poolState.accumulatedRewardsPerNft += - cApeShare.toUint128() / + cApeShare.toUint104() / totalPosition; } diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index cbbd22c48..fca7e4caa 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -96,15 +96,6 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { mapping(uint256 => TokenStatus) tokenStatus; } - struct ApeCoinPoolState { - // total NFT position count - uint32 totalPosition; - // accumulated cApe reward for per NFT position - uint128 accumulatedRewardsPerNft; - //tokenId => reward debt position - mapping(uint256 => TokenStatus) tokenStatus; - } - struct VaultStorage { mapping(uint256 => PoolState) poolStates; BAKCPoolState bakcPoolState; From 1c5c943afa924c45f6202d127ec5d8ce3a6761c6 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 25 Jul 2023 16:40:31 +0800 Subject: [PATCH 66/99] chore: refactor pool state and bakc single pool --- contracts/apestaking/ParaApeStaking.sol | 229 +++---- .../apestaking/logic/ApeCoinPoolLogic.sol | 9 - .../logic/ApeStakingCommonLogic.sol | 14 + .../logic/ApeStakingPairPoolLogic.sol | 19 +- .../logic/ApeStakingSinglePoolLogic.sol | 588 ++++++++---------- contracts/interfaces/IApeStakingVault.sol | 38 +- contracts/interfaces/IParaApeStaking.sol | 38 +- test/para_ape_staking.spec.ts | 281 +++++---- 8 files changed, 589 insertions(+), 627 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index cdc27ba65..a17ccbf9e 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -12,7 +12,6 @@ import "../dependencies/yoga-labs/ApeCoinStaking.sol"; import "../interfaces/IACLManager.sol"; import "../interfaces/ICApe.sol"; import {PercentageMath} from "../protocol/libraries/math/PercentageMath.sol"; -import {WadRayMath} from "../protocol/libraries/math/WadRayMath.sol"; import "./logic/ApeStakingP2PLogic.sol"; import "./logic/ApeStakingPairPoolLogic.sol"; import "./logic/ApeStakingSinglePoolLogic.sol"; @@ -29,8 +28,6 @@ contract ParaApeStaking is { using SafeERC20 for IERC20; using SafeCast for uint256; - using PercentageMath for uint256; - using WadRayMath for uint256; //keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); bytes32 internal constant EIP712_DOMAIN = @@ -53,12 +50,24 @@ contract ParaApeStaking is uint16 private immutable sApeReserveId; address private immutable psApe; + //record all pool states + mapping(uint256 => PoolState) internal poolStates; + + //record user sApe balance + mapping(address => SApeBalance) private sApeBalance; + + //P2P storage + bytes32 internal DOMAIN_SEPARATOR; + mapping(bytes32 => ListingOrderStatus) public listingOrderStatus; + mapping(bytes32 => MatchedOrder) public matchedOrders; + //record Ape in P2P and ApeCoin pool mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; mapping(address => uint256) private cApeShareBalance; - mapping(address => SApeBalance) private sApeBalance; + address public apeStakingBot; uint64 public compoundFee; + uint32 apePairStakingRewardRatio; constructor( address _pool, @@ -264,8 +273,6 @@ contract ParaApeStaking is /* *Ape Coin Staking Pool Logic */ - mapping(uint256 => PoolState) internal apeCoinPoolStates; - function depositApeCoinPool(ApeCoinActionInfo calldata depositInfo) external whenNotPaused @@ -273,10 +280,10 @@ contract ParaApeStaking is { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = depositInfo.isBAYC - ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; ApeCoinPoolLogic.depositApeCoinPool( - apeCoinPoolStates[poolId], + poolStates[poolId], apeMatchedCount, sApeBalance, vars, @@ -291,10 +298,10 @@ contract ParaApeStaking is ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC - ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; ApeCoinPoolLogic.compoundApeCoinPool( - apeCoinPoolStates[poolId], + poolStates[poolId], cApeShareBalance, vars, isBAYC, @@ -309,10 +316,10 @@ contract ParaApeStaking is { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; (, uint256 pendingReward, ) = ApeCoinPoolLogic.calculatePendingReward( - apeCoinPoolStates[poolId], + poolStates[poolId], vars, isBAYC, tokenIds @@ -327,10 +334,10 @@ contract ParaApeStaking is { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; ApeCoinPoolLogic.claimApeCoinPool( - apeCoinPoolStates[poolId], + poolStates[poolId], vars, isBAYC, tokenIds @@ -346,10 +353,10 @@ contract ParaApeStaking is vars.compoundFee = compoundFee; vars.sApeReserveId = sApeReserveId; uint256 poolId = withdrawInfo.isBAYC - ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; ApeCoinPoolLogic.withdrawApeCoinPool( - apeCoinPoolStates[poolId], + poolStates[poolId], apeMatchedCount, sApeBalance, cApeShareBalance, @@ -365,10 +372,10 @@ contract ParaApeStaking is { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = depositInfo.isBAYC - ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; ApeCoinPoolLogic.depositApeCoinPairPool( - apeCoinPoolStates[poolId], + poolStates[poolId], apeMatchedCount, sApeBalance, vars, @@ -384,10 +391,10 @@ contract ParaApeStaking is ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC - ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; ApeCoinPoolLogic.compoundApeCoinPairPool( - apeCoinPoolStates[poolId], + poolStates[poolId], cApeShareBalance, vars, isBAYC, @@ -402,10 +409,10 @@ contract ParaApeStaking is ) external view returns (uint256) { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; (, uint256 pendingReward, ) = ApeCoinPoolLogic.calculatePendingReward( - apeCoinPoolStates[poolId], + poolStates[poolId], vars, isBAYC, apeTokenIds @@ -420,10 +427,10 @@ contract ParaApeStaking is { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; ApeCoinPoolLogic.claimApeCoinPairPool( - apeCoinPoolStates[poolId], + poolStates[poolId], vars, isBAYC, apeTokenIds @@ -437,10 +444,10 @@ contract ParaApeStaking is vars.compoundFee = compoundFee; vars.sApeReserveId = sApeReserveId; uint256 poolId = withdrawInfo.isBAYC - ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; ApeCoinPoolLogic.withdrawApeCoinPairPool( - apeCoinPoolStates[poolId], + poolStates[poolId], apeMatchedCount, sApeBalance, cApeShareBalance, @@ -455,15 +462,15 @@ contract ParaApeStaking is ) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 singlePoolId = isBAYC - ? ApeCoinPoolLogic.BAYC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; uint256 PairPoolId = isBAYC - ? ApeCoinPoolLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeCoinPoolLogic.MAYC_BAKC_APECOIN_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; ApeCoinPoolLogic.tryUnstakeApeCoinPoolPosition( - apeCoinPoolStates[singlePoolId], - apeCoinPoolStates[PairPoolId], + poolStates[singlePoolId], + poolStates[PairPoolId], apeMatchedCount, sApeBalance, cApeShareBalance, @@ -477,10 +484,6 @@ contract ParaApeStaking is * P2P Pair Staking Logic */ - bytes32 internal DOMAIN_SEPARATOR; - mapping(bytes32 => ListingOrderStatus) public listingOrderStatus; - mapping(bytes32 => MatchedOrder) public matchedOrders; - /// @inheritdoc IApeStakingP2P function cancelListing(ListingOrder calldata listingOrder) external @@ -628,21 +631,18 @@ contract ParaApeStaking is * Ape Staking Vault Logic */ - VaultStorage internal vaultStorage; - - function setSinglePoolApeRewardRatio( - uint128 baycRewardRatio, - uint128 maycRewardRatio - ) external onlyPoolAdmin { - uint128 oldValue = vaultStorage.baycPairStakingRewardRatio; - if (oldValue != baycRewardRatio) { - vaultStorage.baycPairStakingRewardRatio = baycRewardRatio; - emit BaycPairStakingRewardRatioUpdated(oldValue, baycRewardRatio); - } - oldValue = vaultStorage.maycPairStakingRewardRatio; - if (oldValue != maycRewardRatio) { - vaultStorage.maycPairStakingRewardRatio = maycRewardRatio; - emit MaycPairStakingRewardRatioUpdated(oldValue, maycRewardRatio); + function setSinglePoolApeRewardRatio(uint32 apeRewardRatio) + external + onlyPoolAdmin + { + require( + apeRewardRatio < PercentageMath.PERCENTAGE_FACTOR, + Errors.INVALID_PARAMETER + ); + uint32 oldValue = apePairStakingRewardRatio; + if (oldValue != apeRewardRatio) { + apePairStakingRewardRatio = apeRewardRatio; + emit ApePairStakingRewardRatioUpdated(oldValue, apeRewardRatio); } } @@ -654,10 +654,10 @@ contract ParaApeStaking is ) external override whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingPairPoolLogic.depositPairNFT( - vaultStorage.poolStates[poolId], + poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -673,10 +673,10 @@ contract ParaApeStaking is ) external override whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingPairPoolLogic.stakingPairNFT( - vaultStorage.poolStates[poolId], + poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -693,10 +693,10 @@ contract ParaApeStaking is ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC - ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingPairPoolLogic.compoundPairNFT( - vaultStorage.poolStates[poolId], + poolStates[poolId], cApeShareBalance, vars, isBAYC, @@ -713,11 +713,11 @@ contract ParaApeStaking is ) external view override returns (uint256) { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; (, uint256 pendingReward, ) = ApeStakingPairPoolLogic .calculatePendingReward( - vaultStorage.poolStates[poolId], + poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -735,10 +735,10 @@ contract ParaApeStaking is ) external override whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingPairPoolLogic.claimPairNFT( - vaultStorage.poolStates[poolId], + poolStates[poolId], vars, isBAYC, apeTokenIds, @@ -755,10 +755,10 @@ contract ParaApeStaking is ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC - ? ApeStakingPairPoolLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingPairPoolLogic.MAYC_BAKC_PAIR_POOL_ID; + ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; ApeStakingPairPoolLogic.withdrawPairNFT( - vaultStorage.poolStates[poolId], + poolStates[poolId], cApeShareBalance, vars, isBAYC, @@ -779,7 +779,17 @@ contract ParaApeStaking is Errors.NFT_NOT_ALLOWED ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - ApeStakingSinglePoolLogic.depositNFT(vaultStorage, vars, nft, tokenIds); + uint256 poolId = (nft == bayc) + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : (nft == mayc) + ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; + ApeStakingSinglePoolLogic.depositNFT( + poolStates[poolId], + vars, + nft, + tokenIds + ); } /// @inheritdoc IApeStakingVault @@ -791,10 +801,10 @@ contract ParaApeStaking is { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC - ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID - : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID; ApeStakingSinglePoolLogic.stakingApe( - vaultStorage.poolStates[poolId], + poolStates[poolId], vars, isBAYC, tokenIds @@ -802,23 +812,14 @@ contract ParaApeStaking is } /// @inheritdoc IApeStakingVault - function stakingBAKC( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external override whenNotPaused nonReentrant { + function stakingBAKC(BAKCPairActionInfo calldata actionInfo) + external + override + whenNotPaused + nonReentrant + { ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = isBAYC - ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID - : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; - ApeStakingSinglePoolLogic.stakingBAKC( - vaultStorage.poolStates[poolId], - vaultStorage.bakcPoolState, - vars, - isBAYC, - apeTokenIds, - bakcTokenIds - ); + ApeStakingSinglePoolLogic.stakingBAKC(poolStates, vars, actionInfo); } /// @inheritdoc IApeStakingVault @@ -830,10 +831,10 @@ contract ParaApeStaking is ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; uint256 poolId = isBAYC - ? ApeStakingPairPoolLogic.BAYC_SINGLE_POOL_ID - : ApeStakingPairPoolLogic.MAYC_SINGLE_POOL_ID; + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID; ApeStakingSinglePoolLogic.compoundApe( - vaultStorage.poolStates[poolId], + poolStates[poolId], cApeShareBalance, vars, isBAYC, @@ -842,20 +843,19 @@ contract ParaApeStaking is } /// @inheritdoc IApeStakingVault - function compoundBAKC( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external override onlyApeStakingBot { + function compoundBAKC(BAKCPairActionInfo calldata actionInfo) + external + override + onlyApeStakingBot + { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; + vars.apeRewardRatio = apePairStakingRewardRatio; ApeStakingSinglePoolLogic.compoundBAKC( - vaultStorage, + poolStates, cApeShareBalance, vars, - isBAYC, - apeTokenIds, - bakcTokenIds + actionInfo ); } @@ -872,7 +872,7 @@ contract ParaApeStaking is ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 pendingReward = ApeStakingSinglePoolLogic - .calculatePendingReward(vaultStorage, vars, nft, tokenIds); + .calculatePendingReward(poolStates, vars, nft, tokenIds); return pendingReward; } @@ -889,7 +889,7 @@ contract ParaApeStaking is Errors.NFT_NOT_ALLOWED ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - ApeStakingSinglePoolLogic.claimNFT(vaultStorage, vars, nft, tokenIds); + ApeStakingSinglePoolLogic.claimNFT(poolStates, vars, nft, tokenIds); } /// @inheritdoc IApeStakingVault @@ -905,8 +905,9 @@ contract ParaApeStaking is ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; + vars.apeRewardRatio = apePairStakingRewardRatio; ApeStakingSinglePoolLogic.withdrawNFT( - vaultStorage, + poolStates, cApeShareBalance, vars, nft, diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 882484222..63cab5c57 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -27,15 +27,6 @@ library ApeCoinPoolLogic { using SafeERC20 for IERC20; using WadRayMath for uint256; - uint256 public constant BAYC_APECOIN_POOL_ID = 1; - uint256 public constant MAYC_APECOIN_POOL_ID = 2; - uint256 public constant BAYC_BAKC_APECOIN_POOL_ID = 3; - uint256 public constant MAYC_BAKC_APECOIN_POOL_ID = 4; - - uint256 constant BAYC_POOL_ID = 1; - uint256 constant MAYC_POOL_ID = 2; - uint256 constant BAKC_POOL_ID = 3; - /** * @dev Minimum health factor to consider a user position healthy * A value of 1e18 results in 1 diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 0e6c520a7..f346c800e 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -20,6 +20,20 @@ library ApeStakingCommonLogic { using SafeCast for uint256; using WadRayMath for uint256; + uint256 public constant BAYC_BAKC_PAIR_POOL_ID = 1; + uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; + uint256 public constant BAYC_SINGLE_POOL_ID = 3; + uint256 public constant MAYC_SINGLE_POOL_ID = 4; + uint256 public constant BAKC_SINGLE_POOL_ID = 5; + uint256 public constant BAYC_APECOIN_POOL_ID = 6; + uint256 public constant MAYC_APECOIN_POOL_ID = 7; + uint256 public constant BAYC_BAKC_APECOIN_POOL_ID = 8; + uint256 public constant MAYC_BAKC_APECOIN_POOL_ID = 9; + + uint256 public constant BAYC_POOL_ID = 1; + uint256 public constant MAYC_POOL_ID = 2; + uint256 public constant BAKC_POOL_ID = 3; + function validateTokenIdArray(uint32[] calldata tokenIds) internal pure { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 12b1fc271..c9b93cc38 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -24,15 +24,6 @@ library ApeStakingPairPoolLogic { using SafeERC20 for IERC20; using WadRayMath for uint256; - uint256 public constant BAYC_BAKC_PAIR_POOL_ID = 1; - uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; - uint256 public constant BAYC_SINGLE_POOL_ID = 3; - uint256 public constant MAYC_SINGLE_POOL_ID = 4; - - uint256 constant BAYC_POOL_ID = 1; - uint256 constant MAYC_POOL_ID = 2; - uint256 constant BAKC_POOL_ID = 3; - event PairNFTDeposited( bool isBAYC, uint256 apeTokenId, @@ -61,11 +52,11 @@ library ApeStakingPairPoolLogic { ); if (isBAYC) { - vars.apeStakingPoolId = BAYC_POOL_ID; + vars.apeStakingPoolId = ApeStakingCommonLogic.BAYC_POOL_ID; vars.apeToken = vars.bayc; vars.nApe = vars.nBayc; } else { - vars.apeStakingPoolId = MAYC_POOL_ID; + vars.apeStakingPoolId = ApeStakingCommonLogic.MAYC_POOL_ID; vars.apeToken = vars.mayc; vars.nApe = vars.nMayc; } @@ -93,7 +84,7 @@ library ApeStakingPairPoolLogic { ); require(stakedAmount == 0, Errors.APE_POSITION_EXISTED); (stakedAmount, ) = vars.apeCoinStaking.nftPosition( - BAKC_POOL_ID, + ApeStakingCommonLogic.BAKC_POOL_ID, bakcTokenId ); require(stakedAmount == 0, Errors.BAKC_POSITION_EXISTED); @@ -230,12 +221,12 @@ library ApeStakingPairPoolLogic { ); if (isBAYC) { - vars.apeStakingPoolId = BAYC_POOL_ID; + vars.apeStakingPoolId = ApeStakingCommonLogic.BAYC_POOL_ID; vars.apeToken = vars.bayc; vars.nApe = vars.nBayc; vars.positionCap = vars.baycMatchedCap; } else { - vars.apeStakingPoolId = MAYC_POOL_ID; + vars.apeStakingPoolId = ApeStakingCommonLogic.MAYC_POOL_ID; vars.apeToken = vars.mayc; vars.nApe = vars.nMayc; vars.positionCap = vars.maycMatchedCap; diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index e90ac1494..86821de96 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -12,6 +12,7 @@ import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import "./ApeStakingCommonLogic.sol"; import "../../protocol/libraries/helpers/Errors.sol"; +import "hardhat/console.sol"; /** * @title ApeStakingSinglePoolLogic library @@ -24,15 +25,6 @@ library ApeStakingSinglePoolLogic { using SafeERC20 for IERC20; using WadRayMath for uint256; - uint256 public constant BAYC_BAKC_PAIR_POOL_ID = 1; - uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; - uint256 public constant BAYC_SINGLE_POOL_ID = 3; - uint256 public constant MAYC_SINGLE_POOL_ID = 4; - - uint256 constant BAYC_POOL_ID = 1; - uint256 constant MAYC_POOL_ID = 2; - uint256 constant BAKC_POOL_ID = 3; - event NFTDeposited(address nft, uint256 tokenId); event ApeStaked(bool isBAYC, uint256 tokenId); event BakcStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); @@ -42,7 +34,7 @@ library ApeStakingSinglePoolLogic { event NFTWithdrawn(address nft, uint256 tokenId); function depositNFT( - IParaApeStaking.VaultStorage storage vaultStorage, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds @@ -50,27 +42,20 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - uint128 accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( - vaultStorage, - vars, - nft - ); - mapping(uint256 => IParaApeStaking.TokenStatus) - storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); - address nToken; uint256 apeStakingPoolId; if (nft == vars.bayc) { nToken = vars.nBayc; - apeStakingPoolId = BAYC_POOL_ID; + apeStakingPoolId = ApeStakingCommonLogic.BAYC_POOL_ID; } else if (nft == vars.mayc) { nToken = vars.nMayc; - apeStakingPoolId = MAYC_POOL_ID; + apeStakingPoolId = ApeStakingCommonLogic.MAYC_POOL_ID; } else { nToken = vars.nBakc; - apeStakingPoolId = BAKC_POOL_ID; + apeStakingPoolId = ApeStakingCommonLogic.BAKC_POOL_ID; } address msgSender = msg.sender; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -95,7 +80,7 @@ library ApeStakingSinglePoolLogic { IERC721(nft).safeTransferFrom(nToken, address(this), tokenId); //update token status - tokenStatus[tokenId] = IParaApeStaking.TokenStatus({ + poolState.tokenStatus[tokenId] = IParaApeStaking.TokenStatus({ rewardsDebt: accumulatedRewardsPerNft, isInPool: true, bakcTokenId: 0, @@ -107,17 +92,7 @@ library ApeStakingSinglePoolLogic { } //update state - if (nft == vars.bayc) { - vaultStorage - .poolStates[BAYC_SINGLE_POOL_ID] - .totalPosition += arrayLength.toUint24(); - } else if (nft == vars.mayc) { - vaultStorage - .poolStates[MAYC_SINGLE_POOL_ID] - .totalPosition += arrayLength.toUint24(); - } else { - vaultStorage.bakcPoolState.totalPosition += arrayLength.toUint24(); - } + poolState.totalPosition += arrayLength.toUint24(); } function stakingApe( @@ -171,29 +146,63 @@ library ApeStakingSinglePoolLogic { } function stakingBAKC( - IParaApeStaking.PoolState storage apePoolState, - IParaApeStaking.BAKCPoolState storage bakcPoolState, + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds + IParaApeStaking.BAKCPairActionInfo calldata actionInfo ) external { - uint256 arrayLength = apeTokenIds.length; - require( - arrayLength == bakcTokenIds.length && arrayLength > 0, - Errors.INVALID_PARAMETER - ); + _validateBAKCPairActionInfo(actionInfo); + uint256 baycArrayLength = actionInfo.baycTokenIds.length; + uint256 maycArrayLength = actionInfo.maycTokenIds.length; ApeCoinStaking.PairNftDepositWithAmount[] - memory _nftPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - arrayLength + memory _baycPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + baycArrayLength ); - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; + ApeCoinStaking.PairNftDepositWithAmount[] + memory _maycPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + maycArrayLength + ); + IParaApeStaking.PoolState storage bakcPoolState = poolStates[ + ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID + ]; + for (uint256 index = 0; index < baycArrayLength; index++) { + uint32 apeTokenId = actionInfo.baycTokenIds[index]; + uint32 bakcTokenId = actionInfo.bakcPairBaycTokenIds[index]; + + IParaApeStaking.PoolState storage baycPoolState = poolStates[ + ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + ]; + + require( + baycPoolState.tokenStatus[apeTokenId].isInPool, + Errors.NFT_NOT_IN_POOL + ); + require( + bakcPoolState.tokenStatus[bakcTokenId].isInPool, + Errors.NFT_NOT_IN_POOL + ); + + // construct staking data + _baycPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: vars.bakcMatchedCap.toUint184() + }); + + //emit event + emit BakcStaked(true, apeTokenId, bakcTokenId); + } + + for (uint256 index = 0; index < maycArrayLength; index++) { + uint32 apeTokenId = actionInfo.maycTokenIds[index]; + uint32 bakcTokenId = actionInfo.bakcPairMaycTokenIds[index]; + + IParaApeStaking.PoolState storage maycPoolState = poolStates[ + ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + ]; require( - apePoolState.tokenStatus[apeTokenId].isInPool, + maycPoolState.tokenStatus[apeTokenId].isInPool, Errors.NFT_NOT_IN_POOL ); require( @@ -202,37 +211,31 @@ library ApeStakingSinglePoolLogic { ); // construct staking data - _nftPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ + _maycPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ mainTokenId: apeTokenId, bakcTokenId: bakcTokenId, amount: vars.bakcMatchedCap.toUint184() }); //emit event - emit BakcStaked(isBAYC, apeTokenId, bakcTokenId); + emit BakcStaked(false, apeTokenId, bakcTokenId); } // prepare Ape coin - uint256 totalBorrow = vars.bakcMatchedCap * arrayLength; + uint256 totalBorrow = vars.bakcMatchedCap * + (baycArrayLength + maycArrayLength); uint256 cApeDebtShare = ApeStakingCommonLogic.borrowCApeFromPool( vars, totalBorrow ); //stake in ApeCoinStaking - ApeCoinStaking.PairNftDepositWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 0 - ); - if (isBAYC) { - vars.apeCoinStaking.depositBAKC(_nftPairs, _otherPairs); - bakcPoolState.baycStakingPosition += arrayLength.toUint24(); - bakcPoolState.baycCApeDebtShare += cApeDebtShare.toUint104(); - } else { - vars.apeCoinStaking.depositBAKC(_otherPairs, _nftPairs); - bakcPoolState.maycStakingPosition += arrayLength.toUint24(); - bakcPoolState.maycCApeDebtShare += cApeDebtShare.toUint104(); - } + vars.apeCoinStaking.depositBAKC(_baycPairs, _maycPairs); + + //update bakc pool state + bakcPoolState.stakingPosition += (baycArrayLength + maycArrayLength) + .toUint24(); + bakcPoolState.cApeDebtShare += cApeDebtShare.toUint104(); } function compoundApe( @@ -295,70 +298,72 @@ library ApeStakingSinglePoolLogic { } function compoundBAKC( - IParaApeStaking.VaultStorage storage vaultStorage, + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds + IParaApeStaking.BAKCPairActionInfo calldata actionInfo ) external { - uint256 arrayLength = apeTokenIds.length; - require( - arrayLength == bakcTokenIds.length && arrayLength > 0, - Errors.INVALID_PARAMETER - ); + _validateBAKCPairActionInfo(actionInfo); + uint256 baycArrayLength = actionInfo.baycTokenIds.length; + uint256 maycArrayLength = actionInfo.maycTokenIds.length; + + ApeCoinStaking.PairNft[] + memory _baycPairs = new ApeCoinStaking.PairNft[](baycArrayLength); + ApeCoinStaking.PairNft[] + memory _maycPairs = new ApeCoinStaking.PairNft[](maycArrayLength); + IParaApeStaking.PoolState storage bakcPoolState = poolStates[ + ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID + ]; + + for (uint256 index = 0; index < baycArrayLength; index++) { + uint32 apeTokenId = actionInfo.baycTokenIds[index]; + uint32 bakcTokenId = actionInfo.bakcPairBaycTokenIds[index]; + + // we just need to check bakc is in the pool + require( + bakcPoolState.tokenStatus[bakcTokenId].isInPool, + Errors.NFT_NOT_IN_POOL + ); - IParaApeStaking.PoolState storage apePoolState; - if (isBAYC) { - apePoolState = vaultStorage.poolStates[BAYC_SINGLE_POOL_ID]; - vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; - } else { - apePoolState = vaultStorage.poolStates[MAYC_SINGLE_POOL_ID]; - vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; - } - IParaApeStaking.BAKCPoolState storage bakcPoolState = vaultStorage - .bakcPoolState; + // construct staking data + _baycPairs[index] = ApeCoinStaking.PairNft({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId + }); - { - ApeCoinStaking.PairNft[] - memory _nftPairs = new ApeCoinStaking.PairNft[](arrayLength); - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; + //emit event + emit BakcCompounded(true, apeTokenId, bakcTokenId); + } - // we just need to check bakc is in the pool - require( - bakcPoolState.tokenStatus[bakcTokenId].isInPool, - Errors.NFT_NOT_IN_POOL - ); + for (uint256 index = 0; index < maycArrayLength; index++) { + uint32 apeTokenId = actionInfo.maycTokenIds[index]; + uint32 bakcTokenId = actionInfo.bakcPairMaycTokenIds[index]; - // construct staking data - _nftPairs[index] = ApeCoinStaking.PairNft({ - mainTokenId: apeTokenId, - bakcTokenId: bakcTokenId - }); + // we just need to check bakc is in the pool + require( + bakcPoolState.tokenStatus[bakcTokenId].isInPool, + Errors.NFT_NOT_IN_POOL + ); - //emit event - emit BakcCompounded(isBAYC, apeTokenId, bakcTokenId); - } + // construct staking data + _maycPairs[index] = ApeCoinStaking.PairNft({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId + }); - vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - //claim from ApeCoinStaking - ApeCoinStaking.PairNft[] - memory _otherPairs = new ApeCoinStaking.PairNft[](0); - if (isBAYC) { - vars.apeCoinStaking.claimSelfBAKC(_nftPairs, _otherPairs); - } else { - vars.apeCoinStaking.claimSelfBAKC(_otherPairs, _nftPairs); - } - vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); + //emit event + emit BakcCompounded(false, apeTokenId, bakcTokenId); } + vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); + vars.apeCoinStaking.claimSelfBAKC(_baycPairs, _maycPairs); + vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; + IAutoCompoundApe(vars.cApe).deposit( + address(this), + vars.totalClaimedApe + ); + //repay and compound vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( WadRayMath.RAY @@ -368,12 +373,7 @@ library ApeStakingSinglePoolLogic { ( vars.totalRepay, vars.totalCompoundFee - ) = _calculateRepayAndCompoundBAKC( - apePoolState, - bakcPoolState, - vars, - isBAYC - ); + ) = _calculateRepayAndCompoundBAKC(poolStates, vars); if (vars.totalRepay > 0) { IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); @@ -384,26 +384,24 @@ library ApeStakingSinglePoolLogic { } function claimNFT( - IParaApeStaking.VaultStorage storage vaultStorage, + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds ) external { ApeStakingCommonLogic.validateTokenIdArray(tokenIds); - vars.accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( - vaultStorage, - vars, - nft - ); - mapping(uint256 => IParaApeStaking.TokenStatus) - storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); - - _claimNFT(tokenStatus, vars, true, nft, tokenIds); + uint256 poolId = (nft == vars.bayc) + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : (nft == vars.mayc) + ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; + IParaApeStaking.PoolState storage poolState = poolStates[poolId]; + _claimNFT(poolState, vars, true, nft, tokenIds); } function withdrawNFT( - IParaApeStaking.VaultStorage storage vaultStorage, + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, @@ -412,35 +410,33 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - vars.accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( - vaultStorage, - vars, - nft - ); - mapping(uint256 => IParaApeStaking.TokenStatus) - storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); + uint256 poolId = (nft == vars.bayc) + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : (nft == vars.mayc) + ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; //claim pending reward - address nApeOwner = _claimNFT(tokenStatus, vars, false, nft, tokenIds); - - address nToken; + IParaApeStaking.PoolState storage poolState = poolStates[poolId]; + address nApeOwner = _claimNFT(poolState, vars, false, nft, tokenIds); if (nft == vars.bayc) { - _unstakeApe(vaultStorage, cApeShareBalance, vars, true, tokenIds); - nToken = vars.nBayc; + _unstakeApe(poolStates, cApeShareBalance, vars, true, tokenIds); } else if (nft == vars.mayc) { - _unstakeApe(vaultStorage, cApeShareBalance, vars, false, tokenIds); - nToken = vars.nMayc; + _unstakeApe(poolStates, cApeShareBalance, vars, false, tokenIds); } else { - _unstakeBAKC(vaultStorage, cApeShareBalance, vars, tokenIds); - nToken = vars.nBakc; + _unstakeBAKC(poolStates, cApeShareBalance, vars, tokenIds); } //transfer nft back to nToken require(msg.sender == nApeOwner, Errors.NOT_THE_OWNER); + + address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; - delete tokenStatus[tokenId]; + delete poolState.tokenStatus[tokenId]; IERC721(nft).safeTransferFrom(address(this), nToken, tokenId); @@ -450,7 +446,7 @@ library ApeStakingSinglePoolLogic { } function _unstakeApe( - IParaApeStaking.VaultStorage storage vaultStorage, + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, @@ -458,17 +454,18 @@ library ApeStakingSinglePoolLogic { ) internal { IParaApeStaking.PoolState storage apePoolState; if (isBAYC) { - vars.apeStakingPoolId = BAYC_POOL_ID; + vars.apeStakingPoolId = ApeStakingCommonLogic.BAYC_POOL_ID; vars.positionCap = vars.baycMatchedCap; - apePoolState = vaultStorage.poolStates[BAYC_SINGLE_POOL_ID]; - vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; + apePoolState = poolStates[ + ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + ]; } else { - vars.apeStakingPoolId = MAYC_POOL_ID; + vars.apeStakingPoolId = ApeStakingCommonLogic.MAYC_POOL_ID; vars.positionCap = vars.maycMatchedCap; - apePoolState = vaultStorage.poolStates[MAYC_SINGLE_POOL_ID]; - vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; + apePoolState = poolStates[ + ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + ]; } - apePoolState.totalPosition -= tokenIds.length.toUint24(); ApeCoinStaking.SingleNft[] @@ -516,13 +513,10 @@ library ApeStakingSinglePoolLogic { } apePoolState.stakingPosition -= singleStakingCount; - IParaApeStaking.BAKCPoolState storage bakcPoolState = vaultStorage - .bakcPoolState; - if (isBAYC) { - bakcPoolState.baycStakingPosition -= pairStakingCount; - } else { - bakcPoolState.maycStakingPosition -= pairStakingCount; - } + IParaApeStaking.PoolState storage bakcPoolState = poolStates[ + ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID + ]; + bakcPoolState.stakingPosition -= pairStakingCount; vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( WadRayMath.RAY @@ -580,12 +574,7 @@ library ApeStakingSinglePoolLogic { ( uint256 bakcTotalRepay, uint256 bakcCompoundFee - ) = _calculateRepayAndCompoundBAKC( - apePoolState, - bakcPoolState, - vars, - isBAYC - ); + ) = _calculateRepayAndCompoundBAKC(poolStates, vars); vars.totalRepay += bakcTotalRepay; vars.totalCompoundFee += bakcCompoundFee; } @@ -599,15 +588,16 @@ library ApeStakingSinglePoolLogic { } function _unstakeBAKC( - IParaApeStaking.VaultStorage storage vaultStorage, + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint32[] calldata tokenIds ) internal { uint256 arrayLength = tokenIds.length; - IParaApeStaking.BAKCPoolState storage bakcPoolState = vaultStorage - .bakcPoolState; - vaultStorage.bakcPoolState.totalPosition -= arrayLength.toUint24(); + IParaApeStaking.PoolState storage bakcPoolState = poolStates[ + ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID + ]; + bakcPoolState.totalPosition -= arrayLength.toUint24(); ApeCoinStaking.PairNftWithdrawWithAmount[] memory baycPair = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength @@ -623,7 +613,7 @@ library ApeStakingSinglePoolLogic { (uint256 mainTokenId, bool isPaired) = vars .apeCoinStaking - .bakcToMain(tokenId, BAYC_POOL_ID); + .bakcToMain(tokenId, ApeStakingCommonLogic.BAYC_POOL_ID); if (isPaired) { baycPair[baycPairCount] = ApeCoinStaking .PairNftWithdrawWithAmount({ @@ -638,7 +628,7 @@ library ApeStakingSinglePoolLogic { (mainTokenId, isPaired) = vars.apeCoinStaking.bakcToMain( tokenId, - MAYC_POOL_ID + ApeStakingCommonLogic.MAYC_POOL_ID ); if (isPaired) { maycPair[maycPairCount] = ApeCoinStaking @@ -660,20 +650,16 @@ library ApeStakingSinglePoolLogic { mstore(maycPair, maycPairCount) } - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( WadRayMath.RAY ); vars.latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); - if (baycPairCount > 0) { - bakcPoolState.baycStakingPosition -= baycPairCount; + if (baycPairCount > 0 || maycPairCount > 0) { + bakcPoolState.stakingPosition -= (baycPairCount + maycPairCount); vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - vars.apeCoinStaking.withdrawBAKC(baycPair, _otherPairs); + vars.apeCoinStaking.withdrawBAKC(baycPair, maycPair); vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; IAutoCompoundApe(vars.cApe).deposit( @@ -681,87 +667,65 @@ library ApeStakingSinglePoolLogic { vars.totalClaimedApe ); - vars.apeRewardRatio = vaultStorage.baycPairStakingRewardRatio; ( vars.totalRepay, vars.totalCompoundFee - ) = _calculateRepayAndCompoundBAKC( - vaultStorage.poolStates[BAYC_SINGLE_POOL_ID], - bakcPoolState, - vars, - true - ); - } - if (maycPairCount > 0) { - bakcPoolState.maycStakingPosition -= maycPairCount; - - vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); - vars.apeCoinStaking.withdrawBAKC(_otherPairs, maycPair); - vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); - vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); + ) = _calculateRepayAndCompoundBAKC(poolStates, vars); - vars.apeRewardRatio = vaultStorage.maycPairStakingRewardRatio; - ( - uint256 maycTotalRepay, - uint256 maycCompoundFee - ) = _calculateRepayAndCompoundBAKC( - vaultStorage.poolStates[MAYC_SINGLE_POOL_ID], - bakcPoolState, - vars, - false + if (vars.totalRepay > 0) { + IPool(vars.pool).repay( + vars.cApe, + vars.totalRepay, + address(this) ); - vars.totalRepay += maycTotalRepay; - vars.totalCompoundFee += maycCompoundFee; - } - - if (vars.totalRepay > 0) { - IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); - } - if (vars.totalCompoundFee > 0) { - cApeShareBalance[address(this)] += vars.totalCompoundFee; + } + if (vars.totalCompoundFee > 0) { + cApeShareBalance[address(this)] += vars.totalCompoundFee; + } } } function calculatePendingReward( - IParaApeStaking.VaultStorage storage vaultStorage, + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds ) external view returns (uint256) { - vars.accumulatedRewardsPerNft = _getPoolAccumulatedRewardsPerNft( - vaultStorage, + uint256 poolId = (nft == vars.bayc) + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : (nft == vars.mayc) + ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; + IParaApeStaking.PoolState storage poolState = poolStates[poolId]; + (, uint256 pendingReward, ) = _calculatePendingReward( + poolState, vars, - nft - ); - mapping(uint256 => IParaApeStaking.TokenStatus) - storage tokenStatus = _getPoolTokenStatus(vaultStorage, vars, nft); - - address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) - ? vars.nMayc - : vars.nBakc; - - (, uint256 pendingReward) = _calculatePendingReward( - tokenStatus, - vars, - nToken, + nft, tokenIds ); return pendingReward; } function _calculatePendingReward( - mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nToken, + address nft, uint32[] calldata tokenIds - ) internal view returns (address claimFor, uint256 pendingReward) { + ) + internal + view + returns ( + address claimFor, + uint256 pendingReward, + uint128 accumulatedRewardsPerNft + ) + { uint256 rewardShares; uint256 arrayLength = tokenIds.length; - uint128 accumulatedRewardsPerNft = vars.accumulatedRewardsPerNft; + accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -775,33 +739,29 @@ library ApeStakingSinglePoolLogic { } } - require(tokenStatus[tokenId].isInPool, Errors.NFT_NOT_IN_POOL); + IParaApeStaking.TokenStatus memory tokenStatus = poolState + .tokenStatus[tokenId]; + require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); //update reward, to save gas we don't claim pending reward in ApeCoinStaking. rewardShares += (accumulatedRewardsPerNft - - tokenStatus[tokenId].rewardsDebt); + tokenStatus.rewardsDebt); } pendingReward = ICApe(vars.cApe).getPooledApeByShares(rewardShares); - - return (claimFor, pendingReward); } function _claimNFT( - mapping(uint256 => IParaApeStaking.TokenStatus) storage tokenStatus, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool needUpdateStatus, address nft, uint32[] calldata tokenIds ) internal returns (address) { - address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) - ? vars.nMayc - : vars.nBakc; - (address owner, uint256 pendingReward) = _calculatePendingReward( - tokenStatus, - vars, - nToken, - tokenIds - ); + ( + address owner, + uint256 pendingReward, + uint128 accumulatedRewardsPerNft + ) = _calculatePendingReward(poolState, vars, nft, tokenIds); if (pendingReward > 0) { uint256 arrayLength = tokenIds.length; @@ -809,8 +769,9 @@ library ApeStakingSinglePoolLogic { uint32 tokenId = tokenIds[index]; if (needUpdateStatus) { - tokenStatus[tokenId].rewardsDebt = vars - .accumulatedRewardsPerNft; + poolState + .tokenStatus[tokenId] + .rewardsDebt = accumulatedRewardsPerNft; } //emit event @@ -824,43 +785,28 @@ library ApeStakingSinglePoolLogic { } function _calculateRepayAndCompoundBAKC( - IParaApeStaking.PoolState storage apePoolState, - IParaApeStaking.BAKCPoolState storage bakcPoolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.ApeStakingVaultCacheVars memory vars ) internal returns (uint256, uint256) { - uint256 repayAmount = 0; - uint256 debtInterest = 0; - uint256 cApeDebtShare; - uint256 stakingPosition; - if (isBAYC) { - cApeDebtShare = bakcPoolState.baycCApeDebtShare; - stakingPosition = bakcPoolState.baycStakingPosition; - } else { - cApeDebtShare = bakcPoolState.maycCApeDebtShare; - stakingPosition = bakcPoolState.maycStakingPosition; - } - debtInterest = ApeStakingCommonLogic + IParaApeStaking.PoolState storage bakcPoolState = poolStates[ + ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID + ]; + uint256 cApeDebtShare = bakcPoolState.cApeDebtShare; + uint256 debtInterest = ApeStakingCommonLogic .calculateCurrentPositionDebtInterest( cApeDebtShare, - stakingPosition, + bakcPoolState.stakingPosition, vars.bakcMatchedCap, vars.cApeExchangeRate, vars.latestBorrowIndex ); - repayAmount = (debtInterest >= vars.totalClaimedApe) + uint256 repayAmount = (debtInterest >= vars.totalClaimedApe) ? vars.totalClaimedApe : debtInterest; cApeDebtShare -= repayAmount.rayDiv(vars.latestBorrowIndex).rayDiv( vars.cApeExchangeRate ); - if (isBAYC) { - bakcPoolState.baycCApeDebtShare = cApeDebtShare.toUint104(); - } else { - bakcPoolState.maycCApeDebtShare = cApeDebtShare.toUint104(); - } - - //calculate compound fee + bakcPoolState.cApeDebtShare = cApeDebtShare.toUint104(); uint256 compoundFee = 0; if (vars.totalClaimedApe > debtInterest) { //update reward index @@ -868,15 +814,34 @@ library ApeStakingSinglePoolLogic { .rayDiv(vars.cApeExchangeRate); compoundFee = shareRewardAmount.percentMul(vars.compoundFee); shareRewardAmount -= compoundFee; + uint256 apeShareAmount = shareRewardAmount.percentMul( vars.apeRewardRatio ); - uint104 apeTotalPosition = apePoolState.totalPosition; + IParaApeStaking.PoolState storage baycPoolState = poolStates[ + ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + ]; + IParaApeStaking.PoolState storage maycPoolState = poolStates[ + ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + ]; + uint24 baycPositon = baycPoolState.totalPosition; + uint24 maycPositon = maycPoolState.totalPosition; + uint24 apeTotalPosition = baycPositon + maycPositon; if (apeTotalPosition != 0) { - apePoolState.accumulatedRewardsPerNft += - apeShareAmount.toUint104() / + uint256 baycShareAmount = (apeShareAmount * baycPositon) / apeTotalPosition; + uint256 maycShareAmount = apeShareAmount - baycShareAmount; + if (baycPositon != 0) { + baycPoolState.accumulatedRewardsPerNft += + baycShareAmount.toUint104() / + baycPositon; + } + if (maycPositon != 0) { + maycPoolState.accumulatedRewardsPerNft += + maycShareAmount.toUint104() / + maycPositon; + } } else { compoundFee += apeShareAmount; } @@ -894,37 +859,22 @@ library ApeStakingSinglePoolLogic { return (repayAmount, compoundFee); } - function _getPoolAccumulatedRewardsPerNft( - IParaApeStaking.VaultStorage storage vaultStorage, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft - ) internal view returns (uint128) { - return - (nft == vars.bakc) - ? vaultStorage.bakcPoolState.accumulatedRewardsPerNft - : (nft == vars.bayc) - ? vaultStorage - .poolStates[BAYC_SINGLE_POOL_ID] - .accumulatedRewardsPerNft - : vaultStorage - .poolStates[MAYC_SINGLE_POOL_ID] - .accumulatedRewardsPerNft; - } - - function _getPoolTokenStatus( - IParaApeStaking.VaultStorage storage vaultStorage, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft - ) - internal - view - returns (mapping(uint256 => IParaApeStaking.TokenStatus) storage) - { - return - (nft == vars.bakc) - ? vaultStorage.bakcPoolState.tokenStatus - : (nft == vars.bayc) - ? vaultStorage.poolStates[BAYC_SINGLE_POOL_ID].tokenStatus - : vaultStorage.poolStates[MAYC_SINGLE_POOL_ID].tokenStatus; + function _validateBAKCPairActionInfo( + IParaApeStaking.BAKCPairActionInfo calldata actionInfo + ) internal pure { + uint256 baycArrayLength = actionInfo.baycTokenIds.length; + uint256 maycArrayLength = actionInfo.maycTokenIds.length; + require( + baycArrayLength == actionInfo.bakcPairBaycTokenIds.length, + Errors.INVALID_PARAMETER + ); + require( + maycArrayLength == actionInfo.bakcPairMaycTokenIds.length, + Errors.INVALID_PARAMETER + ); + require( + baycArrayLength > 0 || maycArrayLength > 0, + Errors.INVALID_PARAMETER + ); } } diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index be15d7d0c..df50e07f3 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -2,19 +2,19 @@ pragma solidity 0.8.10; interface IApeStakingVault { - /** - * @dev Emitted during setSinglePoolApeRewardRatio() - * @param oldRatio The value of the old baycPairStakingRewardRatio - * @param newRatio The value of the new baycPairStakingRewardRatio - **/ - event BaycPairStakingRewardRatioUpdated(uint128 oldRatio, uint128 newRatio); + struct BAKCPairActionInfo { + uint32[] baycTokenIds; + uint32[] bakcPairBaycTokenIds; + uint32[] maycTokenIds; + uint32[] bakcPairMaycTokenIds; + } /** * @dev Emitted during setSinglePoolApeRewardRatio() - * @param oldRatio The value of the old maycPairStakingRewardRatio - * @param newRatio The value of the new maycPairStakingRewardRatio + * @param oldRatio The value of the old ApePairStakingRewardRatio + * @param newRatio The value of the new ApePairStakingRewardRatio **/ - event MaycPairStakingRewardRatioUpdated(uint128 oldRatio, uint128 newRatio); + event ApePairStakingRewardRatioUpdated(uint256 oldRatio, uint256 newRatio); /** * @notice deposit Ape and BAKC pair into the pool @@ -107,15 +107,9 @@ interface IApeStakingVault { /** * @notice stake pool's Ape and BAKC into ApeCoinStaking pair staking pool - * @param isBAYC if Ape is BAYC - * @param apeTokenIds Ape token ids - * @param bakcTokenIds BAKC token ids + * @param actionInfo detail staking info */ - function stakingBAKC( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external; + function stakingBAKC(BAKCPairActionInfo calldata actionInfo) external; /** * @notice claim Ape staking reward from ApeCoinStaking and compound as cApe for user @@ -128,15 +122,9 @@ interface IApeStakingVault { /** * @notice claim single pool's Ape and BAKC pair staking reward from ApeCoinStaking and compound as cApe for user * only ape staking bot can call this function - * @param isBAYC if Ape is BAYC - * @param apeTokenIds Ape token ids - * @param bakcTokenIds BAKC token ids + * @param actionInfo detail staking info */ - function compoundBAKC( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external; + function compoundBAKC(BAKCPairActionInfo calldata actionInfo) external; /** * @notice get single pool nft unclaimed cApe reward diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index fca7e4caa..da78264bb 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -42,12 +42,6 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { address nApeOwner; } - struct ApeStatus { - uint32 matchedCount; - bool isInApeCoinPool; - bool isInApeCoinPairPool; - } - struct SApeBalance { //cApe share uint128 freeShareBalance; @@ -60,49 +54,25 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { uint128 rewardsDebt; // identify if tokenId is in pool bool isInPool; - // pair bakc token + // pair bakc token, only for pair pool uint32 bakcTokenId; - // is paird with bakc + // is paird with bakc, only for pair pool bool isPaired; } struct PoolState { - //pool cape debt token share, max value for uint104 is 2e31, ape coin total supply is 1e27. + //pool cape debt token share, max value for uint104 is 2e31, ape coin total supply is 1e27. only for pool staking uint104 cApeDebtShare; // accumulated cApe reward for per NFT position uint104 accumulatedRewardsPerNft; // total NFT position count, max value for uint24 is 16777216 uint24 totalPosition; - // total staking position + // total staking position, used for calculate interest debt, . only for pool staking uint24 stakingPosition; //tokenId => reward debt position mapping(uint256 => TokenStatus) tokenStatus; } - struct BAKCPoolState { - // accumulated cApe reward for per NFT position - uint104 accumulatedRewardsPerNft; - // total NFT position count - uint24 totalPosition; - //bayc pair cape debt token share - uint104 baycCApeDebtShare; - //bayc pair staking position - uint24 baycStakingPosition; - //mayc pair cape debt token share - uint104 maycCApeDebtShare; - //mayc pair staking position - uint24 maycStakingPosition; - //tokenId => reward debt position - mapping(uint256 => TokenStatus) tokenStatus; - } - - struct VaultStorage { - mapping(uint256 => PoolState) poolStates; - BAKCPoolState bakcPoolState; - uint128 baycPairStakingRewardRatio; - uint128 maycPairStakingRewardRatio; - } - struct ApeCoinActionInfo { address cashToken; uint256 cashAmount; diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index df3392d47..89c5ae25c 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -505,12 +505,12 @@ describe("Para Ape Staking Test", () => { await paraApeStaking.connect(user4.signer).stakingApe(false, [0, 1, 2]) ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingBAKC(true, [0, 1], [0, 1]) - ); - await waitForTx( - await paraApeStaking.connect(user4.signer).stakingBAKC(false, [2], [2]) + await paraApeStaking.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [2], + bakcPairMaycTokenIds: [2], + }) ); expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq( parseEther("200000") @@ -552,12 +552,12 @@ describe("Para Ape Staking Test", () => { await paraApeStaking.connect(user4.signer).compoundApe(false, [0, 1, 2]) ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundBAKC(true, [0, 1], [0, 1]) - ); - await waitForTx( - await paraApeStaking.connect(user4.signer).compoundBAKC(false, [2], [2]) + await paraApeStaking.connect(user4.signer).compoundBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [2], + bakcPairMaycTokenIds: [2], + }) ); let compoundFee = await paraApeStaking.pendingCApeReward( paraApeStaking.address @@ -578,15 +578,15 @@ describe("Para Ape Staking Test", () => { ); expect(user1PendingReward).to.be.closeTo( parseEther("3240"), - parseEther("50") + parseEther("100") ); expect(user2PendingReward).to.be.closeTo( parseEther("3240"), - parseEther("50") + parseEther("100") ); expect(user3PendingReward).to.be.closeTo( parseEther("3240"), - parseEther("50") + parseEther("100") ); await waitForTx( @@ -607,12 +607,9 @@ describe("Para Ape Staking Test", () => { let user1Balance = await cApe.balanceOf(user1.address); let user2Balance = await cApe.balanceOf(user2.address); let user3Balance = await cApe.balanceOf(user3.address); - expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); - expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); - expect(user3Balance).to.be.closeTo(user3PendingReward, parseEther("1")); - //base on both baycPairStakingRewardRatio and maycPairStakingRewardRatio are 0 - expect(user1Balance).to.be.closeTo(user2Balance, parseEther("50")); - expect(user1Balance).to.be.closeTo(user3Balance, parseEther("50")); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("100")); + expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("100")); + expect(user3Balance).to.be.closeTo(user3PendingReward, parseEther("100")); const newUser1PendingReward = await paraApeStaking.nftPendingReward( bayc.address, @@ -673,8 +670,8 @@ describe("Para Ape Staking Test", () => { user1Balance = await cApe.balanceOf(user1.address); user2Balance = await cApe.balanceOf(user2.address); user3Balance = await cApe.balanceOf(user3.address); - expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("10")); - expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("10")); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); + expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); expect(user3Balance).to.be.closeTo( user3PendingReward.mul(2), parseEther("10") @@ -686,7 +683,7 @@ describe("Para Ape Staking Test", () => { expect(await cApe.balanceOf(paraApeStaking.address)).to.be.closeTo( "0", - parseEther("1") + parseEther("10") ); }); @@ -993,17 +990,30 @@ describe("Para Ape Staking Test", () => { ); await expect( - paraApeStaking.connect(user4.signer).stakingBAKC(true, [2], [0]) + paraApeStaking.connect(user4.signer).stakingBAKC({ + baycTokenIds: [2], + bakcPairBaycTokenIds: [0], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await expect( - paraApeStaking.connect(user4.signer).stakingBAKC(true, [0], [2]) + paraApeStaking.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0], + bakcPairBaycTokenIds: [2], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .stakingBAKC(true, [0, 1], [0, 1]) + await paraApeStaking.connect(user1.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ); }); @@ -1058,25 +1068,41 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .stakingBAKC(true, [0, 1], [0, 1]) + await paraApeStaking.connect(user1.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ); await advanceTimeAndBlock(parseInt("3600")); await expect( - paraApeStaking.connect(user4.signer).compoundBAKC(true, [2], [1]) + paraApeStaking.connect(user4.signer).compoundBAKC({ + baycTokenIds: [2], + bakcPairBaycTokenIds: [1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ).to.be.reverted; await expect( - paraApeStaking.connect(user4.signer).compoundBAKC(true, [1], [2]) + paraApeStaking.connect(user4.signer).compoundBAKC({ + baycTokenIds: [1], + bakcPairBaycTokenIds: [2], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundBAKC(true, [0, 1], [0, 1]) + await paraApeStaking.connect(user4.signer).compoundBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ); }); @@ -1119,9 +1145,12 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .stakingBAKC(true, [0, 1], [0, 1]) + await paraApeStaking.connect(user1.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ); await advanceTimeAndBlock(parseInt("3600")); @@ -1131,9 +1160,12 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundBAKC(true, [0, 1], [0, 1]) + await paraApeStaking.connect(user4.signer).compoundBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ); await expect( @@ -1208,9 +1240,12 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .stakingBAKC(true, [0, 1], [0, 1]) + await paraApeStaking.connect(user1.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ); await expect( @@ -1382,7 +1417,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(poolAdmin.signer) - .setSinglePoolApeRewardRatio(6000, 5000) + .setSinglePoolApeRewardRatio(5000) ); await supplyAndValidate(bayc, "2", user1, true); @@ -1406,27 +1441,23 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingBAKC(true, [0, 1], [0, 1]) - ); - await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingBAKC(false, [0, 1], [2, 3]) + await paraApeStaking.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [0, 1], + bakcPairMaycTokenIds: [2, 3], + }) ); await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundBAKC(true, [0, 1], [0, 1]) - ); - await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundBAKC(false, [0, 1], [2, 3]) + await paraApeStaking.connect(user4.signer).compoundBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [0, 1], + bakcPairMaycTokenIds: [2, 3], + }) ); await waitForTx( @@ -1441,34 +1472,46 @@ describe("Para Ape Staking Test", () => { .claimNFT(bakc.address, [0, 1, 2, 3]) ); - //user1: 900 * 0.5 * 2 + //user1: 3600 * 0.5 * 0.5 expect(await cApe.balanceOf(user1.address)).to.be.closeTo( - parseEther("1080"), - parseEther("50") + parseEther("900"), + parseEther("10") ); - //user2: 900 * 0.6 * 2 + //user1: 3600 * 0.5 * 0.5 expect(await cApe.balanceOf(user2.address)).to.be.closeTo( parseEther("900"), - parseEther("50") + parseEther("10") ); - //user3: 900 * 0.5 * 2 + 900 * 0.4 * 2 + //user3: 3600 * 0.5 expect(await cApe.balanceOf(user3.address)).to.be.closeTo( - parseEther("1620"), - parseEther("50") + parseEther("1800"), + parseEther("10") ); await advanceTimeAndBlock(parseInt("3600")); + //user1: 900 + 0 + //user2: 900 + 900 + //user3: 1800 + 900 + //user4: 0 + 0 await waitForTx( await paraApeStaking .connect(user1.signer) .withdrawNFT(bayc.address, [0, 1]) ); + //user1: 900 + 0 + 0 + //user2: 900 + 900 + 0 + //user3: 1800 + 900 + 900 + //user4: 0 + 900 await waitForTx( await paraApeStaking .connect(user2.signer) .withdrawNFT(mayc.address, [0, 1]) ); + //user1: 900 + 0 + 0 + 0 + //user2: 900 + 900 + 0 + 0 + //user3: 1800 + 900 + 900 + 0 + //user4: 0 + 900 + 0 + 0 await waitForTx( await paraApeStaking .connect(user3.signer) @@ -1478,24 +1521,21 @@ describe("Para Ape Staking Test", () => { await paraApeStaking.connect(user4.signer).claimCompoundFee(user4.address) ); - //user1: 1080 expect(await cApe.balanceOf(user1.address)).to.be.closeTo( - parseEther("1080"), - parseEther("50") + parseEther("900"), + parseEther("10") ); - //user2: 900 expect(await cApe.balanceOf(user2.address)).to.be.closeTo( - parseEther("900"), - parseEther("50") + parseEther("1800"), + parseEther("30") ); - //user3: 1620 * 2 expect(await cApe.balanceOf(user3.address)).to.be.closeTo( - parseEther("3240"), + parseEther("3600"), parseEther("50") ); expect(await cApe.balanceOf(user4.address)).to.be.closeTo( - parseEther("1980"), - parseEther("50") + parseEther("900"), + parseEther("10") ); }); @@ -1511,7 +1551,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(poolAdmin.signer) - .setSinglePoolApeRewardRatio(5000, 5000) + .setSinglePoolApeRewardRatio(5000) ); await supplyAndValidate(bayc, "3", user1, true); @@ -1541,14 +1581,12 @@ describe("Para Ape Staking Test", () => { await paraApeStaking.connect(user4.signer).stakingApe(false, [0, 1, 2]) ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingBAKC(true, [0, 1], [0, 1]) - ); - await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingBAKC(false, [0, 1], [2, 3]) + await paraApeStaking.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [0, 1], + bakcPairMaycTokenIds: [2, 3], + }) ); expect( await variableDebtCApeCoin.balanceOf(paraApeStaking.address) @@ -1557,39 +1595,55 @@ describe("Para Ape Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundBAKC(true, [0, 1], [0, 1]) + await paraApeStaking.connect(user4.signer).compoundBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) ); const user3PendingReward0 = await paraApeStaking.nftPendingReward( bakc.address, [0, 1, 2, 3] ); + expect(user3PendingReward0).to.be.closeTo( + parseEther("900"), + parseEther("10") + ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .compoundBAKC(false, [0, 1], [2, 3]) + await paraApeStaking.connect(user4.signer).compoundBAKC({ + baycTokenIds: [], + bakcPairBaycTokenIds: [], + maycTokenIds: [0, 1], + bakcPairMaycTokenIds: [2, 3], + }) ); const user3PendingReward1 = await paraApeStaking.nftPendingReward( bakc.address, [0, 1, 2, 3] ); + expect(user3PendingReward1).to.be.closeTo( + parseEther("1800"), + parseEther("10") + ); const user1PendingReward = await paraApeStaking.nftPendingReward( bayc.address, [0, 1] ); + //900 * 2 / 3 + expect(user1PendingReward).to.be.closeTo( + parseEther("600"), + parseEther("10") + ); const user2PendingReward = await paraApeStaking.nftPendingReward( mayc.address, [0, 1] ); - expect(user3PendingReward0.mul(2)).to.be.closeTo( - user3PendingReward1, - parseEther("1") - ); - expect(user1PendingReward).to.be.closeTo( - user2PendingReward, - parseEther("1") + //900 * 2 / 3 + expect(user2PendingReward).to.be.closeTo( + parseEther("600"), + parseEther("10") ); }); @@ -1605,7 +1659,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(poolAdmin.signer) - .setSinglePoolApeRewardRatio(5000, 5000) + .setSinglePoolApeRewardRatio(5000) ); await supplyAndValidate(bayc, "3", user1, true); @@ -1635,14 +1689,12 @@ describe("Para Ape Staking Test", () => { await paraApeStaking.connect(user4.signer).stakingApe(false, [0, 1, 2]) ); await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingBAKC(true, [0, 1], [0, 1]) - ); - await waitForTx( - await paraApeStaking - .connect(user4.signer) - .stakingBAKC(false, [0, 1], [2, 3]) + await paraApeStaking.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [0, 1], + bakcPairMaycTokenIds: [2, 3], + }) ); expect( await variableDebtCApeCoin.balanceOf(paraApeStaking.address) @@ -1659,6 +1711,11 @@ describe("Para Ape Staking Test", () => { bayc.address, [0, 1] ); + //900 * 2 / 3 + expect(user1PendingReward).to.be.closeTo( + parseEther("600"), + parseEther("10") + ); const user2PendingReward = await paraApeStaking.nftPendingReward( mayc.address, [0, 1] From f53f16314f14bc7987dc32f159b856140bf39545 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 27 Jul 2023 13:48:51 +0800 Subject: [PATCH 67/99] chore: add pool borrow and stake --- contracts/apestaking/ParaApeStaking.sol | 18 +- .../apestaking/logic/ApeCoinPoolLogic.sol | 23 +- .../logic/ApeStakingSinglePoolLogic.sol | 1 - contracts/interfaces/IApeCoinPool.sol | 69 +++++ contracts/interfaces/IParaApeStaking.sol | 15 - contracts/interfaces/IPoolApeStaking.sol | 9 + contracts/protocol/pool/PoolApeStaking.sol | 93 +++++- helpers/contracts-deployments.ts | 1 + test/para_pool_ape_staking.spec.ts | 292 +++++++++++++++++- 9 files changed, 487 insertions(+), 34 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index a17ccbf9e..1f6a516c5 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -51,7 +51,7 @@ contract ParaApeStaking is address private immutable psApe; //record all pool states - mapping(uint256 => PoolState) internal poolStates; + mapping(uint256 => PoolState) public poolStates; //record user sApe balance mapping(address => SApeBalance) private sApeBalance; @@ -273,11 +273,15 @@ contract ParaApeStaking is /* *Ape Coin Staking Pool Logic */ - function depositApeCoinPool(ApeCoinActionInfo calldata depositInfo) + function depositApeCoinPool(ApeCoinDepositInfo calldata depositInfo) external whenNotPaused nonReentrant { + require( + msg.sender == pool || msg.sender == depositInfo.onBehalf, + Errors.CALLER_NOT_ALLOWED + ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = depositInfo.isBAYC ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID @@ -344,7 +348,7 @@ contract ParaApeStaking is ); } - function withdrawApeCoinPool(ApeCoinActionInfo calldata withdrawInfo) + function withdrawApeCoinPool(ApeCoinWithdrawInfo calldata withdrawInfo) external whenNotPaused nonReentrant @@ -365,11 +369,15 @@ contract ParaApeStaking is ); } - function depositApeCoinPairPool(ApeCoinPairActionInfo calldata depositInfo) + function depositApeCoinPairPool(ApeCoinPairDepositInfo calldata depositInfo) external whenNotPaused nonReentrant { + require( + msg.sender == pool || msg.sender == depositInfo.onBehalf, + Errors.CALLER_NOT_ALLOWED + ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = depositInfo.isBAYC ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID @@ -438,7 +446,7 @@ contract ParaApeStaking is } function withdrawApeCoinPairPool( - ApeCoinPairActionInfo calldata withdrawInfo + ApeCoinPairWithdrawInfo calldata withdrawInfo ) external whenNotPaused nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 63cab5c57..4f2cdbe57 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.10; import {IPool} from "../../interfaces/IPool.sol"; import "../../interfaces/IParaApeStaking.sol"; +import "../../interfaces/IApeCoinPool.sol"; import {IERC20, SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; @@ -81,7 +82,7 @@ library ApeCoinPoolLogic { mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - IParaApeStaking.ApeCoinActionInfo calldata depositInfo + IParaApeStaking.ApeCoinDepositInfo calldata depositInfo ) external { uint256 arrayLength = depositInfo.tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); @@ -95,7 +96,6 @@ library ApeCoinPoolLogic { vars.nApe = vars.nMayc; vars.positionCap = vars.maycMatchedCap; } - address msgSender = msg.sender; uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); @@ -103,7 +103,7 @@ library ApeCoinPoolLogic { uint32 tokenId = depositInfo.tokenIds[index]; require( - msgSender == IERC721(vars.nApe).ownerOf(tokenId), + depositInfo.onBehalf == IERC721(vars.nApe).ownerOf(tokenId), Errors.NOT_THE_OWNER ); @@ -138,7 +138,7 @@ library ApeCoinPoolLogic { totalApeCoinNeeded.toUint128(), depositInfo.cashToken, depositInfo.cashAmount, - msgSender + depositInfo.onBehalf ); //stake in ApeCoinStaking @@ -211,7 +211,7 @@ library ApeCoinPoolLogic { mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - IParaApeStaking.ApeCoinActionInfo memory withdrawInfo + IParaApeStaking.ApeCoinWithdrawInfo memory withdrawInfo ) public { uint256 arrayLength = withdrawInfo.tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); @@ -311,7 +311,7 @@ library ApeCoinPoolLogic { mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - IParaApeStaking.ApeCoinPairActionInfo calldata depositInfo + IParaApeStaking.ApeCoinPairDepositInfo calldata depositInfo ) external { uint256 arrayLength = depositInfo.apeTokenIds.length; require( @@ -340,7 +340,8 @@ library ApeCoinPoolLogic { address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); require( - msg.sender == nApeOwner && msg.sender == nBakcOwner, + depositInfo.onBehalf == nApeOwner && + depositInfo.onBehalf == nBakcOwner, Errors.NOT_THE_OWNER ); } @@ -386,7 +387,7 @@ library ApeCoinPoolLogic { totalApeCoinNeeded.toUint128(), depositInfo.cashToken, depositInfo.cashAmount, - msg.sender + depositInfo.onBehalf ); //stake in ApeCoinStaking @@ -474,7 +475,7 @@ library ApeCoinPoolLogic { mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - IParaApeStaking.ApeCoinPairActionInfo memory withdrawInfo + IParaApeStaking.ApeCoinPairWithdrawInfo memory withdrawInfo ) public { uint256 arrayLength = withdrawInfo.apeTokenIds.length; require( @@ -637,7 +638,7 @@ library ApeCoinPoolLogic { sApeBalance, cApeShareBalance, vars, - IParaApeStaking.ApeCoinActionInfo({ + IApeCoinPool.ApeCoinWithdrawInfo({ cashToken: vars.cApe, cashAmount: 0, isBAYC: isBAYC, @@ -678,7 +679,7 @@ library ApeCoinPoolLogic { sApeBalance, cApeShareBalance, vars, - IParaApeStaking.ApeCoinPairActionInfo({ + IApeCoinPool.ApeCoinPairWithdrawInfo({ cashToken: vars.cApe, cashAmount: 0, isBAYC: isBAYC, diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 86821de96..8fa4ccbaf 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -12,7 +12,6 @@ import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import "./ApeStakingCommonLogic.sol"; import "../../protocol/libraries/helpers/Errors.sol"; -import "hardhat/console.sol"; /** * @title ApeStakingSinglePoolLogic library diff --git a/contracts/interfaces/IApeCoinPool.sol b/contracts/interfaces/IApeCoinPool.sol index 7a56fcabb..4c9fd2b0e 100644 --- a/contracts/interfaces/IApeCoinPool.sol +++ b/contracts/interfaces/IApeCoinPool.sol @@ -2,6 +2,75 @@ pragma solidity 0.8.10; interface IApeCoinPool { + struct ApeCoinDepositInfo { + address onBehalf; + address cashToken; + uint256 cashAmount; + bool isBAYC; + uint32[] tokenIds; + } + + struct ApeCoinPairDepositInfo { + address onBehalf; + address cashToken; + uint256 cashAmount; + bool isBAYC; + uint32[] apeTokenIds; + uint32[] bakcTokenIds; + } + + struct ApeCoinWithdrawInfo { + address cashToken; + uint256 cashAmount; + bool isBAYC; + uint32[] tokenIds; + } + + struct ApeCoinPairWithdrawInfo { + address cashToken; + uint256 cashAmount; + bool isBAYC; + uint32[] apeTokenIds; + uint32[] bakcTokenIds; + } + + function depositApeCoinPool(ApeCoinDepositInfo calldata depositInfo) + external; + + function compoundApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) + external; + + function apeCoinPoolPendingReward(bool isBAYC, uint32[] calldata tokenIds) + external + view + returns (uint256); + + function claimApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external; + + function withdrawApeCoinPool(ApeCoinWithdrawInfo calldata withdrawInfo) + external; + + function depositApeCoinPairPool(ApeCoinPairDepositInfo calldata depositInfo) + external; + + function compoundApeCoinPairPool( + bool isBAYC, + uint32[] calldata apeTokenIds, + uint32[] calldata bakcTokenIds + ) external; + + function apeCoinPairPoolPendingReward( + bool isBAYC, + uint32[] calldata apeTokenIds + ) external view returns (uint256); + + function claimApeCoinPairPool(bool isBAYC, uint32[] calldata apeTokenIds) + external; + + function withdrawApeCoinPairPool( + ApeCoinPairWithdrawInfo calldata withdrawInfo + ) external; + function tryUnstakeApeCoinPoolPosition( bool isBAYC, uint256[] calldata tokenIds diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index da78264bb..b1cb752c7 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -73,21 +73,6 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { mapping(uint256 => TokenStatus) tokenStatus; } - struct ApeCoinActionInfo { - address cashToken; - uint256 cashAmount; - bool isBAYC; - uint32[] tokenIds; - } - - struct ApeCoinPairActionInfo { - address cashToken; - uint256 cashAmount; - bool isBAYC; - uint32[] apeTokenIds; - uint32[] bakcTokenIds; - } - /** * @dev Emitted during setApeStakingBot() * @param oldBot The address of the old compound bot diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 9cddfac6c..8b4a223f8 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.10; import "../dependencies/yoga-labs/ApeCoinStaking.sol"; +import "./IParaApeStaking.sol"; /** * @title IPoolApeStaking @@ -12,4 +13,12 @@ interface IPoolApeStaking { function paraApeStaking() external view returns (address); function borrowPoolCApe(uint256 amount) external returns (uint256); + + function borrowAndStakingApeCoin( + IParaApeStaking.ApeCoinDepositInfo[] calldata apeCoinDepositInfo, + IParaApeStaking.ApeCoinPairDepositInfo[] calldata pairDepositInfo, + address borrowAsset, + uint256 borrowAmount, + bool openSApeCollateralFlag + ) external; } diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index fe59ab484..076de06d2 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -10,6 +10,7 @@ import {IPool} from "../../interfaces/IPool.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; import "../libraries/logic/BorrowLogic.sol"; +import "../../interfaces/IParaApeStaking.sol"; contract PoolApeStaking is ParaVersionedInitializable, @@ -20,6 +21,7 @@ contract PoolApeStaking is uint256 internal constant POOL_REVISION = 149; IPoolAddressesProvider internal immutable ADDRESSES_PROVIDER; + address internal immutable APE_COIN; address internal immutable APE_COMPOUND; address internal immutable PARA_APE_STAKING; @@ -29,10 +31,12 @@ contract PoolApeStaking is */ constructor( IPoolAddressesProvider provider, + address apeCoin, address apeCompound, address apeStakingVault ) { ADDRESSES_PROVIDER = provider; + APE_COIN = apeCoin; APE_COMPOUND = apeCompound; PARA_APE_STAKING = apeStakingVault; } @@ -56,10 +60,97 @@ contract PoolApeStaking is uint256 latestBorrowIndex = BorrowLogic.executeBorrowWithoutCollateral( ps._reserves, PARA_APE_STAKING, - address(APE_COMPOUND), + APE_COMPOUND, amount ); return latestBorrowIndex; } + + function borrowAndStakingApeCoin( + IParaApeStaking.ApeCoinDepositInfo[] calldata apeCoinDepositInfo, + IParaApeStaking.ApeCoinPairDepositInfo[] calldata pairDepositInfo, + address borrowAsset, + uint256 borrowAmount, + bool openSApeCollateralFlag + ) external nonReentrant { + require( + borrowAsset == APE_COIN || borrowAsset == APE_COMPOUND, + Errors.INVALID_ASSET_TYPE + ); + DataTypes.PoolStorage storage ps = poolStorage(); + address msgSender = msg.sender; + + // 1, prepare borrow asset. + if (borrowAmount > 0) { + DataTypes.ReserveData storage borrowAssetReserve = ps._reserves[ + borrowAsset + ]; + // no time lock needed here + DataTypes.TimeLockParams memory timeLockParams; + IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( + msgSender, + borrowAmount, + timeLockParams + ); + } + + // 2, stake + uint256 arrayLength = apeCoinDepositInfo.length; + for (uint256 index = 0; index < arrayLength; index++) { + IParaApeStaking.ApeCoinDepositInfo + calldata depositInfo = apeCoinDepositInfo[index]; + require( + msgSender == depositInfo.onBehalf, + Errors.CALLER_NOT_ALLOWED + ); + IParaApeStaking(PARA_APE_STAKING).depositApeCoinPool(depositInfo); + } + arrayLength = pairDepositInfo.length; + for (uint256 index = 0; index < arrayLength; index++) { + IParaApeStaking.ApeCoinPairDepositInfo + calldata depositInfo = pairDepositInfo[index]; + require( + msgSender == depositInfo.onBehalf, + Errors.CALLER_NOT_ALLOWED + ); + IParaApeStaking(PARA_APE_STAKING).depositApeCoinPairPool( + depositInfo + ); + } + + // 3, check if need to collateralize sAPE + if (openSApeCollateralFlag) { + DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ + msgSender + ]; + Helpers.setAssetUsedAsCollateral( + userConfig, + ps._reserves, + DataTypes.SApeAddress, + msgSender + ); + } + + // 4, execute borrow + if (borrowAmount > 0) { + BorrowLogic.executeBorrow( + ps._reserves, + ps._reservesList, + ps._usersConfig[msgSender], + DataTypes.ExecuteBorrowParams({ + asset: borrowAsset, + user: msgSender, + onBehalfOf: msgSender, + amount: borrowAmount, + referralCode: 0, + releaseUnderlying: false, + reservesCount: ps._reservesCount, + oracle: ADDRESSES_PROVIDER.getPriceOracle(), + priceOracleSentinel: ADDRESSES_PROVIDER + .getPriceOracleSentinel() + }) + ); + } + } } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index e3418278e..8f5647011 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -890,6 +890,7 @@ export const deployPoolComponents = async ( eContractid.PoolApeStakingImpl, [ provider, + allTokens.APE.address, (await getAutoCompoundApe()).address, (await getParaApeStaking()).address, ], diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 08b01790f..78ed0a023 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -5,13 +5,19 @@ import { getAutoCompoundApe, getParaApeStaking, getPTokenSApe, + getVariableDebtToken, } from "../helpers/contracts-getters"; import { advanceBlock, advanceTimeAndBlock, waitForTx, } from "../helpers/misc-utils"; -import {PTokenSApe, AutoCompoundApe, ParaApeStaking} from "../types"; +import { + PTokenSApe, + AutoCompoundApe, + ParaApeStaking, + VariableDebtToken, +} from "../types"; import {TestEnv} from "./helpers/make-suite"; import {testEnvFixture} from "./helpers/setup-env"; @@ -24,6 +30,7 @@ import { import {ProtocolErrors} from "../helpers/types"; import {parseEther} from "ethers/lib/utils"; import {BigNumber} from "ethers"; +import {isUsingAsCollateral} from "../helpers/contracts-helpers"; describe("Para Ape staking ape coin pool test", () => { let testEnv: TestEnv; @@ -123,6 +130,239 @@ describe("Para Ape staking ape coin pool test", () => { return testEnv; }; + it("test borrowAndStakingApeCoin", async () => { + const { + users: [user1, user2, , user4], + ape, + bayc, + mayc, + bakc, + nBAKC, + apeCoinStaking, + pool, + protocolDataProvider, + } = await loadFixture(fixture); + + //prepare user3 asset + await mintAndValidate(ape, "20000000", user4); + await waitForTx( + await ape.connect(user4.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user4.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await pool + .connect(user4.signer) + .supply(ape.address, parseEther("10000000"), user4.address, 0) + ); + await waitForTx( + await cApe + .connect(user4.signer) + .deposit(user4.address, parseEther("10000000")) + ); + await waitForTx( + await pool + .connect(user4.signer) + .supply(cApe.address, parseEther("10000000"), user4.address, 0) + ); + + //prepare user1 user2 asset + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "2", user1, true); + await mintAndValidate(ape, "100000", user1); + await supplyAndValidate(mayc, "1", user2, true); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + await waitForTx( + await ape + .connect(user1.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user2.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + + await changePriceAndValidate(bayc, "100"); + await changePriceAndValidate(mayc, "50"); + await changePriceAndValidate(bakc, "25"); + await changePriceAndValidate(ape, "0.001"); + await changePriceAndValidate(cApe, "0.001"); + await changeSApePriceAndValidate(sApeAddress, "0.001"); + + //collateral value = 100 * 0.3 + 25 * 0.3 + 250000*0.001 * 0.2 = + //borrow value = 150000 * 0.001 + await expect( + pool.connect(user1.signer).borrowAndStakingApeCoin( + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }, + ], + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + ape.address, + parseEther("150000"), + true + ) + ).to.be.revertedWith(ProtocolErrors.COLLATERAL_CANNOT_COVER_NEW_BORROW); + + await expect( + pool.connect(user2.signer).borrowAndStakingApeCoin( + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }, + ], + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + ape.address, + parseEther("150000"), + true + ) + ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_ALLOWED); + + await changePriceAndValidate(ape, "0.00001"); + await changePriceAndValidate(cApe, "0.00001"); + await changeSApePriceAndValidate(sApeAddress, "0.00001"); + + //user1 borrow ape to stake + await waitForTx( + await pool.connect(user1.signer).borrowAndStakingApeCoin( + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }, + ], + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + ape.address, + parseEther("150000"), + true + ) + ); + + //user2 borrow cApe to stake + await waitForTx( + await pool.connect(user2.signer).borrowAndStakingApeCoin( + [ + { + onBehalf: user2.address, + cashToken: cApe.address, + cashAmount: parseEther("100000"), + isBAYC: false, + tokenIds: [0], + }, + ], + [ + { + onBehalf: user2.address, + cashToken: cApe.address, + cashAmount: parseEther("50000"), + isBAYC: false, + apeTokenIds: [0], + bakcTokenIds: [1], + }, + ], + cApe.address, + parseEther("150000"), + true + ) + ); + const sApeData = await pool.getReserveData(sApeAddress); + const user1Config = BigNumber.from( + (await pool.getUserConfiguration(user1.address)).data + ); + const user2Config = BigNumber.from( + (await pool.getUserConfiguration(user2.address)).data + ); + expect(isUsingAsCollateral(user1Config, sApeData.id)).to.be.true; + expect(isUsingAsCollateral(user2Config, sApeData.id)).to.be.true; + + const {variableDebtTokenAddress: variableDebtApeCoinAddress} = + await protocolDataProvider.getReserveTokensAddresses(ape.address); + const variableDebtApeCoin = await getVariableDebtToken( + variableDebtApeCoinAddress + ); + const {variableDebtTokenAddress: variableDebtCApeCoinAddress} = + await protocolDataProvider.getReserveTokensAddresses(cApe.address); + const variableDebtCApeCoin = await getVariableDebtToken( + variableDebtCApeCoinAddress + ); + //check user1 debt + expect(await variableDebtApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("150000"), + parseEther("50") + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.equal( + "0" + ); + + //check user2 debt + expect(await variableDebtApeCoin.balanceOf(user2.address)).to.be.eq("0"); + expect(await variableDebtCApeCoin.balanceOf(user2.address)).to.be.closeTo( + parseEther("150000"), + parseEther("50") + ); + + expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(2, 0)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq( + parseEther("50000") + ); + }); + it("test BAYC + ApeCoin pool logic", async () => { const { users: [user1, user2, , user4], @@ -159,6 +399,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("400000"), isBAYC: true, @@ -167,6 +408,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user2.signer).depositApeCoinPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, @@ -179,6 +421,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("100000"), isBAYC: true, @@ -188,6 +431,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: true, @@ -433,6 +677,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: false, @@ -441,6 +686,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user2.signer).depositApeCoinPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("100000"), isBAYC: false, @@ -453,6 +699,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("100000"), isBAYC: false, @@ -462,6 +709,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: false, @@ -710,6 +958,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, @@ -718,6 +967,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: true, @@ -727,6 +977,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("100000"), isBAYC: false, @@ -735,6 +986,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: false, @@ -895,8 +1147,19 @@ describe("Para Ape staking ape coin pool test", () => { await mintAndValidate(ape, "2000000", user1); await supplyAndValidate(bayc, "1", user1, true); + await expect( + paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user2.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_ALLOWED); + await expect( paraApeStaking.connect(user2.signer).depositApeCoinPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, @@ -906,6 +1169,7 @@ describe("Para Ape staking ape coin pool test", () => { await expect( paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("100000"), isBAYC: true, @@ -928,6 +1192,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: true, @@ -963,6 +1228,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, @@ -971,6 +1237,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user2.signer).depositApeCoinPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, @@ -980,6 +1247,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: true, @@ -1026,6 +1294,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, @@ -1034,6 +1303,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user2.signer).depositApeCoinPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, @@ -1072,8 +1342,20 @@ describe("Para Ape staking ape coin pool test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user1, true); + await expect( + paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user2.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_ALLOWED); + await expect( paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: true, @@ -1084,6 +1366,7 @@ describe("Para Ape staking ape coin pool test", () => { await expect( paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("1"), isBAYC: true, @@ -1107,6 +1390,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, @@ -1149,6 +1433,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: true, @@ -1158,6 +1443,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: true, @@ -1168,6 +1454,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, @@ -1219,6 +1506,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: true, @@ -1228,6 +1516,7 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( await paraApeStaking.connect(user2.signer).depositApeCoinPairPool({ + onBehalf: user2.address, cashToken: ape.address, cashAmount: parseEther("50000"), isBAYC: true, @@ -1284,6 +1573,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("200000"), isBAYC: true, From 47021197e93e75e84db3dd91e38785915a917e16 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 27 Jul 2023 21:15:14 +0800 Subject: [PATCH 68/99] chore: auto claim reward when nToken owner change. --- contracts/apestaking/ParaApeStaking.sol | 182 +++++++++++++++--- .../apestaking/logic/ApeCoinPoolLogic.sol | 27 +-- .../logic/ApeStakingSinglePoolLogic.sol | 9 +- contracts/interfaces/IApeCoinPool.sol | 8 +- .../tokenization/NTokenApeStaking.sol | 34 +++- .../protocol/tokenization/NTokenBAKC.sol | 64 +++++- test/_sape_pool_operation.spec.ts | 1 + test/para_pool_ape_staking.spec.ts | 7 +- 8 files changed, 269 insertions(+), 63 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 1f6a516c5..c563ac0dc 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -229,15 +229,19 @@ contract ParaApeStaking is return sApeBalance[user].stakedBalance; } - function freeSApeBalance(address user) public view returns (uint256) { - return - ICApe(cApe).getPooledApeByShares( - sApeBalance[user].freeShareBalance - ); + function freeSApeBalance(address user) external view returns (uint256) { + uint256 freeShareBalance = sApeBalance[user].freeShareBalance; + if (freeShareBalance == 0) { + return 0; + } + return ICApe(cApe).getPooledApeByShares(freeShareBalance); } function totalSApeBalance(address user) external view returns (uint256) { IParaApeStaking.SApeBalance memory cache = sApeBalance[user]; + if (cache.freeShareBalance == 0) { + return cache.stakedBalance; + } return ICApe(cApe).getPooledApeByShares(cache.freeShareBalance) + cache.stakedBalance; @@ -464,28 +468,144 @@ contract ParaApeStaking is ); } - function tryUnstakeApeCoinPoolPosition( - bool isBAYC, - uint256[] calldata tokenIds - ) external whenNotPaused nonReentrant { + function nBakcOwnerChangeCallback(uint32[] calldata tokenIds) + external + whenNotPaused + nonReentrant + { + uint256 arrayLength = tokenIds.length; + uint256 poolId = ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; + uint32[] memory singlePoolTokenIds = new uint32[](arrayLength); + uint256 singlePoolCount = 0; + + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + if (poolStates[poolId].tokenStatus[tokenId].isInPool) { + singlePoolTokenIds[singlePoolCount] = tokenId; + singlePoolCount++; + } + } + + if (singlePoolCount > 0) { + assembly { + mstore(singlePoolTokenIds, singlePoolCount) + } + + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + ApeStakingSinglePoolLogic.claimNFT( + poolStates[poolId], + vars, + bakc, + singlePoolTokenIds + ); + } + } + + function nApeOwnerChangeCallback(bool isBAYC, uint32[] calldata tokenIds) + external + whenNotPaused + nonReentrant + { ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 singlePoolId = isBAYC - ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; - uint256 PairPoolId = isBAYC - ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; - ApeCoinPoolLogic.tryUnstakeApeCoinPoolPosition( - poolStates[singlePoolId], - poolStates[PairPoolId], - apeMatchedCount, - sApeBalance, - cApeShareBalance, - vars, - isBAYC, - tokenIds - ); + uint32[] memory apeCoinPoolTokenIds = new uint32[](tokenIds.length); + uint256 apeCoinPoolCount = 0; + //handle nft pool in the scope to avoid stack too deep + { + uint32[] memory pairPoolTokenIds = new uint32[](tokenIds.length); + uint32[] memory pairPoolBakcIds = new uint32[](tokenIds.length); + uint32[] memory singlePoolTokenIds = new uint32[](tokenIds.length); + uint256 pairPoolCount = 0; + uint256 singlePoolCount = 0; + + for (uint256 index = 0; index < tokenIds.length; index++) { + uint32 tokenId = tokenIds[index]; + + //check if ape in pair pool + uint256 poolId = isBAYC + ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; + TokenStatus memory tokenStatus = poolStates[poolId].tokenStatus[ + tokenId + ]; + if (tokenStatus.isInPool) { + pairPoolTokenIds[pairPoolCount] = tokenId; + pairPoolBakcIds[pairPoolCount] = tokenStatus.bakcTokenId; + pairPoolCount++; + continue; + } + + //check if ape in single pool + poolId = isBAYC + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID; + if (poolStates[poolId].tokenStatus[tokenId].isInPool) { + singlePoolTokenIds[singlePoolCount] = tokenId; + singlePoolCount++; + continue; + } + + //must be in ape coin pool + apeCoinPoolTokenIds[apeCoinPoolCount] = tokenId; + apeCoinPoolCount++; + } + + if (pairPoolCount > 0) { + assembly { + mstore(pairPoolTokenIds, pairPoolCount) + mstore(pairPoolBakcIds, pairPoolCount) + } + uint256 poolId = isBAYC + ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; + ApeStakingPairPoolLogic.claimPairNFT( + poolStates[poolId], + vars, + isBAYC, + pairPoolTokenIds, + pairPoolBakcIds + ); + } + + if (singlePoolCount > 0) { + assembly { + mstore(singlePoolTokenIds, singlePoolCount) + } + uint256 poolId = isBAYC + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID; + ApeStakingSinglePoolLogic.claimNFT( + poolStates[poolId], + vars, + isBAYC ? bayc : mayc, + singlePoolTokenIds + ); + } + } + + if (apeCoinPoolCount > 0) { + assembly { + mstore(apeCoinPoolTokenIds, apeCoinPoolCount) + } + + uint256 singlePoolId = isBAYC + ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; + uint256 PairPoolId = isBAYC + ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; + + ApeCoinPoolLogic.tryUnstakeApeCoinPoolPosition( + poolStates[singlePoolId], + poolStates[PairPoolId], + apeMatchedCount, + sApeBalance, + cApeShareBalance, + vars, + isBAYC, + apeCoinPoolTokenIds + ); + } } /* @@ -897,7 +1017,17 @@ contract ParaApeStaking is Errors.NFT_NOT_ALLOWED ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); - ApeStakingSinglePoolLogic.claimNFT(poolStates, vars, nft, tokenIds); + uint256 poolId = (nft == vars.bayc) + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : (nft == vars.mayc) + ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; + ApeStakingSinglePoolLogic.claimNFT( + poolStates[poolId], + vars, + nft, + tokenIds + ); } /// @inheritdoc IApeStakingVault diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 4f2cdbe57..ea8aa2197 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -71,6 +71,7 @@ library ApeCoinPoolLogic { Errors.SAPE_FREE_BALANCE_NOT_ENOUGH ); sApeBalanceCache.freeShareBalance -= shareAmount.toUint128(); + sApeBalance[user] = sApeBalanceCache; _validateDropSApeBalance(pool, sApeReserveId, user); @@ -504,6 +505,7 @@ library ApeCoinPoolLogic { memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength ); + bool isBAKCOwnerWithdraw = false; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = withdrawInfo.apeTokenIds[index]; uint32 bakcTokenId = withdrawInfo.bakcTokenIds[index]; @@ -515,6 +517,7 @@ library ApeCoinPoolLogic { bakcTokenId ); require(msg.sender == nBakcOwner, Errors.NOT_THE_OWNER); + isBAKCOwnerWithdraw = true; } } @@ -554,7 +557,7 @@ library ApeCoinPoolLogic { vars, totalApeCoinAmount, withdrawInfo.cashToken, - withdrawInfo.cashAmount, + isBAKCOwnerWithdraw ? 0 : withdrawInfo.cashAmount, nApeOwner ); @@ -606,7 +609,7 @@ library ApeCoinPoolLogic { mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, - uint256[] calldata tokenIds + uint32[] calldata tokenIds ) external { require(tokenIds.length > 0, Errors.INVALID_PARAMETER); @@ -615,14 +618,14 @@ library ApeCoinPoolLogic { uint32[] memory singlePoolTokenIds = new uint32[](tokenIds.length); uint256 singleCount = 0; for (uint256 index = 0; index < tokenIds.length; index++) { - uint256 tokenId = tokenIds[index]; + uint32 tokenId = tokenIds[index]; IParaApeStaking.TokenStatus memory singlePoolTokenStatus = singlePoolState.tokenStatus[ tokenId ]; if (singlePoolTokenStatus.isInPool) { - singlePoolTokenIds[singleCount] = tokenId.toUint32(); + singlePoolTokenIds[singleCount] = tokenId; singleCount++; } } @@ -654,14 +657,14 @@ library ApeCoinPoolLogic { uint32[] memory bakcTokenIds = new uint32[](tokenIds.length); uint256 pairCount = 0; for (uint256 index = 0; index < tokenIds.length; index++) { - uint256 tokenId = tokenIds[index]; + uint32 tokenId = tokenIds[index]; IParaApeStaking.TokenStatus memory pairPoolTokenStatus = pairPoolState.tokenStatus[ tokenId ]; if (pairPoolTokenStatus.isInPool) { - parePoolTokenIds[pairCount] = tokenId.toUint32(); + parePoolTokenIds[pairCount] = tokenId; bakcTokenIds[pairCount] = pairPoolTokenStatus.bakcTokenId; pairCount++; } @@ -836,17 +839,17 @@ library ApeCoinPoolLogic { sApeBalanceCache.stakedBalance -= totalApeCoinWithdrew; sApeBalance[user] = sApeBalanceCache; - if (cashAmount > 0) { - _validateDropSApeBalance(vars.pool, vars.sApeReserveId, user); - IERC20(cashToken).safeTransfer(user, cashAmount); - } - if (cApeDepositAmount > 0) { IAutoCompoundApe(vars.cApe).deposit( address(this), cApeDepositAmount ); } + + if (cashAmount > 0) { + _validateDropSApeBalance(vars.pool, vars.sApeReserveId, user); + IERC20(cashToken).safeTransfer(user, cashAmount); + } } function _claimApeCoinPool( @@ -921,7 +924,7 @@ library ApeCoinPoolLogic { bool usageAsCollateralEnabled = userConfig.isUsingAsCollateral( sApeReserveId ); - if (usageAsCollateralEnabled) { + if (usageAsCollateralEnabled && userConfig.isBorrowingAny()) { (, , , , , uint256 healthFactor, ) = IPool(pool).getUserAccountData( user ); diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 8fa4ccbaf..53f883b8a 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -383,19 +383,12 @@ library ApeStakingSinglePoolLogic { } function claimNFT( - mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, uint32[] calldata tokenIds ) external { ApeStakingCommonLogic.validateTokenIdArray(tokenIds); - - uint256 poolId = (nft == vars.bayc) - ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID - : (nft == vars.mayc) - ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID - : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; - IParaApeStaking.PoolState storage poolState = poolStates[poolId]; _claimNFT(poolState, vars, true, nft, tokenIds); } diff --git a/contracts/interfaces/IApeCoinPool.sol b/contracts/interfaces/IApeCoinPool.sol index 4c9fd2b0e..860db0c34 100644 --- a/contracts/interfaces/IApeCoinPool.sol +++ b/contracts/interfaces/IApeCoinPool.sol @@ -71,8 +71,8 @@ interface IApeCoinPool { ApeCoinPairWithdrawInfo calldata withdrawInfo ) external; - function tryUnstakeApeCoinPoolPosition( - bool isBAYC, - uint256[] calldata tokenIds - ) external; + function nApeOwnerChangeCallback(bool isBAYC, uint32[] calldata tokenIds) + external; + + function nBakcOwnerChangeCallback(uint32[] calldata tokenIds) external; } diff --git a/contracts/protocol/tokenization/NTokenApeStaking.sol b/contracts/protocol/tokenization/NTokenApeStaking.sol index a71eaf424..226e9a8af 100644 --- a/contracts/protocol/tokenization/NTokenApeStaking.sol +++ b/contracts/protocol/tokenization/NTokenApeStaking.sol @@ -7,6 +7,7 @@ import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; import {IRewardController} from "../../interfaces/IRewardController.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; import "../../interfaces/IParaApeStaking.sol"; +import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; /** * @title ApeCoinStaking NToken @@ -14,6 +15,8 @@ import "../../interfaces/IParaApeStaking.sol"; * @notice Implementation of the NToken for the ParaSpace protocol */ abstract contract NTokenApeStaking is NToken { + using SafeCast for uint256; + IParaApeStaking immutable paraApeStaking; /** @@ -63,9 +66,14 @@ abstract contract NTokenApeStaking is NToken { uint256 tokenId, bool validate ) internal override { - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - paraApeStaking.tryUnstakeApeCoinPoolPosition(isBayc(), tokenIds); + address underlyingOwner = IERC721(_ERC721Data.underlyingAsset).ownerOf( + tokenId + ); + if (underlyingOwner == address(paraApeStaking)) { + uint32[] memory tokenIds = new uint32[](1); + tokenIds[0] = tokenId.toUint32(); + paraApeStaking.nApeOwnerChangeCallback(isBayc(), tokenIds); + } super._transfer(from, to, tokenId, validate); } @@ -78,7 +86,25 @@ abstract contract NTokenApeStaking is NToken { uint256[] calldata tokenIds, DataTypes.TimeLockParams calldata timeLockParams ) external virtual override onlyPool nonReentrant returns (uint64, uint64) { - paraApeStaking.tryUnstakeApeCoinPoolPosition(isBayc(), tokenIds); + address underlying = _ERC721Data.underlyingAsset; + uint256 arrayLength = tokenIds.length; + uint32[] memory unstakeTokenIds = new uint32[](arrayLength); + uint256 unstakeTokenIdCount = 0; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index].toUint32(); + address underlyingOwner = IERC721(underlying).ownerOf(tokenId); + if (underlyingOwner == address(paraApeStaking)) { + unstakeTokenIds[unstakeTokenIdCount] = tokenId; + unstakeTokenIdCount++; + } + } + + if (unstakeTokenIdCount > 0) { + assembly { + mstore(unstakeTokenIds, unstakeTokenIdCount) + } + paraApeStaking.nApeOwnerChangeCallback(isBayc(), unstakeTokenIds); + } return _burn(from, receiverOfUnderlying, tokenIds, timeLockParams); } diff --git a/contracts/protocol/tokenization/NTokenBAKC.sol b/contracts/protocol/tokenization/NTokenBAKC.sol index 1169a2b67..df87a0a36 100644 --- a/contracts/protocol/tokenization/NTokenBAKC.sol +++ b/contracts/protocol/tokenization/NTokenBAKC.sol @@ -10,6 +10,8 @@ import {XTokenType} from "../../interfaces/IXTokenType.sol"; import {INToken} from "../../interfaces/INToken.sol"; import {IRewardController} from "../../interfaces/IRewardController.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; +import "../../interfaces/IParaApeStaking.sol"; +import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; /** * @title NTokenBAKC @@ -17,13 +19,19 @@ import {DataTypes} from "../libraries/types/DataTypes.sol"; * @notice Implementation of the NTokenBAKC for the ParaSpace protocol */ contract NTokenBAKC is NToken { + using SafeCast for uint256; + + IParaApeStaking immutable paraApeStaking; + /** * @dev Constructor. * @param pool The address of the Pool contract */ constructor(IPool pool, address delegateRegistry) NToken(pool, false, delegateRegistry) - {} + { + paraApeStaking = IParaApeStaking(pool.paraApeStaking()); + } function initialize( IPool initializingPool, @@ -42,8 +50,58 @@ contract NTokenBAKC is NToken { params ); - address paraApeStaking = POOL.paraApeStaking(); - IERC721(underlyingAsset).setApprovalForAll(paraApeStaking, true); + IERC721(underlyingAsset).setApprovalForAll( + address(paraApeStaking), + true + ); + } + + function _transfer( + address from, + address to, + uint256 tokenId, + bool validate + ) internal override { + address underlyingOwner = IERC721(_ERC721Data.underlyingAsset).ownerOf( + tokenId + ); + if (underlyingOwner == address(paraApeStaking)) { + uint32[] memory tokenIds = new uint32[](1); + tokenIds[0] = tokenId.toUint32(); + paraApeStaking.nBakcOwnerChangeCallback(tokenIds); + } + super._transfer(from, to, tokenId, validate); + } + + /** + * @notice Overrides the burn from NToken to withdraw all staked and pending rewards before burning the NToken on liquidation/withdraw + */ + function burn( + address from, + address receiverOfUnderlying, + uint256[] calldata tokenIds, + DataTypes.TimeLockParams calldata timeLockParams + ) external virtual override onlyPool nonReentrant returns (uint64, uint64) { + address underlying = _ERC721Data.underlyingAsset; + uint256 arrayLength = tokenIds.length; + uint32[] memory claimTokenIds = new uint32[](arrayLength); + uint256 tokenIdCount = 0; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index].toUint32(); + address underlyingOwner = IERC721(underlying).ownerOf(tokenId); + if (underlyingOwner == address(paraApeStaking)) { + claimTokenIds[tokenIdCount] = tokenId; + tokenIdCount++; + } + } + + if (tokenIdCount > 0) { + assembly { + mstore(claimTokenIds, tokenIdCount) + } + paraApeStaking.nBakcOwnerChangeCallback(claimTokenIds); + } + return _burn(from, receiverOfUnderlying, tokenIds, timeLockParams); } function getXTokenType() external pure override returns (XTokenType) { diff --git a/test/_sape_pool_operation.spec.ts b/test/_sape_pool_operation.spec.ts index 6958cea31..0fe7d4251 100644 --- a/test/_sape_pool_operation.spec.ts +++ b/test/_sape_pool_operation.spec.ts @@ -74,6 +74,7 @@ describe("SApe Pool Operation Test", () => { await waitForTx( await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, cashToken: ape.address, cashAmount: parseEther("100000"), isBAYC: false, diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 78ed0a023..8ba2bf5dd 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -12,12 +12,7 @@ import { advanceTimeAndBlock, waitForTx, } from "../helpers/misc-utils"; -import { - PTokenSApe, - AutoCompoundApe, - ParaApeStaking, - VariableDebtToken, -} from "../types"; +import {PTokenSApe, AutoCompoundApe, ParaApeStaking} from "../types"; import {TestEnv} from "./helpers/make-suite"; import {testEnvFixture} from "./helpers/setup-env"; From 725c7a79a1da497b1e1317fd59b7296520f88227 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 27 Jul 2023 22:31:39 +0800 Subject: [PATCH 69/99] chore: auto claim test case --- contracts/apestaking/ParaApeStaking.sol | 5 +- test/para_pool_ape_staking.spec.ts | 156 ++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 2 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index c563ac0dc..630f92c85 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -239,11 +239,12 @@ contract ParaApeStaking is function totalSApeBalance(address user) external view returns (uint256) { IParaApeStaking.SApeBalance memory cache = sApeBalance[user]; - if (cache.freeShareBalance == 0) { + uint256 freeShareBalance = cache.freeShareBalance; + if (freeShareBalance == 0) { return cache.stakedBalance; } return - ICApe(cApe).getPooledApeByShares(cache.freeShareBalance) + + ICApe(cApe).getPooledApeByShares(freeShareBalance) + cache.stakedBalance; } diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 8ba2bf5dd..d5ba5f790 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -1683,4 +1683,160 @@ describe("Para Ape staking ape coin pool test", () => { parseEther("1") ); }); + + it("auto claim reward test", async () => { + const { + users: [user1, user2, , user4], + bayc, + mayc, + bakc, + nBAYC, + nMAYC, + nBAKC, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "2", user1, true); + await supplyAndValidate(mayc, "2", user1, true); + await supplyAndValidate(bakc, "3", user1, true); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositPairNFT(true, [0], [0]) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositPairNFT(false, [0], [1]) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositNFT(bayc.address, [1]) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositNFT(mayc.address, [1]) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositNFT(bakc.address, [2]) + ); + + await waitForTx( + await paraApeStaking.connect(user4.signer).stakingPairNFT(true, [0], [0]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).stakingPairNFT(false, [0], [1]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).stakingApe(true, [1]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).stakingApe(false, [1]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).stakingBAKC({ + baycTokenIds: [1], + bakcPairBaycTokenIds: [2], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundPairNFT(true, [0], [0]) + ); + await waitForTx( + await paraApeStaking + .connect(user4.signer) + .compoundPairNFT(false, [0], [1]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundApe(true, [1]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundApe(false, [1]) + ); + await waitForTx( + await paraApeStaking.connect(user4.signer).compoundBAKC({ + baycTokenIds: [1], + bakcPairBaycTokenIds: [2], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ); + + const baycPairReward = await paraApeStaking.pairNFTPendingReward( + true, + [0], + [0] + ); + const maycPairReward = await paraApeStaking.pairNFTPendingReward( + false, + [0], + [1] + ); + const baycSingleReward = await paraApeStaking.nftPendingReward( + bayc.address, + [1] + ); + const maycSingleReward = await paraApeStaking.nftPendingReward( + mayc.address, + [1] + ); + const bakcSingleReward = await paraApeStaking.nftPendingReward( + bakc.address, + [2] + ); + //1800 + 1200 + expect(baycPairReward).to.be.closeTo(parseEther("3000"), parseEther("50")); + //1800 + 1200 + expect(maycPairReward).to.be.closeTo(parseEther("3000"), parseEther("50")); + //1800 + 0 + expect(baycSingleReward).to.be.closeTo( + parseEther("1800"), + parseEther("50") + ); + //1800 + expect(maycSingleReward).to.be.closeTo( + parseEther("1800"), + parseEther("50") + ); + //1200 + expect(bakcSingleReward).to.be.closeTo( + parseEther("1200"), + parseEther("50") + ); + + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 0) + ); + let cApeBalance = await cApe.balanceOf(user1.address); + expect(cApeBalance).to.be.closeTo(parseEther("3000"), parseEther("50")); + await waitForTx( + await nMAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 0) + ); + cApeBalance = await cApe.balanceOf(user1.address); + expect(cApeBalance).to.be.closeTo(parseEther("6000"), parseEther("100")); + await waitForTx( + await nBAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + cApeBalance = await cApe.balanceOf(user1.address); + expect(cApeBalance).to.be.closeTo(parseEther("7800"), parseEther("150")); + await waitForTx( + await nMAYC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 1) + ); + cApeBalance = await cApe.balanceOf(user1.address); + expect(cApeBalance).to.be.closeTo(parseEther("9600"), parseEther("200")); + await waitForTx( + await nBAKC + .connect(user1.signer) + .transferFrom(user1.address, user2.address, 2) + ); + cApeBalance = await cApe.balanceOf(user1.address); + expect(cApeBalance).to.be.closeTo(parseEther("10800"), parseEther("250")); + }); }); From 9924c4c6c4f5fb6c33217185abfbd2625deeb97e Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 28 Jul 2023 16:08:37 +0800 Subject: [PATCH 70/99] chore: reduce contract size --- contracts/apestaking/ParaApeStaking.sol | 32 ++++-------------- .../logic/ApeStakingSinglePoolLogic.sol | 33 +++++++++++++++++-- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 630f92c85..346e21787 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -474,32 +474,14 @@ contract ParaApeStaking is whenNotPaused nonReentrant { - uint256 arrayLength = tokenIds.length; + ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; - uint32[] memory singlePoolTokenIds = new uint32[](arrayLength); - uint256 singlePoolCount = 0; - - for (uint256 index = 0; index < arrayLength; index++) { - uint32 tokenId = tokenIds[index]; - if (poolStates[poolId].tokenStatus[tokenId].isInPool) { - singlePoolTokenIds[singlePoolCount] = tokenId; - singlePoolCount++; - } - } - - if (singlePoolCount > 0) { - assembly { - mstore(singlePoolTokenIds, singlePoolCount) - } - - ApeStakingVaultCacheVars memory vars = _createCacheVars(); - ApeStakingSinglePoolLogic.claimNFT( - poolStates[poolId], - vars, - bakc, - singlePoolTokenIds - ); - } + ApeStakingSinglePoolLogic.tryClaimNFT( + poolStates[poolId], + vars, + bakc, + tokenIds + ); } function nApeOwnerChangeCallback(bool isBAYC, uint32[] calldata tokenIds) diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 53f883b8a..5dea89ef0 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -437,6 +437,35 @@ library ApeStakingSinglePoolLogic { } } + function tryClaimNFT( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address nft, + uint32[] calldata tokenIds + ) external { + ApeStakingCommonLogic.validateTokenIdArray(tokenIds); + + uint256 arrayLength = tokenIds.length; + uint32[] memory singlePoolTokenIds = new uint32[](arrayLength); + uint256 singlePoolCount = 0; + + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + if (poolState.tokenStatus[tokenId].isInPool) { + singlePoolTokenIds[singlePoolCount] = tokenId; + singlePoolCount++; + } + } + + if (singlePoolCount > 0) { + assembly { + mstore(singlePoolTokenIds, singlePoolCount) + } + + _claimNFT(poolState, vars, true, nft, singlePoolTokenIds); + } + } + function _unstakeApe( mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, mapping(address => uint256) storage cApeShareBalance, @@ -702,7 +731,7 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, address nft, - uint32[] calldata tokenIds + uint32[] memory tokenIds ) internal view @@ -747,7 +776,7 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool needUpdateStatus, address nft, - uint32[] calldata tokenIds + uint32[] memory tokenIds ) internal returns (address) { ( address owner, From 4a3dce9b6e106620bebb76a3dfc773a7976d0955 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 28 Jul 2023 19:38:57 +0800 Subject: [PATCH 71/99] chore: gas optimization --- .../logic/ApeStakingSinglePoolLogic.sol | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 5dea89ef0..29f96f01e 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -533,21 +533,19 @@ library ApeStakingSinglePoolLogic { } } - apePoolState.stakingPosition -= singleStakingCount; - IParaApeStaking.PoolState storage bakcPoolState = poolStates[ - ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID - ]; - bakcPoolState.stakingPosition -= pairStakingCount; - - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); - vars.latestBorrowIndex = IPool(vars.pool) - .getReserveNormalizedVariableDebt(vars.cApe); + if (singleStakingCount > 0 || pairStakingCount > 0) { + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + vars.latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); + } + uint256 totalClaimed = 0; if (singleStakingCount > 0) { assembly { mstore(_nfts, singleStakingCount) } + apePoolState.stakingPosition -= singleStakingCount; vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); if (isBAYC) { @@ -557,10 +555,7 @@ library ApeStakingSinglePoolLogic { } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); + totalClaimed += vars.totalClaimedApe; (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic .calculateRepayAndCompound( @@ -574,6 +569,8 @@ library ApeStakingSinglePoolLogic { assembly { mstore(_nftPairs, pairStakingCount) } + poolStates[ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID] + .stakingPosition -= pairStakingCount; vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); ApeCoinStaking.PairNftWithdrawWithAmount[] @@ -587,10 +584,7 @@ library ApeStakingSinglePoolLogic { } vars.balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); vars.totalClaimedApe = vars.balanceAfter - vars.balanceBefore; - IAutoCompoundApe(vars.cApe).deposit( - address(this), - vars.totalClaimedApe - ); + totalClaimed += vars.totalClaimedApe; ( uint256 bakcTotalRepay, @@ -600,6 +594,9 @@ library ApeStakingSinglePoolLogic { vars.totalCompoundFee += bakcCompoundFee; } + if (totalClaimed > 0) { + IAutoCompoundApe(vars.cApe).deposit(address(this), totalClaimed); + } if (vars.totalRepay > 0) { IPool(vars.pool).repay(vars.cApe, vars.totalRepay, address(this)); } @@ -664,19 +661,20 @@ library ApeStakingSinglePoolLogic { } } - assembly { - mstore(baycPair, baycPairCount) - } - assembly { - mstore(maycPair, maycPairCount) - } - - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); - vars.latestBorrowIndex = IPool(vars.pool) - .getReserveNormalizedVariableDebt(vars.cApe); if (baycPairCount > 0 || maycPairCount > 0) { + assembly { + mstore(baycPair, baycPairCount) + } + assembly { + mstore(maycPair, maycPairCount) + } + + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + vars.latestBorrowIndex = IPool(vars.pool) + .getReserveNormalizedVariableDebt(vars.cApe); + bakcPoolState.stakingPosition -= (baycPairCount + maycPairCount); vars.balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); From d95441833cb03ae2fbd963795fa360b9fe380d55 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Sat, 29 Jul 2023 17:45:00 +0800 Subject: [PATCH 72/99] chore: add time lock for sApe --- .../apestaking/logic/ApeCoinPoolLogic.sol | 34 +++- contracts/interfaces/IPoolApeStaking.sol | 5 + contracts/interfaces/IPoolParameters.sol | 42 ----- contracts/misc/TimeLock.sol | 3 + .../protocol/libraries/types/DataTypes.sol | 6 - contracts/protocol/pool/PoolApeStaking.sol | 22 ++- contracts/protocol/pool/PoolParameters.sol | 46 ------ test/_timelock.spec.ts | 153 +++++++++++++++++- 8 files changed, 210 insertions(+), 101 deletions(-) diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index ea8aa2197..b9836622a 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.10; import {IPool} from "../../interfaces/IPool.sol"; import "../../interfaces/IParaApeStaking.sol"; import "../../interfaces/IApeCoinPool.sol"; +import "../../interfaces/ITimeLock.sol"; import {IERC20, SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; @@ -74,8 +75,7 @@ library ApeCoinPoolLogic { sApeBalance[user] = sApeBalanceCache; _validateDropSApeBalance(pool, sApeReserveId, user); - - IERC20(cApe).safeTransfer(receiver, amount); + _sendUserFunds(pool, cApe, amount, receiver); } function depositApeCoinPool( @@ -848,7 +848,7 @@ library ApeCoinPoolLogic { if (cashAmount > 0) { _validateDropSApeBalance(vars.pool, vars.sApeReserveId, user); - IERC20(cashToken).safeTransfer(user, cashAmount); + _sendUserFunds(vars.pool, cashToken, cashAmount, user); } } @@ -935,4 +935,32 @@ library ApeCoinPoolLogic { ); } } + + function _sendUserFunds( + address pool, + address asset, + uint256 amount, + address user + ) internal { + address receiver = user; + DataTypes.TimeLockParams memory timeLockParams = IPool(pool) + .calculateTimeLockParams(asset, amount); + if (timeLockParams.releaseTime != 0) { + ITimeLock timeLock = IPool(pool).TIME_LOCK(); + uint256[] memory amounts = new uint256[](1); + amounts[0] = amount; + + timeLock.createAgreement( + DataTypes.AssetType.ERC20, + DataTypes.TimeLockActionType.WITHDRAW, + address(0), + asset, + amounts, + user, + timeLockParams.releaseTime + ); + receiver = address(timeLock); + } + IERC20(asset).safeTransfer(receiver, amount); + } } diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 8b4a223f8..dbbb7b3f2 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.10; import "../dependencies/yoga-labs/ApeCoinStaking.sol"; import "./IParaApeStaking.sol"; +import {DataTypes} from "../protocol/libraries/types/DataTypes.sol"; /** * @title IPoolApeStaking @@ -21,4 +22,8 @@ interface IPoolApeStaking { uint256 borrowAmount, bool openSApeCollateralFlag ) external; + + function calculateTimeLockParams(address asset, uint256 amount) + external + returns (DataTypes.TimeLockParams memory); } diff --git a/contracts/interfaces/IPoolParameters.sol b/contracts/interfaces/IPoolParameters.sol index be17782a1..a21af0411 100644 --- a/contracts/interfaces/IPoolParameters.sol +++ b/contracts/interfaces/IPoolParameters.sol @@ -26,19 +26,6 @@ interface IPoolParameters { uint256 variableBorrowIndex ); - /** - * @dev Emitted when the value of claim for yield incentive rate update - **/ - event ClaimApeForYieldIncentiveUpdated(uint256 oldValue, uint256 newValue); - - /** - * @dev Emitted when the address of claim for yield incentive bot update - **/ - event ClaimApeForYieldIncentiveBotUpdated( - address oldValue, - address newValue - ); - /** * @notice Initializes a reserve, activating it, assigning an xToken and debt tokens and an * interest rate strategy @@ -138,35 +125,6 @@ interface IPoolParameters { */ function revokeUnlimitedApprove(address token, address to) external; - /** - * @notice undate fee percentage for claim ape for compound - * @param fee new fee percentage - */ - function setClaimApeForCompoundFee(uint256 fee) external; - - /** - * @notice undate compound bot for claim ape for compound - * @param bot new compound bot - */ - function setClaimApeForCompoundBot(address bot) external; - - /** - * @notice undate ape compound strategy - * @param strategy new compound strategy - */ - function setApeCompoundStrategy( - DataTypes.ApeCompoundStrategy calldata strategy - ) external; - - /** - * @notice get user ape compound strategy - * @param user The user address - */ - function getUserApeCompoundStrategy(address user) - external - view - returns (DataTypes.ApeCompoundStrategy memory); - /** * @notice Set the auction recovery health factor * @param value The new auction health factor diff --git a/contracts/misc/TimeLock.sol b/contracts/misc/TimeLock.sol index 229b535d4..98fc6a568 100644 --- a/contracts/misc/TimeLock.sol +++ b/contracts/misc/TimeLock.sol @@ -34,6 +34,7 @@ contract TimeLock is ITimeLock, ReentrancyGuardUpgradeable, IERC721Receiver { address private immutable weth; address private immutable wpunk; address private immutable Punk; + address private immutable PARA_APE_STAKING; /** * @dev Only POOL or callerTag asset's xToken can call functions marked by this modifier. @@ -41,6 +42,7 @@ contract TimeLock is ITimeLock, ReentrancyGuardUpgradeable, IERC721Receiver { modifier onlyValidCaller(address sourceAsset) { require( msg.sender == address(POOL) || + msg.sender == PARA_APE_STAKING || msg.sender == POOL.getReserveXToken(sourceAsset), Errors.CALLER_NOT_ALLOWED ); @@ -72,6 +74,7 @@ contract TimeLock is ITimeLock, ReentrancyGuardUpgradeable, IERC721Receiver { ? IWrappedPunks(_wpunk).punkContract() : address(0); weth = provider.getWETH(); + PARA_APE_STAKING = POOL.paraApeStaking(); } function initialize() public initializer { diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index 0cec1d03a..fb7a58e35 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -399,12 +399,6 @@ library DataTypes { uint16 _reservesCount; // Auction recovery health factor uint64 _auctionRecoveryHealthFactor; - // Incentive fee for claim ape reward to compound - uint16 _apeCompoundFee; - // Map of user's ape compound strategies - mapping(address => ApeCompoundStrategy) _apeCompoundStrategies; - // Address of ape compound bot - address _apeCompoundBot; } struct ReserveConfigData { diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 076de06d2..8a4053482 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -10,6 +10,7 @@ import {IPool} from "../../interfaces/IPool.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; import "../libraries/logic/BorrowLogic.sol"; +import {GenericLogic} from "../libraries/logic/GenericLogic.sol"; import "../../interfaces/IParaApeStaking.sol"; contract PoolApeStaking is @@ -54,7 +55,7 @@ contract PoolApeStaking is nonReentrant returns (uint256) { - require(msg.sender == PARA_APE_STAKING); + require(msg.sender == PARA_APE_STAKING, Errors.CALLER_NOT_ALLOWED); DataTypes.PoolStorage storage ps = poolStorage(); uint256 latestBorrowIndex = BorrowLogic.executeBorrowWithoutCollateral( @@ -67,6 +68,25 @@ contract PoolApeStaking is return latestBorrowIndex; } + function calculateTimeLockParams(address asset, uint256 amount) + external + returns (DataTypes.TimeLockParams memory) + { + require(msg.sender == PARA_APE_STAKING, Errors.CALLER_NOT_ALLOWED); + DataTypes.PoolStorage storage ps = poolStorage(); + + DataTypes.TimeLockParams memory timeLockParams = GenericLogic + .calculateTimeLockParams( + ps._reserves[asset], + DataTypes.TimeLockFactorParams({ + assetType: DataTypes.AssetType.ERC20, + asset: asset, + amount: amount + }) + ); + return timeLockParams; + } + function borrowAndStakingApeCoin( IParaApeStaking.ApeCoinDepositInfo[] calldata apeCoinDepositInfo, IParaApeStaking.ApeCoinPairDepositInfo[] calldata pairDepositInfo, diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 429ea3089..da5a0320e 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -242,52 +242,6 @@ contract PoolParameters is IERC20(token).approve(to, 0); } - /// @inheritdoc IPoolParameters - function setClaimApeForCompoundFee(uint256 fee) external onlyPoolAdmin { - require(fee < PercentageMath.HALF_PERCENTAGE_FACTOR, "Value Too High"); - DataTypes.PoolStorage storage ps = poolStorage(); - uint256 oldValue = ps._apeCompoundFee; - if (oldValue != fee) { - ps._apeCompoundFee = uint16(fee); - emit ClaimApeForYieldIncentiveUpdated(oldValue, fee); - } - } - - /// @inheritdoc IPoolParameters - function setClaimApeForCompoundBot(address bot) external onlyPoolAdmin { - DataTypes.PoolStorage storage ps = poolStorage(); - address oldValue = ps._apeCompoundBot; - if (oldValue != bot) { - ps._apeCompoundBot = bot; - emit ClaimApeForYieldIncentiveBotUpdated(oldValue, bot); - } - } - - /// @inheritdoc IPoolParameters - function setApeCompoundStrategy( - DataTypes.ApeCompoundStrategy calldata strategy - ) external { - require( - strategy.swapPercent == 0 || - (strategy.ty == DataTypes.ApeCompoundType.SwapAndSupply && - strategy.swapPercent > 0 && - strategy.swapPercent <= PercentageMath.PERCENTAGE_FACTOR), - "Invalid swap percent" - ); - DataTypes.PoolStorage storage ps = poolStorage(); - ps._apeCompoundStrategies[msg.sender] = strategy; - } - - /// @inheritdoc IPoolParameters - function getUserApeCompoundStrategy(address user) - external - view - returns (DataTypes.ApeCompoundStrategy memory strategy) - { - DataTypes.PoolStorage storage ps = poolStorage(); - strategy = ps._apeCompoundStrategies[user]; - } - /// @inheritdoc IPoolParameters function setAuctionRecoveryHealthFactor(uint64 value) external diff --git a/test/_timelock.spec.ts b/test/_timelock.spec.ts index 2b306aecd..a97506a45 100644 --- a/test/_timelock.spec.ts +++ b/test/_timelock.spec.ts @@ -1,36 +1,46 @@ import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; import {expect} from "chai"; import {deployReserveTimeLockStrategy} from "../helpers/contracts-deployments"; -import {MAX_UINT_AMOUNT} from "../helpers/constants"; +import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; import { + getAutoCompoundApe, + getParaApeStaking, getPoolConfiguratorProxy, + getPTokenSApe, getTimeLockProxy, } from "../helpers/contracts-getters"; import {convertToCurrencyDecimals} from "../helpers/contracts-helpers"; import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {eContractid} from "../helpers/types"; import {testEnvFixture} from "./helpers/setup-env"; -import {supplyAndValidate} from "./helpers/validated-steps"; +import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; import {parseEther} from "ethers/lib/utils"; import {almostEqual} from "./helpers/uniswapv3-helper"; +import {AutoCompoundApe, ParaApeStaking, PTokenSApe} from "../types"; describe("TimeLock functionality tests", () => { const minTime = 5; const midTime = 300; const maxTime = 3600; let timeLockProxy; + let cApe: AutoCompoundApe; + let paraApeStaking: ParaApeStaking; + let pSApeCoin: PTokenSApe; + const sApeAddress = ONE_ADDRESS; const fixture = async () => { const testEnv = await loadFixture(testEnvFixture); const { dai, + ape, usdc, pool, mayc, weth, wPunk, - users: [user1, user2], + users: [user1, user2, , , , user6], poolAdmin, + protocolDataProvider, } = testEnv; // User 1 - Deposit dai @@ -42,6 +52,22 @@ describe("TimeLock functionality tests", () => { await supplyAndValidate(weth, "1", user1, true); + cApe = await getAutoCompoundApe(); + const MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + paraApeStaking = await getParaApeStaking(); + const {xTokenAddress: pSApeCoinAddress} = + await protocolDataProvider.getReserveTokensAddresses(sApeAddress); + pSApeCoin = await getPTokenSApe(pSApeCoinAddress); + + // user6 deposit MINIMUM_LIQUIDITY to make test case easy + await mintAndValidate(ape, "1", user6); + await waitForTx( + await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) + ); + const minThreshold = await convertToCurrencyDecimals(usdc.address, "1000"); const midThreshold = await convertToCurrencyDecimals(usdc.address, "2000"); const minThresholdNFT = 2; @@ -90,6 +116,19 @@ describe("TimeLock functionality tests", () => { defaultStrategy.address ) ); + await waitForTx( + await poolConfigurator + .connect(poolAdmin.signer) + .setReserveTimeLockStrategyAddress(ape.address, defaultStrategy.address) + ); + await waitForTx( + await poolConfigurator + .connect(poolAdmin.signer) + .setReserveTimeLockStrategyAddress( + cApe.address, + defaultStrategy.address + ) + ); await waitForTx( await poolConfigurator .connect(poolAdmin.signer) @@ -504,4 +543,112 @@ describe("TimeLock functionality tests", () => { await expect(balanceAfter).to.be.eq(balanceBefore.add(3)); }); + + it("sApe work as expected0", async () => { + const { + users: [user1], + ape, + bayc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await mintAndValidate(ape, "200000", user1); + + await waitForTx( + await ape + .connect(user1.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.equal( + parseEther("200000") + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawApeCoinPool({ + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ); + expect(await ape.balanceOf(user1.address)).to.be.equal("0"); + expect(await ape.balanceOf(timeLockProxy.address)).to.be.equal( + parseEther("200000") + ); + await advanceTimeAndBlock(13 * 3600); + await waitForTx(await timeLockProxy.connect(user1.signer).claim(["0"])); + expect(await ape.balanceOf(timeLockProxy.address)).to.be.equal("0"); + expect(await ape.balanceOf(user1.address)).to.be.equal( + parseEther("200000") + ); + }); + + it("sApe work as expected1", async () => { + const { + users: [user1], + ape, + bayc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await mintAndValidate(ape, "200000", user1); + + await waitForTx( + await ape + .connect(user1.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ); + + expect(await pSApeCoin.balanceOf(user1.address)).to.be.equal( + parseEther("200000") + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).withdrawApeCoinPool({ + cashToken: ape.address, + cashAmount: "0", + isBAYC: true, + tokenIds: [0], + }) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(user1.address, parseEther("200000")) + ); + + expect(await cApe.balanceOf(user1.address)).to.be.equal("0"); + expect(await cApe.balanceOf(timeLockProxy.address)).to.be.closeTo( + parseEther("200000"), + parseEther("1") + ); + + await advanceTimeAndBlock(13 * 3600); + await waitForTx(await timeLockProxy.connect(user1.signer).claim(["0"])); + + expect(await cApe.balanceOf(user1.address)).to.be.closeTo( + parseEther("200000"), + parseEther("1") + ); + }); }); From 1e5a776f82517c66846f758ebbd5b0e62f772f65 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 31 Jul 2023 09:19:49 +0800 Subject: [PATCH 73/99] chore: reduce contract size --- contracts/apestaking/ParaApeStaking.sol | 10 +++------- .../apestaking/logic/ApeStakingP2PLogic.sol | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 346e21787..cfaa901dc 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -602,14 +602,10 @@ contract ParaApeStaking is nonReentrant { require(msg.sender == listingOrder.offerer, Errors.NOT_ORDER_OFFERER); - bytes32 orderHash = ApeStakingP2PLogic.getListingOrderHash( - listingOrder + bytes32 orderHash = ApeStakingP2PLogic.cancelListing( + listingOrder, + listingOrderStatus ); - require( - listingOrderStatus[orderHash] != ListingOrderStatus.Cancelled, - Errors.ORDER_ALREADY_CANCELLED - ); - listingOrderStatus[orderHash] = ListingOrderStatus.Cancelled; emit OrderCancelled(orderHash, listingOrder.offerer); } diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index 0720bec69..267af520b 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -37,6 +37,23 @@ library ApeStakingP2PLogic { bytes32 internal constant MATCHED_ORDER_HASH = 0x7db3dae7d89c86e6881a66a131841305c008b207e41ff86a804b4bb056652808; + function cancelListing( + IApeStakingP2P.ListingOrder calldata listingOrder, + mapping(bytes32 => IApeStakingP2P.ListingOrderStatus) + storage listingOrderStatus + ) external returns (bytes32) { + bytes32 orderHash = getListingOrderHash(listingOrder); + require( + listingOrderStatus[orderHash] != + IApeStakingP2P.ListingOrderStatus.Cancelled, + Errors.ORDER_ALREADY_CANCELLED + ); + listingOrderStatus[orderHash] = IApeStakingP2P + .ListingOrderStatus + .Cancelled; + return orderHash; + } + function matchPairStakingList( IApeStakingP2P.ListingOrder calldata apeOrder, IApeStakingP2P.ListingOrder calldata apeCoinOrder, From 31ea3276ef4ed0024c0e568a9900fd0758f38df3 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 1 Aug 2023 09:19:13 +0800 Subject: [PATCH 74/99] chore:support ape coin order as sApe balance --- contracts/apestaking/ParaApeStaking.sol | 24 +- .../apestaking/logic/ApeCoinPoolLogic.sol | 39 +- .../apestaking/logic/ApeStakingP2PLogic.sol | 113 ++++-- contracts/interfaces/IApeStakingP2P.sol | 1 - test/auto_compound_ape.spec.ts | 11 - test/helpers/p2ppairstaking-helper.ts | 11 +- test/p2p_pair_staking.spec.ts | 357 ++++++++---------- test/para_pool_ape_staking.spec.ts | 88 ++++- 8 files changed, 379 insertions(+), 265 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index cfaa901dc..95eaf048f 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -259,7 +259,22 @@ contract ParaApeStaking is sApeBalance[to].freeShareBalance += shareAmount.toUint128(); } - function withdrawFreeSApe(address receiver, uint128 amount) + function depositFreeSApe(address cashAsset, uint128 amount) + external + whenNotPaused + nonReentrant + { + ApeCoinPoolLogic.depositFreeSApe( + sApeBalance, + apeCoin, + cApe, + msg.sender, + cashAsset, + amount + ); + } + + function withdrawFreeSApe(address receiveAsset, uint128 amount) external whenNotPaused nonReentrant @@ -267,10 +282,11 @@ contract ParaApeStaking is ApeCoinPoolLogic.withdrawFreeSApe( sApeBalance, pool, + apeCoin, cApe, sApeReserveId, msg.sender, - receiver, + receiveAsset, amount ); } @@ -601,7 +617,6 @@ contract ParaApeStaking is override nonReentrant { - require(msg.sender == listingOrder.offerer, Errors.NOT_ORDER_OFFERER); bytes32 orderHash = ApeStakingP2PLogic.cancelListing( listingOrder, listingOrderStatus @@ -623,6 +638,7 @@ contract ParaApeStaking is listingOrderStatus, matchedOrders, apeMatchedCount, + sApeBalance, vars ); @@ -646,6 +662,7 @@ contract ParaApeStaking is listingOrderStatus, matchedOrders, apeMatchedCount, + sApeBalance, vars ); @@ -668,6 +685,7 @@ contract ParaApeStaking is matchedOrders, cApeShareBalance, apeMatchedCount, + sApeBalance, vars, orderHash ); diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index b9836622a..fc2807894 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -56,15 +56,47 @@ library ApeCoinPoolLogic { uint256 bakcTokenId ); + function depositFreeSApe( + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + address apeCoin, + address cApe, + address user, + address cashAsset, + uint128 amount + ) external { + require( + cashAsset == cApe || cashAsset == apeCoin, + Errors.INVALID_TOKEN + ); + + IERC20(cashAsset).safeTransferFrom(user, address(this), amount); + + if (cashAsset == apeCoin) { + IAutoCompoundApe(cApe).deposit(address(this), amount); + } + + //update sApe balance + IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; + uint256 shareAmount = ICApe(cApe).getShareByPooledApe(amount); + sApeBalanceCache.freeShareBalance += shareAmount.toUint128(); + sApeBalance[user] = sApeBalanceCache; + } + function withdrawFreeSApe( mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, address pool, + address apeCoin, address cApe, uint16 sApeReserveId, address user, - address receiver, + address receiveAsset, uint128 amount ) external { + require( + receiveAsset == cApe || receiveAsset == apeCoin, + Errors.INVALID_TOKEN + ); + IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; uint256 shareAmount = ICApe(cApe).getShareByPooledApe(amount); require( @@ -75,7 +107,10 @@ library ApeCoinPoolLogic { sApeBalance[user] = sApeBalanceCache; _validateDropSApeBalance(pool, sApeReserveId, user); - _sendUserFunds(pool, cApe, amount, receiver); + if (receiveAsset == apeCoin) { + IAutoCompoundApe(cApe).withdraw(amount); + } + _sendUserFunds(pool, receiveAsset, amount, user); } function depositApeCoinPool( diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index 267af520b..8403e33fa 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -13,6 +13,7 @@ import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import "./ApeStakingCommonLogic.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import "../../protocol/libraries/helpers/Errors.sol"; +import {DataTypes} from "../../protocol/libraries/types/DataTypes.sol"; /** * @title ApeStakingVaultLogic library @@ -33,7 +34,7 @@ library ApeStakingP2PLogic { bytes32 internal constant LISTING_ORDER_HASH = 0x227f9dd14259caacdbcf45411b33cf1c018f31bd3da27e613a66edf8ae45814f; - //keccak256("MatchedOrder(uint8 stakingType,address apeToken,uint32 apeTokenId,uint32 apeShare,uint32 bakcTokenId,uint32 bakcShare,address apeCoinOfferer,uint32 apeCoinShare,uint256 apePrincipleAmount)"); + //keccak256("MatchedOrder(uint8 stakingType,address apeToken,uint32 apeTokenId,uint32 apeShare,uint32 bakcTokenId,uint32 bakcShare,address apeCoinOfferer,uint32 apeCoinShare)"); bytes32 internal constant MATCHED_ORDER_HASH = 0x7db3dae7d89c86e6881a66a131841305c008b207e41ff86a804b4bb056652808; @@ -42,6 +43,8 @@ library ApeStakingP2PLogic { mapping(bytes32 => IApeStakingP2P.ListingOrderStatus) storage listingOrderStatus ) external returns (bytes32) { + require(msg.sender == listingOrder.offerer, Errors.NOT_ORDER_OFFERER); + bytes32 orderHash = getListingOrderHash(listingOrder); require( listingOrderStatus[orderHash] != @@ -61,6 +64,7 @@ library ApeStakingP2PLogic { storage listingOrderStatus, mapping(bytes32 => IApeStakingP2P.MatchedOrder) storage matchedOrders, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars ) external returns (bytes32 orderHash) { //1 validate all order @@ -89,7 +93,16 @@ library ApeStakingP2PLogic { //3 transfer token _handleApeTransfer(apeMatchedCount, apeOrder, vars); - uint256 apeAmount = _handleCApeTransferAndConvert(apeCoinOrder, vars); + uint256 apeCoinCap = getApeCoinStakingCap( + apeCoinOrder.stakingType, + vars + ); + _prepareApeCoin( + sApeBalance, + vars.cApe, + apeCoinOrder.offerer, + apeCoinCap + ); //4 create match order IApeStakingP2P.MatchedOrder memory matchedOrder = IApeStakingP2P @@ -102,7 +115,6 @@ library ApeStakingP2PLogic { bakcShare: 0, apeCoinOfferer: apeCoinOrder.offerer, apeCoinShare: apeCoinOrder.share, - apePrincipleAmount: apeAmount, apeCoinListingOrderHash: apeCoinListingOrderHash }); orderHash = getMatchedOrderHash(matchedOrder); @@ -112,7 +124,7 @@ library ApeStakingP2PLogic { ApeCoinStaking.SingleNft[] memory singleNft = new ApeCoinStaking.SingleNft[](1); singleNft[0].tokenId = apeOrder.tokenId; - singleNft[0].amount = apeAmount.toUint224(); + singleNft[0].amount = apeCoinCap.toUint224(); if (apeOrder.stakingType == IApeStakingP2P.StakingType.BAYCStaking) { vars.apeCoinStaking.depositBAYC(singleNft); } else { @@ -135,6 +147,7 @@ library ApeStakingP2PLogic { storage listingOrderStatus, mapping(bytes32 => IApeStakingP2P.MatchedOrder) storage matchedOrders, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars ) external returns (bytes32 orderHash) { //1 validate all order @@ -169,7 +182,16 @@ library ApeStakingP2PLogic { address(this), bakcOrder.tokenId ); - uint256 apeAmount = _handleCApeTransferAndConvert(apeCoinOrder, vars); + uint256 apeCoinCap = getApeCoinStakingCap( + apeCoinOrder.stakingType, + vars + ); + _prepareApeCoin( + sApeBalance, + vars.cApe, + apeCoinOrder.offerer, + apeCoinCap + ); //4 create match order IApeStakingP2P.MatchedOrder memory matchedOrder = IApeStakingP2P @@ -182,7 +204,6 @@ library ApeStakingP2PLogic { bakcShare: bakcOrder.share, apeCoinOfferer: apeCoinOrder.offerer, apeCoinShare: apeCoinOrder.share, - apePrincipleAmount: apeAmount, apeCoinListingOrderHash: apeCoinListingOrderHash }); orderHash = getMatchedOrderHash(matchedOrder); @@ -195,7 +216,7 @@ library ApeStakingP2PLogic { ); _stakingPairs[0].mainTokenId = apeOrder.tokenId; _stakingPairs[0].bakcTokenId = bakcOrder.tokenId; - _stakingPairs[0].amount = apeAmount.toUint184(); + _stakingPairs[0].amount = apeCoinCap.toUint184(); ApeCoinStaking.PairNftDepositWithAmount[] memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( 0 @@ -220,6 +241,7 @@ library ApeStakingP2PLogic { mapping(bytes32 => IApeStakingP2P.MatchedOrder) storage matchedOrders, mapping(address => uint256) storage cApeShareBalance, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bytes32 orderHash ) external { @@ -227,14 +249,13 @@ library ApeStakingP2PLogic { //1 check if have permission to break up address apeNToken = _getApeNTokenAddress(vars, order.apeToken); - address apeNTokenOwner = IERC721(apeNToken).ownerOf(order.apeTokenId); - address nBakcOwner = IERC721(vars.nBakc).ownerOf(order.bakcTokenId); require( - msg.sender == apeNTokenOwner || - msg.sender == order.apeCoinOfferer || - (msg.sender == nBakcOwner && - order.stakingType == - IApeStakingP2P.StakingType.BAKCPairStaking), + msg.sender == order.apeCoinOfferer || + msg.sender == IERC721(apeNToken).ownerOf(order.apeTokenId) || + (order.stakingType == + IApeStakingP2P.StakingType.BAKCPairStaking && + msg.sender == + IERC721(vars.nBakc).ownerOf(order.bakcTokenId)), Errors.NO_BREAK_UP_PERMISSION ); @@ -252,6 +273,7 @@ library ApeStakingP2PLogic { delete matchedOrders[orderHash]; //4 exit from ApeCoinStaking + uint256 apeCoinCap = getApeCoinStakingCap(order.stakingType, vars); if ( order.stakingType == IApeStakingP2P.StakingType.BAYCStaking || order.stakingType == IApeStakingP2P.StakingType.MAYCStaking @@ -259,7 +281,7 @@ library ApeStakingP2PLogic { ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](1); _nfts[0].tokenId = order.apeTokenId; - _nfts[0].amount = order.apePrincipleAmount.toUint224(); + _nfts[0].amount = apeCoinCap.toUint224(); if (order.stakingType == IApeStakingP2P.StakingType.BAYCStaking) { vars.apeCoinStaking.withdrawSelfBAYC(_nfts); } else { @@ -272,7 +294,7 @@ library ApeStakingP2PLogic { ); _nfts[0].mainTokenId = order.apeTokenId; _nfts[0].bakcTokenId = order.bakcTokenId; - _nfts[0].amount = order.apePrincipleAmount.toUint184(); + _nfts[0].amount = apeCoinCap.toUint184(); _nfts[0].isUncommit = true; ApeCoinStaking.PairNftWithdrawWithAmount[] memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( @@ -297,9 +319,11 @@ library ApeStakingP2PLogic { } apeMatchedCount[order.apeToken][order.apeTokenId] = matchedCount - 1; - IAutoCompoundApe(vars.cApe).deposit( + _handleApeCoin( + sApeBalance, + vars.cApe, order.apeCoinOfferer, - order.apePrincipleAmount + apeCoinCap ); if (order.stakingType == IApeStakingP2P.StakingType.BAKCPairStaking) { IERC721(vars.bakc).safeTransferFrom( @@ -502,7 +526,10 @@ library ApeStakingP2PLogic { listingOrderStatus, apeCoinOrder ); - require(apeCoinOrder.token == vars.cApe, Errors.INVALID_TOKEN); + require( + apeCoinOrder.token == DataTypes.SApeAddress, + Errors.INVALID_TOKEN + ); require( listingOrderStatus[orderHash] != IApeStakingP2P.ListingOrderStatus.Matched, @@ -546,21 +573,40 @@ library ApeStakingP2PLogic { apeMatchedCount[order.token][order.tokenId] = currentMatchCount + 1; } - function _handleCApeTransferAndConvert( - IApeStakingP2P.ListingOrder calldata apeCoinOrder, - IParaApeStaking.ApeStakingVaultCacheVars memory vars - ) internal returns (uint256) { - uint256 apeAmount = getApeCoinStakingCap( - apeCoinOrder.stakingType, - vars + function _prepareApeCoin( + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + address cApe, + address user, + uint256 amount + ) internal { + uint256 freeShareBalanceNeeded = ICApe(cApe).getShareByPooledApe( + amount ); - IERC20(vars.cApe).safeTransferFrom( - apeCoinOrder.offerer, - address(this), - apeAmount + IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; + require( + sApeBalanceCache.freeShareBalance >= freeShareBalanceNeeded, + Errors.SAPE_FREE_BALANCE_NOT_ENOUGH ); - IAutoCompoundApe(vars.cApe).withdraw(apeAmount); - return apeAmount; + sApeBalanceCache.freeShareBalance -= freeShareBalanceNeeded.toUint128(); + sApeBalanceCache.stakedBalance += amount.toUint128(); + sApeBalance[user] = sApeBalanceCache; + + IAutoCompoundApe(cApe).withdraw(amount); + } + + function _handleApeCoin( + mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, + address cApe, + address user, + uint256 amount + ) internal { + uint256 freeSApeBalanceAdded = ICApe(cApe).getShareByPooledApe(amount); + IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; + sApeBalanceCache.freeShareBalance += freeSApeBalanceAdded.toUint128(); + sApeBalanceCache.stakedBalance -= amount.toUint128(); + sApeBalance[user] = sApeBalanceCache; + + IAutoCompoundApe(cApe).deposit(address(this), amount); } function _getApeNTokenAddress( @@ -592,8 +638,7 @@ library ApeStakingP2PLogic { order.bakcTokenId, order.bakcShare, order.apeCoinOfferer, - order.apeCoinShare, - order.apePrincipleAmount + order.apeCoinShare ) ); } diff --git a/contracts/interfaces/IApeStakingP2P.sol b/contracts/interfaces/IApeStakingP2P.sol index b2d000bf9..27b00f1aa 100644 --- a/contracts/interfaces/IApeStakingP2P.sol +++ b/contracts/interfaces/IApeStakingP2P.sol @@ -36,7 +36,6 @@ interface IApeStakingP2P { uint32 bakcShare; address apeCoinOfferer; uint32 apeCoinShare; - uint256 apePrincipleAmount; bytes32 apeCoinListingOrderHash; } diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index fd87eea42..58607c80a 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -40,7 +40,6 @@ describe("Auto Compound Ape Test", () => { users: [user1, user2, , , user3, user4, user5], apeCoinStaking, pool, - poolAdmin, nftPositionManager, } = testEnv; @@ -84,16 +83,6 @@ describe("Auto Compound Ape Test", () => { await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) ); - await waitForTx( - await pool.connect(poolAdmin.signer).setClaimApeForCompoundFee(30) - ); - - await waitForTx( - await pool - .connect(poolAdmin.signer) - .setClaimApeForCompoundBot(user2.address) - ); - // send extra tokens to the apestaking contract for rewards await waitForTx( await ape diff --git a/test/helpers/p2ppairstaking-helper.ts b/test/helpers/p2ppairstaking-helper.ts index c0cc0e30e..1784378d0 100644 --- a/test/helpers/p2ppairstaking-helper.ts +++ b/test/helpers/p2ppairstaking-helper.ts @@ -1,10 +1,5 @@ import {DRE} from "../../helpers/misc-utils"; -import { - AutoCompoundApe, - MintableERC20, - MintableERC721, - ParaApeStaking, -} from "../../types"; +import {ParaApeStaking} from "../../types"; import {SignerWithAddress} from "./make-suite"; import {convertSignatureToEIP2098} from "../../helpers/seaport-helpers/encoding"; import {BigNumberish, BytesLike} from "ethers"; @@ -25,7 +20,7 @@ export type ListingOrder = { export async function getSignedListingOrder( p2pPairStaking: ParaApeStaking, stakingType: number, - listingToken: MintableERC721 | MintableERC20 | AutoCompoundApe, + listingToken: string, tokenId: number, share: number, signer: SignerWithAddress @@ -54,7 +49,7 @@ export async function getSignedListingOrder( const order = { stakingType: stakingType, offerer: signer.address, - token: listingToken.address, + token: listingToken, tokenId: tokenId, share: share, startTime: now - 3600, diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index c9659aafa..eaceb1bb2 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -8,7 +8,7 @@ import { getAutoCompoundApe, getParaApeStaking, } from "../helpers/contracts-getters"; -import {MAX_UINT_AMOUNT} from "../helpers/constants"; +import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; import {parseEther} from "ethers/lib/utils"; @@ -60,14 +60,13 @@ describe("P2P Pair Staking Test", () => { await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) ); + //user2 deposit free sApe await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe + await ape .connect(user2.signer) .approve(paraApeStaking.address, MAX_UINT_AMOUNT) ); + await mintAndValidate(ape, "1000000", user2); return testEnv; }; @@ -81,17 +80,18 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - bayc, + bayc.address, 0, 2000, user1 @@ -99,7 +99,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - cApe, + ONE_ADDRESS, 0, 8000, user2 @@ -113,6 +113,14 @@ describe("P2P Pair Staking Test", () => { const logLength = txReceipt.logs.length; const orderHash = txReceipt.logs[logLength - 1].data; + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( @@ -151,7 +159,14 @@ describe("P2P Pair Staking Test", () => { ); expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); - almostEqual(await cApe.balanceOf(user2.address), apeAmount); + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + almostEqual( await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") @@ -171,23 +186,18 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await mayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); const apeAmount = await paraApeStaking.getApeCoinStakingCap(1); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 1, - mayc, + mayc.address, 0, 2000, user1 @@ -195,7 +205,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 1, - cApe, + ONE_ADDRESS, 0, 8000, user2 @@ -209,6 +219,14 @@ describe("P2P Pair Staking Test", () => { const logLength = txReceipt.logs.length; const orderHash = txReceipt.logs[logLength - 1].data; + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( @@ -247,7 +265,14 @@ describe("P2P Pair Staking Test", () => { ); expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); - almostEqual(await cApe.balanceOf(user2.address), apeAmount); + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + almostEqual( await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") @@ -270,28 +295,18 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bayc, + bayc.address, 0, 2000, user1 @@ -299,7 +314,7 @@ describe("P2P Pair Staking Test", () => { const user3SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bakc, + bakc.address, 0, 2000, user3 @@ -307,7 +322,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - cApe, + ONE_ADDRESS, 0, 6000, user2 @@ -326,6 +341,14 @@ describe("P2P Pair Staking Test", () => { const logLength = txReceipt.logs.length; const orderHash = txReceipt.logs[logLength - 1].data; + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( @@ -373,7 +396,13 @@ describe("P2P Pair Staking Test", () => { expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(1); - almostEqual(await cApe.balanceOf(user2.address), apeAmount); + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); almostEqual( await paraApeStaking.pendingCApeReward(user1.address), @@ -401,17 +430,18 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(mayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - mayc, + mayc.address, 0, 2000, user1 @@ -419,7 +449,7 @@ describe("P2P Pair Staking Test", () => { const user3SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bakc, + bakc.address, 0, 2000, user3 @@ -427,7 +457,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - cApe, + ONE_ADDRESS, 0, 6000, user2 @@ -446,6 +476,14 @@ describe("P2P Pair Staking Test", () => { const logLength = txReceipt.logs.length; const orderHash = txReceipt.logs[logLength - 1].data; + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( @@ -493,7 +531,14 @@ describe("P2P Pair Staking Test", () => { expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(1); - almostEqual(await cApe.balanceOf(user2.address), apeAmount); + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + almostEqual( await paraApeStaking.pendingCApeReward(user1.address), parseEther("720") @@ -518,22 +563,10 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "10", user1, true); await supplyAndValidate(bakc, "10", user3, true); - await mintAndValidate(ape, "10000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); - await waitForTx( - await cApe + await paraApeStaking .connect(user2.signer) - .deposit(user2.address, parseEther("1000000")) + .depositFreeSApe(ape.address, parseEther("1000000")) ); const txArray: string[] = []; @@ -541,7 +574,7 @@ describe("P2P Pair Staking Test", () => { const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bayc, + bayc.address, i, 2000, user1 @@ -549,7 +582,7 @@ describe("P2P Pair Staking Test", () => { const user3SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bakc, + bakc.address, i, 2000, user3 @@ -557,7 +590,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - cApe, + ONE_ADDRESS, 0, 6000, user2 @@ -597,23 +630,17 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( - await ape + await paraApeStaking .connect(user2.signer) - .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - bayc, + bayc.address, 0, 2000, user1 @@ -621,7 +648,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - ape, + ONE_ADDRESS, 0, 8000, user2 @@ -648,28 +675,17 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user2, true); - await mintAndValidate(ape, "1000000", user3); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( - await bakc + await paraApeStaking .connect(user2.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); - await waitForTx( - await ape - .connect(user3.signer) - .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bayc, + bayc.address, 0, 2000, user1 @@ -677,7 +693,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bakc, + bakc.address, 0, 2000, user2 @@ -685,7 +701,7 @@ describe("P2P Pair Staking Test", () => { const user3SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - ape, + ONE_ADDRESS, 0, 6000, user3 @@ -714,22 +730,17 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - bayc, + bayc.address, 0, 2000, user1 @@ -737,7 +748,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 1, - cApe, + ONE_ADDRESS, 0, 8000, user2 @@ -760,28 +771,17 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bayc, + bayc.address, 0, 2000, user1 @@ -789,7 +789,7 @@ describe("P2P Pair Staking Test", () => { const user3SignedOrder = await getSignedListingOrder( paraApeStaking, 1, - bakc, + bakc.address, 0, 2000, user3 @@ -797,7 +797,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - cApe, + ONE_ADDRESS, 0, 6000, user2 @@ -822,22 +822,17 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - bayc, + bayc.address, 0, 2000, user1 @@ -845,7 +840,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - cApe, + ONE_ADDRESS, 0, 7000, user2 @@ -868,28 +863,17 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bayc, + bayc.address, 0, 2000, user1 @@ -897,7 +881,7 @@ describe("P2P Pair Staking Test", () => { const user3SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bakc, + bakc.address, 0, 2000, user3 @@ -905,7 +889,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - cApe, + ONE_ADDRESS, 0, 7000, user2 @@ -931,7 +915,7 @@ describe("P2P Pair Staking Test", () => { const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - bayc, + bayc.address, 0, 2000, user1 @@ -948,7 +932,7 @@ describe("P2P Pair Staking Test", () => { it("compound fee work as expected", async () => { const { - users: [user1, user2, user3], + users: [user1, user2], bayc, ape, poolAdmin, @@ -958,41 +942,26 @@ describe("P2P Pair Staking Test", () => { await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(50) ); - await supplyAndValidate(bayc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - //deposit cApe for user3 to let exchangeRate > 1 - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("1000")) - ); - - await waitForTx( - await bayc - .connect(user3.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); + await supplyAndValidate(bayc, "1", user1, true); const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - bayc, + bayc.address, 0, 2000, - user3 + user1 ); const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - cApe, + ONE_ADDRESS, 0, 8000, user2 @@ -1015,7 +984,7 @@ describe("P2P Pair Staking Test", () => { ); almostEqual( - await paraApeStaking.pendingCApeReward(user3.address), + await paraApeStaking.pendingCApeReward(user1.address), parseEther("716.4") ); almostEqual( @@ -1039,29 +1008,18 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); - await waitForTx( - await cApe + await paraApeStaking .connect(user2.signer) - .deposit(user2.address, parseEther("500000")) + .depositFreeSApe(ape.address, parseEther("1000000")) ); //match bayc + ApeCoin let user1SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - bayc, + bayc.address, 0, 2000, user1 @@ -1069,7 +1027,7 @@ describe("P2P Pair Staking Test", () => { let user2SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - cApe, + ONE_ADDRESS, 0, 8000, user2 @@ -1087,7 +1045,7 @@ describe("P2P Pair Staking Test", () => { user1SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bayc, + bayc.address, 0, 2000, user1 @@ -1095,7 +1053,7 @@ describe("P2P Pair Staking Test", () => { const user3SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - bakc, + bakc.address, 0, 2000, user3 @@ -1103,7 +1061,7 @@ describe("P2P Pair Staking Test", () => { user2SignedOrder = await getSignedListingOrder( paraApeStaking, 2, - cApe, + ONE_ADDRESS, 0, 6000, user2 @@ -1138,23 +1096,17 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "2", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(paraApeStaking.address, true) - ); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); await waitForTx( - await cApe + await paraApeStaking .connect(user2.signer) - .deposit(user2.address, parseEther("500000")) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder0 = await getSignedListingOrder( paraApeStaking, 0, - bayc, + bayc.address, 0, 2000, user1 @@ -1162,7 +1114,7 @@ describe("P2P Pair Staking Test", () => { const user1SignedOrder1 = await getSignedListingOrder( paraApeStaking, 0, - bayc, + bayc.address, 1, 2000, user1 @@ -1170,7 +1122,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 0, - cApe, + ONE_ADDRESS, 0, 8000, user2 @@ -1210,17 +1162,18 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); const apeAmount = await paraApeStaking.getApeCoinStakingCap(1); await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( paraApeStaking, 1, - mayc, + mayc.address, 0, 2000, user1 @@ -1228,7 +1181,7 @@ describe("P2P Pair Staking Test", () => { const user2SignedOrder = await getSignedListingOrder( paraApeStaking, 1, - cApe, + ONE_ADDRESS, 0, 8000, user2 diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index d5ba5f790..75fcd38fc 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -916,7 +916,7 @@ describe("Para Ape staking ape coin pool test", () => { ); }); - it("sApe test", async () => { + it("sApe test0", async () => { const { users: [user1, user2, liquidator], ape, @@ -1132,6 +1132,86 @@ describe("Para Ape staking ape coin pool test", () => { ); }); + it("sApe test1", async () => { + const { + users: [user1], + ape, + } = await loadFixture(fixture); + await mintAndValidate(ape, "1000000", user1); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositFreeSApe(ape.address, parseEther("1000000")) + ); + + expect(await ape.balanceOf(user1.address)).to.be.equal("0"); + expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( + parseEther("1000000"), + parseEther("1") + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(ape.address, parseEther("1000000")) + ); + + expect(await ape.balanceOf(user1.address)).to.be.closeTo( + parseEther("1000000"), + parseEther("1") + ); + expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( + "0", + parseEther("1") + ); + + await waitForTx( + await ape.connect(user1.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user1.signer) + .deposit(user1.address, parseEther("1000000")) + ); + expect(await ape.balanceOf(user1.address)).to.be.equal("0"); + expect(await cApe.balanceOf(user1.address)).to.be.closeTo( + parseEther("1000000"), + parseEther("1") + ); + + await waitForTx( + await cApe + .connect(user1.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .depositFreeSApe(cApe.address, parseEther("1000000")) + ); + expect(await cApe.balanceOf(user1.address)).to.be.equal("0"); + expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( + parseEther("1000000"), + parseEther("1") + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(cApe.address, parseEther("1000000")) + ); + + expect(await cApe.balanceOf(user1.address)).to.be.closeTo( + parseEther("1000000"), + parseEther("1") + ); + expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( + "0", + parseEther("1") + ); + }); + it("depositApeCoinPool revert test", async () => { const { users: [user1, user2], @@ -1605,7 +1685,7 @@ describe("Para Ape staking ape coin pool test", () => { await expect( paraApeStaking .connect(user1.signer) - .withdrawFreeSApe(user1.address, parseEther("200000")) + .withdrawFreeSApe(ape.address, parseEther("200000")) ).to.be.revertedWith(ProtocolErrors.SAPE_FREE_BALANCE_NOT_ENOUGH); await mintAndValidate(weth, "200", liquidator); @@ -1654,7 +1734,7 @@ describe("Para Ape staking ape coin pool test", () => { await expect( paraApeStaking .connect(user1.signer) - .withdrawFreeSApe(user1.address, parseEther("200000")) + .withdrawFreeSApe(cApe.address, parseEther("200000")) ).to.be.revertedWith( ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD ); @@ -1675,7 +1755,7 @@ describe("Para Ape staking ape coin pool test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .withdrawFreeSApe(user1.address, parseEther("200000")) + .withdrawFreeSApe(cApe.address, parseEther("200000")) ); expect(await cApe.balanceOf(user1.address)).to.be.closeTo( From 5839f00fb1028727dc2f098123d1707d4927ba08 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 1 Aug 2023 09:32:59 +0800 Subject: [PATCH 75/99] chore: update constant hash value --- contracts/apestaking/logic/ApeStakingP2PLogic.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index 8403e33fa..28d922e9b 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -36,7 +36,7 @@ library ApeStakingP2PLogic { //keccak256("MatchedOrder(uint8 stakingType,address apeToken,uint32 apeTokenId,uint32 apeShare,uint32 bakcTokenId,uint32 bakcShare,address apeCoinOfferer,uint32 apeCoinShare)"); bytes32 internal constant MATCHED_ORDER_HASH = - 0x7db3dae7d89c86e6881a66a131841305c008b207e41ff86a804b4bb056652808; + 0x48f3bc7b1131aafcb847892fa3593862086dbde63aca2af4deccea8f6e8a380e; function cancelListing( IApeStakingP2P.ListingOrder calldata listingOrder, From 86a15f1fede625643f5a58f2280aaca156fe71a5 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 1 Aug 2023 14:22:49 +0800 Subject: [PATCH 76/99] chore: ApeCoin Order sApe liquidation --- contracts/apestaking/ParaApeStaking.sol | 1 + .../apestaking/logic/ApeCoinPoolLogic.sol | 9 +- .../logic/ApeStakingCommonLogic.sol | 6 + .../apestaking/logic/ApeStakingP2PLogic.sol | 116 +++++++++++----- test/p2p_pair_staking.spec.ts | 125 +++++++++++++++++- test/para_pool_ape_staking.spec.ts | 2 +- 6 files changed, 220 insertions(+), 39 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 95eaf048f..5329dda91 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -680,6 +680,7 @@ contract ParaApeStaking is { ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; + vars.sApeReserveId = sApeReserveId; ApeStakingP2PLogic.breakUpMatchedOrder( listingOrderStatus, matchedOrders, diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index fc2807894..b81d05370 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -29,12 +29,6 @@ library ApeCoinPoolLogic { using SafeERC20 for IERC20; using WadRayMath for uint256; - /** - * @dev Minimum health factor to consider a user position healthy - * A value of 1e18 results in 1 - */ - uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18; - event ApeCoinPoolDeposited(bool isBAYC, uint256 tokenId); event ApeCoinPoolCompounded(bool isBAYC, uint256 tokenId); event ApeCoinPoolClaimed(bool isBAYC, uint256 tokenId); @@ -965,7 +959,8 @@ library ApeCoinPoolLogic { ); //need to check user health factor require( - healthFactor >= HEALTH_FACTOR_LIQUIDATION_THRESHOLD, + healthFactor >= + ApeStakingCommonLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD ); } diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index f346c800e..b0f48b0d4 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -20,6 +20,12 @@ library ApeStakingCommonLogic { using SafeCast for uint256; using WadRayMath for uint256; + /** + * @dev Minimum health factor to consider a user position healthy + * A value of 1e18 results in 1 + */ + uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18; + uint256 public constant BAYC_BAKC_PAIR_POOL_ID = 1; uint256 public constant MAYC_BAKC_PAIR_POOL_ID = 2; uint256 public constant BAYC_SINGLE_POOL_ID = 3; diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index 28d922e9b..1bb005fb0 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -13,6 +13,7 @@ import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import "./ApeStakingCommonLogic.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import "../../protocol/libraries/helpers/Errors.sol"; +import {UserConfiguration} from "../../protocol/libraries/configuration/UserConfiguration.sol"; import {DataTypes} from "../../protocol/libraries/types/DataTypes.sol"; /** @@ -21,6 +22,7 @@ import {DataTypes} from "../../protocol/libraries/types/DataTypes.sol"; * @notice Implements the base logic for ape staking vault */ library ApeStakingP2PLogic { + using UserConfiguration for DataTypes.UserConfigurationMap; using PercentageMath for uint256; using SafeCast for uint256; using SafeERC20 for IERC20; @@ -255,25 +257,18 @@ library ApeStakingP2PLogic { (order.stakingType == IApeStakingP2P.StakingType.BAKCPairStaking && msg.sender == - IERC721(vars.nBakc).ownerOf(order.bakcTokenId)), + IERC721(vars.nBakc).ownerOf(order.bakcTokenId)) || + _ifCanLiquidateApeCoinOffererSApe( + vars.pool, + vars.sApeReserveId, + order.apeCoinOfferer + ), Errors.NO_BREAK_UP_PERMISSION ); - //2 claim pending reward and compound - bytes32[] memory orderHashes = new bytes32[](1); - orderHashes[0] = orderHash; - claimForMatchedOrdersAndCompound( - matchedOrders, - cApeShareBalance, - vars, - orderHashes - ); - - //3 delete matched order - delete matchedOrders[orderHash]; - - //4 exit from ApeCoinStaking + //2 exit from ApeCoinStaking uint256 apeCoinCap = getApeCoinStakingCap(order.stakingType, vars); + uint256 balanceBefore = IERC20(vars.apeCoin).balanceOf(address(this)); if ( order.stakingType == IApeStakingP2P.StakingType.BAYCStaking || order.stakingType == IApeStakingP2P.StakingType.MAYCStaking @@ -306,7 +301,26 @@ library ApeStakingP2PLogic { vars.apeCoinStaking.withdrawBAKC(_otherPairs, _nfts); } } - //5 transfer token + uint256 balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); + uint256 withdrawAmount = balanceAfter - balanceBefore; + + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WAD); + if (withdrawAmount > apeCoinCap) { + uint256 _compoundFeeShare = _distributeReward( + cApeShareBalance, + vars, + order, + vars.cApeExchangeRate, + withdrawAmount - apeCoinCap + ); + ApeStakingCommonLogic.depositCApeShareForUser( + cApeShareBalance, + address(this), + _compoundFeeShare + ); + } + + //3 transfer token uint256 matchedCount = apeMatchedCount[order.apeToken][ order.apeTokenId ]; @@ -319,12 +333,13 @@ library ApeStakingP2PLogic { } apeMatchedCount[order.apeToken][order.apeTokenId] = matchedCount - 1; - _handleApeCoin( + _updateUserSApeBalance( sApeBalance, - vars.cApe, order.apeCoinOfferer, - apeCoinCap + apeCoinCap, + vars.cApeExchangeRate ); + IAutoCompoundApe(vars.cApe).deposit(address(this), withdrawAmount); if (order.stakingType == IApeStakingP2P.StakingType.BAKCPairStaking) { IERC721(vars.bakc).safeTransferFrom( address(this), @@ -333,7 +348,10 @@ library ApeStakingP2PLogic { ); } - //6 reset ape coin listing order status + //4 delete matched order + delete matchedOrders[orderHash]; + + //5 reset ape coin listing order status if ( listingOrderStatus[order.apeCoinListingOrderHash] != IApeStakingP2P.ListingOrderStatus.Cancelled @@ -432,6 +450,26 @@ library ApeStakingP2PLogic { return (0, 0); } + uint256 _compoundFeeShare = _distributeReward( + cApeShareBalance, + vars, + order, + cApeExchangeRate, + rewardAmount + ); + + emit OrderClaimedAndCompounded(orderHash, rewardAmount); + + return (rewardAmount, _compoundFeeShare); + } + + function _distributeReward( + mapping(address => uint256) storage cApeShareBalance, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + IApeStakingP2P.MatchedOrder memory order, + uint256 cApeExchangeRate, + uint256 rewardAmount + ) internal returns (uint256) { uint256 rewardShare = rewardAmount.wadDiv(cApeExchangeRate); //compound fee uint256 _compoundFeeShare = rewardShare.percentMul(vars.compoundFee); @@ -457,9 +495,7 @@ library ApeStakingP2PLogic { ); } - emit OrderClaimedAndCompounded(orderHash, rewardAmount); - - return (rewardAmount, _compoundFeeShare); + return _compoundFeeShare; } function _validateOrderBasicInfo( @@ -594,19 +630,17 @@ library ApeStakingP2PLogic { IAutoCompoundApe(cApe).withdraw(amount); } - function _handleApeCoin( + function _updateUserSApeBalance( mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, - address cApe, address user, - uint256 amount + uint256 apeCoinAmount, + uint256 cApeExchangeRate ) internal { - uint256 freeSApeBalanceAdded = ICApe(cApe).getShareByPooledApe(amount); + uint256 freeSApeBalanceAdded = apeCoinAmount.wadDiv(cApeExchangeRate); IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; sApeBalanceCache.freeShareBalance += freeSApeBalanceAdded.toUint128(); - sApeBalanceCache.stakedBalance -= amount.toUint128(); + sApeBalanceCache.stakedBalance -= apeCoinAmount.toUint128(); sApeBalance[user] = sApeBalanceCache; - - IAutoCompoundApe(cApe).deposit(address(this), amount); } function _getApeNTokenAddress( @@ -622,6 +656,28 @@ library ApeStakingP2PLogic { } } + function _ifCanLiquidateApeCoinOffererSApe( + address pool, + uint16 sApeReserveId, + address user + ) internal view returns (bool) { + DataTypes.UserConfigurationMap memory userConfig = IPool(pool) + .getUserConfiguration(user); + bool usageAsCollateralEnabled = userConfig.isUsingAsCollateral( + sApeReserveId + ); + + if (usageAsCollateralEnabled && userConfig.isBorrowingAny()) { + (, , , , , uint256 healthFactor, ) = IPool(pool).getUserAccountData( + user + ); + return + healthFactor < + ApeStakingCommonLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD; + } + return false; + } + function getMatchedOrderHash(IApeStakingP2P.MatchedOrder memory order) public pure diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index eaceb1bb2..4ed09002d 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -3,7 +3,12 @@ import {expect} from "chai"; import {AutoCompoundApe, ParaApeStaking} from "../types"; import {TestEnv} from "./helpers/make-suite"; import {testEnvFixture} from "./helpers/setup-env"; -import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; +import { + changePriceAndValidate, + changeSApePriceAndValidate, + mintAndValidate, + supplyAndValidate, +} from "./helpers/validated-steps"; import { getAutoCompoundApe, getParaApeStaking, @@ -1239,4 +1244,122 @@ describe("P2P Pair Staking Test", () => { await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) ); }); + + it("ApeCoin order staked sApe can be liquidate", async () => { + const { + users: [user1, user2, liquidator], + ape, + weth, + bayc, + pool, + } = await loadFixture(fixture); + const sApeAddress = ONE_ADDRESS; + + await supplyAndValidate(bayc, "1", user1, true); + + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + + await waitForTx( + await pool + .connect(user2.signer) + .setUserUseERC20AsCollateral(sApeAddress, true) + ); + + await changePriceAndValidate(ape, "0.001"); + await changeSApePriceAndValidate(sApeAddress, "0.001"); + await supplyAndValidate(weth, "100", liquidator, true); + + //collateral value: 200000 * 0.001 = 200 eth + //borrow value: 30 eth + await waitForTx( + await pool + .connect(user2.signer) + .borrow(weth.address, parseEther("30"), 0, user2.address) + ); + + await expect( + paraApeStaking.connect(liquidator.signer).breakUpMatchedOrder(orderHash) + ).to.be.revertedWith(ProtocolErrors.NO_BREAK_UP_PERMISSION); + + await changePriceAndValidate(ape, "0.0001"); + await changeSApePriceAndValidate(sApeAddress, "0.0001"); + + await waitForTx( + await paraApeStaking + .connect(liquidator.signer) + .breakUpMatchedOrder(orderHash) + ); + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + await waitForTx( + await pool + .connect(liquidator.signer) + .liquidateERC20( + sApeAddress, + weth.address, + user2.address, + MAX_UINT_AMOUNT, + true, + { + value: parseEther("100"), + gasLimit: 5000000, + } + ) + ); + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + expect( + await paraApeStaking.freeSApeBalance(liquidator.address) + ).to.be.closeTo(apeAmount, parseEther("1")); + }); }); diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 75fcd38fc..2d5de7d81 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -916,7 +916,7 @@ describe("Para Ape staking ape coin pool test", () => { ); }); - it("sApe test0", async () => { + it("sApe test0: Ape coin pool sApe", async () => { const { users: [user1, user2, liquidator], ape, From a7dc3eee410633e27dcb93f462913b690ebb2240 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 1 Aug 2023 16:59:26 +0800 Subject: [PATCH 77/99] chore: small optimization --- .../apestaking/logic/ApeCoinPoolLogic.sol | 35 ++----------- .../logic/ApeStakingCommonLogic.sol | 27 ++++++++++ .../apestaking/logic/ApeStakingP2PLogic.sol | 50 ++++++++----------- 3 files changed, 51 insertions(+), 61 deletions(-) diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index b81d05370..175a925ed 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -137,7 +137,7 @@ library ApeCoinPoolLogic { Errors.NOT_THE_OWNER ); - _handleApeTransferIn( + ApeStakingCommonLogic.handleApeTransferIn( apeMatchedCount, vars.apeToken, vars.nApe, @@ -324,7 +324,7 @@ library ApeCoinPoolLogic { for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = withdrawInfo.tokenIds[index]; - _handleApeTransferOut( + ApeStakingCommonLogic.handleApeTransferOut( apeMatchedCount, vars.apeToken, vars.nApe, @@ -376,7 +376,7 @@ library ApeCoinPoolLogic { ); } - _handleApeTransferIn( + ApeStakingCommonLogic.handleApeTransferIn( apeMatchedCount, vars.apeToken, vars.nApe, @@ -609,7 +609,7 @@ library ApeCoinPoolLogic { uint32 apeTokenId = withdrawInfo.apeTokenIds[index]; uint32 bakcTokenId = withdrawInfo.bakcTokenIds[index]; - _handleApeTransferOut( + ApeStakingCommonLogic.handleApeTransferOut( apeMatchedCount, vars.apeToken, vars.nApe, @@ -769,33 +769,6 @@ library ApeCoinPoolLogic { return (claimFor, pendingReward, accumulatedRewardsPerNft); } - function _handleApeTransferIn( - mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, - address ape, - address nApe, - uint32 tokenId - ) internal { - uint256 currentMatchCount = apeMatchedCount[ape][tokenId]; - if (currentMatchCount == 0) { - IERC721(ape).safeTransferFrom(nApe, address(this), tokenId); - } - apeMatchedCount[ape][tokenId] = currentMatchCount + 1; - } - - function _handleApeTransferOut( - mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, - address ape, - address nApe, - uint32 tokenId - ) internal { - uint256 matchedCount = apeMatchedCount[ape][tokenId]; - matchedCount -= 1; - if (matchedCount == 0) { - IERC721(ape).safeTransferFrom(address(this), nApe, tokenId); - } - apeMatchedCount[ape][tokenId] = matchedCount; - } - function _prepareApeCoin( mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index b0f48b0d4..d40f065b0 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -129,4 +129,31 @@ library ApeStakingCommonLogic { ); return (currentDebt - perPositionCap * currentStakingPosition); } + + function handleApeTransferIn( + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + address ape, + address nApe, + uint32 tokenId + ) internal { + uint256 currentMatchCount = apeMatchedCount[ape][tokenId]; + if (currentMatchCount == 0) { + IERC721(ape).safeTransferFrom(nApe, address(this), tokenId); + } + apeMatchedCount[ape][tokenId] = currentMatchCount + 1; + } + + function handleApeTransferOut( + mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, + address ape, + address nApe, + uint32 tokenId + ) internal { + uint256 matchedCount = apeMatchedCount[ape][tokenId]; + matchedCount -= 1; + if (matchedCount == 0) { + IERC721(ape).safeTransferFrom(address(this), nApe, tokenId); + } + apeMatchedCount[ape][tokenId] = matchedCount; + } } diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index 1bb005fb0..8a1313d6d 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -94,7 +94,13 @@ library ApeStakingP2PLogic { ); //3 transfer token - _handleApeTransfer(apeMatchedCount, apeOrder, vars); + address nTokenAddress = _getApeNTokenAddress(vars, apeOrder.token); + ApeStakingCommonLogic.handleApeTransferIn( + apeMatchedCount, + apeOrder.token, + nTokenAddress, + apeOrder.tokenId + ); uint256 apeCoinCap = getApeCoinStakingCap( apeCoinOrder.stakingType, vars @@ -178,7 +184,13 @@ library ApeStakingP2PLogic { ); //3 transfer token - _handleApeTransfer(apeMatchedCount, apeOrder, vars); + address nTokenAddress = _getApeNTokenAddress(vars, apeOrder.token); + ApeStakingCommonLogic.handleApeTransferIn( + apeMatchedCount, + apeOrder.token, + nTokenAddress, + apeOrder.tokenId + ); IERC721(vars.bakc).safeTransferFrom( vars.nBakc, address(this), @@ -321,18 +333,12 @@ library ApeStakingP2PLogic { } //3 transfer token - uint256 matchedCount = apeMatchedCount[order.apeToken][ + ApeStakingCommonLogic.handleApeTransferOut( + apeMatchedCount, + order.apeToken, + apeNToken, order.apeTokenId - ]; - if (matchedCount == 1) { - IERC721(order.apeToken).safeTransferFrom( - address(this), - apeNToken, - order.apeTokenId - ); - } - apeMatchedCount[order.apeToken][order.apeTokenId] = matchedCount - 1; - + ); _updateUserSApeBalance( sApeBalance, order.apeCoinOfferer, @@ -340,6 +346,7 @@ library ApeStakingP2PLogic { vars.cApeExchangeRate ); IAutoCompoundApe(vars.cApe).deposit(address(this), withdrawAmount); + if (order.stakingType == IApeStakingP2P.StakingType.BAKCPairStaking) { IERC721(vars.bakc).safeTransferFrom( address(this), @@ -592,23 +599,6 @@ library ApeStakingP2PLogic { ); } - function _handleApeTransfer( - mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, - IApeStakingP2P.ListingOrder calldata order, - IParaApeStaking.ApeStakingVaultCacheVars memory vars - ) internal { - uint256 currentMatchCount = apeMatchedCount[order.token][order.tokenId]; - if (currentMatchCount == 0) { - address nTokenAddress = _getApeNTokenAddress(vars, order.token); - IERC721(order.token).safeTransferFrom( - nTokenAddress, - address(this), - order.tokenId - ); - } - apeMatchedCount[order.token][order.tokenId] = currentMatchCount + 1; - } - function _prepareApeCoin( mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, address cApe, From 5843b044fde3d8387d1dae76f72572ba32f4086a Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 2 Aug 2023 15:02:53 +0800 Subject: [PATCH 78/99] chore: add query interface --- contracts/apestaking/ParaApeStaking.sol | 22 +++++++++++- .../apestaking/logic/ApeCoinPoolLogic.sol | 36 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 5329dda91..1b35381d6 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -51,7 +51,7 @@ contract ParaApeStaking is address private immutable psApe; //record all pool states - mapping(uint256 => PoolState) public poolStates; + mapping(uint256 => PoolState) private poolStates; //record user sApe balance mapping(address => SApeBalance) private sApeBalance; @@ -221,6 +221,26 @@ contract ParaApeStaking is emit RescueERC20(token, to, amount); } + /* + *common Logic + */ + function isNFTInPoolId( + address nft, + uint256 tokenId, + uint256 poolId + ) external returns (bool) { + return + ApeCoinPoolLogic.isNFTInPoolId( + poolStates, + bayc, + mayc, + bakc, + nft, + tokenId, + poolId + ); + } + /* *sApe Logic */ diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 175a925ed..7d4ff1416 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -50,6 +50,42 @@ library ApeCoinPoolLogic { uint256 bakcTokenId ); + function isNFTInPoolId( + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, + address bayc, + address mayc, + address bakc, + address nft, + uint256 tokenId, + uint256 poolId + ) external returns (bool) { + if (nft == bayc) { + if ( + poolId == ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID || + poolId == ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID || + poolId == ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID || + poolId == ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + ) { + return poolStates[poolId].tokenStatus[tokenId].isInPool; + } + } else if (nft == mayc) { + if ( + poolId == ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID || + poolId == ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID || + poolId == ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID || + poolId == ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID + ) { + return poolStates[poolId].tokenStatus[tokenId].isInPool; + } + } else if (nft == bakc) { + if (poolId == ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID) { + return poolStates[poolId].tokenStatus[tokenId].isInPool; + } + } + + return false; + } + function depositFreeSApe( mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, address apeCoin, From 4093145014b8653f84de45b12d4d0c1c60892d60 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 2 Aug 2023 15:06:05 +0800 Subject: [PATCH 79/99] chore: remove burn callback --- contracts/apestaking/ParaApeStaking.sol | 2 +- .../apestaking/logic/ApeCoinPoolLogic.sol | 2 +- .../tokenization/NTokenApeStaking.sol | 32 ------------------- .../protocol/tokenization/NTokenBAKC.sol | 31 ------------------ 4 files changed, 2 insertions(+), 65 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 1b35381d6..e5359cd9f 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -228,7 +228,7 @@ contract ParaApeStaking is address nft, uint256 tokenId, uint256 poolId - ) external returns (bool) { + ) external view returns (bool) { return ApeCoinPoolLogic.isNFTInPoolId( poolStates, diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 7d4ff1416..e0156631f 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -58,7 +58,7 @@ library ApeCoinPoolLogic { address nft, uint256 tokenId, uint256 poolId - ) external returns (bool) { + ) external view returns (bool) { if (nft == bayc) { if ( poolId == ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID || diff --git a/contracts/protocol/tokenization/NTokenApeStaking.sol b/contracts/protocol/tokenization/NTokenApeStaking.sol index 226e9a8af..514aeabb9 100644 --- a/contracts/protocol/tokenization/NTokenApeStaking.sol +++ b/contracts/protocol/tokenization/NTokenApeStaking.sol @@ -76,36 +76,4 @@ abstract contract NTokenApeStaking is NToken { } super._transfer(from, to, tokenId, validate); } - - /** - * @notice Overrides the burn from NToken to withdraw all staked and pending rewards before burning the NToken on liquidation/withdraw - */ - function burn( - address from, - address receiverOfUnderlying, - uint256[] calldata tokenIds, - DataTypes.TimeLockParams calldata timeLockParams - ) external virtual override onlyPool nonReentrant returns (uint64, uint64) { - address underlying = _ERC721Data.underlyingAsset; - uint256 arrayLength = tokenIds.length; - uint32[] memory unstakeTokenIds = new uint32[](arrayLength); - uint256 unstakeTokenIdCount = 0; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 tokenId = tokenIds[index].toUint32(); - address underlyingOwner = IERC721(underlying).ownerOf(tokenId); - if (underlyingOwner == address(paraApeStaking)) { - unstakeTokenIds[unstakeTokenIdCount] = tokenId; - unstakeTokenIdCount++; - } - } - - if (unstakeTokenIdCount > 0) { - assembly { - mstore(unstakeTokenIds, unstakeTokenIdCount) - } - paraApeStaking.nApeOwnerChangeCallback(isBayc(), unstakeTokenIds); - } - - return _burn(from, receiverOfUnderlying, tokenIds, timeLockParams); - } } diff --git a/contracts/protocol/tokenization/NTokenBAKC.sol b/contracts/protocol/tokenization/NTokenBAKC.sol index df87a0a36..f49f531c5 100644 --- a/contracts/protocol/tokenization/NTokenBAKC.sol +++ b/contracts/protocol/tokenization/NTokenBAKC.sol @@ -73,37 +73,6 @@ contract NTokenBAKC is NToken { super._transfer(from, to, tokenId, validate); } - /** - * @notice Overrides the burn from NToken to withdraw all staked and pending rewards before burning the NToken on liquidation/withdraw - */ - function burn( - address from, - address receiverOfUnderlying, - uint256[] calldata tokenIds, - DataTypes.TimeLockParams calldata timeLockParams - ) external virtual override onlyPool nonReentrant returns (uint64, uint64) { - address underlying = _ERC721Data.underlyingAsset; - uint256 arrayLength = tokenIds.length; - uint32[] memory claimTokenIds = new uint32[](arrayLength); - uint256 tokenIdCount = 0; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 tokenId = tokenIds[index].toUint32(); - address underlyingOwner = IERC721(underlying).ownerOf(tokenId); - if (underlyingOwner == address(paraApeStaking)) { - claimTokenIds[tokenIdCount] = tokenId; - tokenIdCount++; - } - } - - if (tokenIdCount > 0) { - assembly { - mstore(claimTokenIds, tokenIdCount) - } - paraApeStaking.nBakcOwnerChangeCallback(claimTokenIds); - } - return _burn(from, receiverOfUnderlying, tokenIds, timeLockParams); - } - function getXTokenType() external pure override returns (XTokenType) { return XTokenType.NTokenBAKC; } From 9810d8b89b3831d431f6c66d51a784a5e8b7cb58 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 2 Aug 2023 17:51:09 +0800 Subject: [PATCH 80/99] chore: optimization --- contracts/apestaking/ParaApeStaking.sol | 88 ++++++------ .../apestaking/logic/ApeCoinPoolLogic.sol | 121 ++++++++--------- .../logic/ApeStakingCommonLogic.sol | 21 +++ .../logic/ApeStakingPairPoolLogic.sol | 125 +++++++----------- .../logic/ApeStakingSinglePoolLogic.sol | 105 ++++++--------- contracts/interfaces/IApeStakingVault.sol | 16 +-- test/para_ape_staking.spec.ts | 45 ++----- test/para_pool_ape_staking.spec.ts | 20 +-- 8 files changed, 228 insertions(+), 313 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index e5359cd9f..6555db4d5 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -359,17 +359,15 @@ contract ParaApeStaking is view returns (uint256) { - ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; - (, uint256 pendingReward, ) = ApeCoinPoolLogic.calculatePendingReward( - poolStates[poolId], - vars, - isBAYC, - tokenIds - ); - return pendingReward; + return + ApeCoinPoolLogic.calculatePendingReward( + poolStates[poolId], + cApe, + tokenIds + ); } function claimApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) @@ -456,17 +454,15 @@ contract ParaApeStaking is bool isBAYC, uint32[] calldata apeTokenIds ) external view returns (uint256) { - ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; - (, uint256 pendingReward, ) = ApeCoinPoolLogic.calculatePendingReward( - poolStates[poolId], - vars, - isBAYC, - apeTokenIds - ); - return pendingReward; + return + ApeCoinPoolLogic.calculatePendingReward( + poolStates[poolId], + cApe, + apeTokenIds + ); } function claimApeCoinPairPool(bool isBAYC, uint32[] calldata apeTokenIds) @@ -532,7 +528,6 @@ contract ParaApeStaking is //handle nft pool in the scope to avoid stack too deep { uint32[] memory pairPoolTokenIds = new uint32[](tokenIds.length); - uint32[] memory pairPoolBakcIds = new uint32[](tokenIds.length); uint32[] memory singlePoolTokenIds = new uint32[](tokenIds.length); uint256 pairPoolCount = 0; uint256 singlePoolCount = 0; @@ -549,7 +544,6 @@ contract ParaApeStaking is ]; if (tokenStatus.isInPool) { pairPoolTokenIds[pairPoolCount] = tokenId; - pairPoolBakcIds[pairPoolCount] = tokenStatus.bakcTokenId; pairPoolCount++; continue; } @@ -572,7 +566,6 @@ contract ParaApeStaking is if (pairPoolCount > 0) { assembly { mstore(pairPoolTokenIds, pairPoolCount) - mstore(pairPoolBakcIds, pairPoolCount) } uint256 poolId = isBAYC ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID @@ -581,8 +574,7 @@ contract ParaApeStaking is poolStates[poolId], vars, isBAYC, - pairPoolTokenIds, - pairPoolBakcIds + pairPoolTokenIds ); } @@ -852,33 +844,30 @@ contract ParaApeStaking is } /// @inheritdoc IApeStakingVault - function pairNFTPendingReward( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external view override returns (uint256) { - ApeStakingVaultCacheVars memory vars = _createCacheVars(); + function pairNFTPendingReward(bool isBAYC, uint32[] calldata apeTokenIds) + external + view + override + returns (uint256) + { uint256 poolId = isBAYC ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; - (, uint256 pendingReward, ) = ApeStakingPairPoolLogic - .calculatePendingReward( + return + ApeStakingPairPoolLogic.calculatePendingReward( poolStates[poolId], - vars, - isBAYC, - apeTokenIds, - bakcTokenIds + cApe, + apeTokenIds ); - - return pendingReward; } /// @inheritdoc IApeStakingVault - function claimPairNFT( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external override whenNotPaused nonReentrant { + function claimPairNFT(bool isBAYC, uint32[] calldata apeTokenIds) + external + override + whenNotPaused + nonReentrant + { ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID @@ -887,8 +876,7 @@ contract ParaApeStaking is poolStates[poolId], vars, isBAYC, - apeTokenIds, - bakcTokenIds + apeTokenIds ); } @@ -1016,11 +1004,17 @@ contract ParaApeStaking is nft == bayc || nft == mayc || nft == bakc, Errors.NFT_NOT_ALLOWED ); - ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 pendingReward = ApeStakingSinglePoolLogic - .calculatePendingReward(poolStates, vars, nft, tokenIds); - - return pendingReward; + uint256 poolId = (nft == bayc) + ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + : (nft == mayc) + ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; + return + ApeStakingSinglePoolLogic.calculatePendingReward( + poolStates[poolId], + cApe, + tokenIds + ); } /// @inheritdoc IApeStakingVault diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index e0156631f..210831079 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -31,7 +31,7 @@ library ApeCoinPoolLogic { event ApeCoinPoolDeposited(bool isBAYC, uint256 tokenId); event ApeCoinPoolCompounded(bool isBAYC, uint256 tokenId); - event ApeCoinPoolClaimed(bool isBAYC, uint256 tokenId); + event ApeCoinPoolClaimed(bool isBAYC, uint256 tokenId, uint256 rewardShare); event ApeCoinPoolWithdrew(bool isBAYC, uint256 tokenId); event ApeCoinPairPoolDeposited( bool isBAYC, @@ -43,7 +43,11 @@ library ApeCoinPoolLogic { uint256 apeTokenId, uint256 bakcTokenId ); - event ApeCoinPairPoolClaimed(bool isBAYC, uint256 apeTokenId); + event ApeCoinPairPoolClaimed( + bool isBAYC, + uint256 apeTokenId, + uint256 rewardShare + ); event ApeCoinPairPoolWithdrew( bool isBAYC, uint256 apeTokenId, @@ -761,48 +765,15 @@ library ApeCoinPoolLogic { function calculatePendingReward( IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, + address cApe, uint32[] memory tokenIds - ) - public - view - returns ( - address claimFor, - uint256 pendingReward, - uint128 accumulatedRewardsPerNft - ) - { - uint256 rewardShares; - uint256 arrayLength = tokenIds.length; - accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - address nApe = isBAYC ? vars.nBayc : vars.nMayc; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 tokenId = tokenIds[index]; - - //just need to check ape ntoken owner - { - address nApeOwner = IERC721(nApe).ownerOf(tokenId); - if (claimFor == address(0)) { - claimFor = nApeOwner; - } else { - require(nApeOwner == claimFor, Errors.NOT_THE_SAME_OWNER); - } - } - - //check is in pool - require( - poolState.tokenStatus[tokenId].isInPool, - Errors.NFT_NOT_IN_POOL + ) external view returns (uint256) { + return + ApeStakingCommonLogic.calculatePendingReward( + poolState, + cApe, + tokenIds ); - - //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - rewardShares += (accumulatedRewardsPerNft - - poolState.tokenStatus[tokenId].rewardsDebt); - } - pendingReward = ICApe(vars.cApe).getPooledApeByShares(rewardShares); - - return (claimFor, pendingReward, accumulatedRewardsPerNft); } function _prepareApeCoin( @@ -898,34 +869,56 @@ library ApeCoinPoolLogic { bool needUpdateStatus, uint32[] memory apeTokenIds ) internal returns (address) { - ( - address owner, - uint256 pendingReward, - uint128 accumulatedRewardsPerNft - ) = calculatePendingReward(poolState, vars, isBAYC, apeTokenIds); - - if (pendingReward > 0) { - uint256 arrayLength = apeTokenIds.length; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 apeTokenId = apeTokenIds[index]; - - if (needUpdateStatus) { - poolState - .tokenStatus[apeTokenId] - .rewardsDebt = accumulatedRewardsPerNft; - } + address claimFor; + uint256 totalRewardShares; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + address nApe = isBAYC ? vars.nBayc : vars.nMayc; + for (uint256 index = 0; index < apeTokenIds.length; index++) { + uint32 apeTokenId = apeTokenIds[index]; - //emit event - if (isSinglePool) { - emit ApeCoinPoolClaimed(isBAYC, apeTokenId); + //just need to check ape ntoken owner + { + address nApeOwner = IERC721(nApe).ownerOf(apeTokenId); + if (claimFor == address(0)) { + claimFor = nApeOwner; } else { - emit ApeCoinPairPoolClaimed(isBAYC, apeTokenId); + require(nApeOwner == claimFor, Errors.NOT_THE_SAME_OWNER); } } - IERC20(vars.cApe).safeTransfer(owner, pendingReward); + IParaApeStaking.TokenStatus memory tokenStatus = poolState + .tokenStatus[apeTokenId]; + + //check is in pool + require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); + + //update reward, to save gas we don't claim pending reward in ApeCoinStaking. + uint256 rewardShare = accumulatedRewardsPerNft - + tokenStatus.rewardsDebt; + totalRewardShares += rewardShare; + + if (needUpdateStatus) { + poolState + .tokenStatus[apeTokenId] + .rewardsDebt = accumulatedRewardsPerNft; + } + + //emit event + if (isSinglePool) { + emit ApeCoinPoolClaimed(isBAYC, apeTokenId, rewardShare); + } else { + emit ApeCoinPairPoolClaimed(isBAYC, apeTokenId, rewardShare); + } + } + + if (totalRewardShares > 0) { + uint256 pendingReward = ICApe(vars.cApe).getPooledApeByShares( + totalRewardShares + ); + IERC20(vars.cApe).safeTransfer(claimFor, pendingReward); } - return owner; + + return claimFor; } function _distributePoolReward( diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index d40f065b0..dca6b13a9 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -156,4 +156,25 @@ library ApeStakingCommonLogic { } apeMatchedCount[ape][tokenId] = matchedCount; } + + function calculatePendingReward( + IParaApeStaking.PoolState storage poolState, + address cApe, + uint32[] memory tokenIds + ) internal view returns (uint256) { + uint256 rewardShares; + uint256 arrayLength = tokenIds.length; + uint256 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + IParaApeStaking.TokenStatus memory tokenStatus = poolState + .tokenStatus[tokenId]; + require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); + + rewardShares += (accumulatedRewardsPerNft - + tokenStatus.rewardsDebt); + } + return ICApe(cApe).getPooledApeByShares(rewardShares); + } } diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index c9b93cc38..70085f3e2 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -31,7 +31,7 @@ library ApeStakingPairPoolLogic { ); event PairNFTStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); event PairNFTWithdrew(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); - event PairNFTClaimed(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event PairNFTClaimed(bool isBAYC, uint256 apeTokenId, uint256 rewardShare); event PairNFTCompounded( bool isBAYC, uint256 apeTokenId, @@ -216,8 +216,7 @@ library ApeStakingPairPoolLogic { vars, false, isBAYC, - apeTokenIds, - bakcTokenIds + apeTokenIds ); if (isBAYC) { @@ -242,8 +241,6 @@ library ApeStakingPairPoolLogic { uint32 apeTokenId = apeTokenIds[index]; uint32 bakcTokenId = bakcTokenIds[index]; - // we don't need check pair status again here - //check ntoken owner { if (vars.nApeOwner != msg.sender) { @@ -361,16 +358,11 @@ library ApeStakingPairPoolLogic { IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds + uint32[] calldata apeTokenIds ) external { ApeStakingCommonLogic.validateTokenIdArray(apeTokenIds); - require( - apeTokenIds.length == bakcTokenIds.length, - Errors.INVALID_PARAMETER - ); - _claimPairNFT(poolState, vars, true, isBAYC, apeTokenIds, bakcTokenIds); + _claimPairNFT(poolState, vars, true, isBAYC, apeTokenIds); } function compoundPairNFT( @@ -457,25 +449,30 @@ library ApeStakingPairPoolLogic { } function calculatePendingReward( + IParaApeStaking.PoolState storage poolState, + address cApe, + uint32[] memory tokenIds + ) external view returns (uint256) { + return + ApeStakingCommonLogic.calculatePendingReward( + poolState, + cApe, + tokenIds + ); + } + + function _claimPairNFT( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool needUpdateStatus, bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) - public - view - returns ( - address claimFor, - uint256 pendingReward, - uint128 accumulatedRewardsPerNft - ) - { - uint256 rewardShares; - uint256 arrayLength = apeTokenIds.length; - accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + uint32[] calldata apeTokenIds + ) internal returns (address) { + address claimFor; + uint256 totalRewardShares; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; address nApe = isBAYC ? vars.nBayc : vars.nMayc; - for (uint256 index = 0; index < arrayLength; index++) { + for (uint256 index = 0; index < apeTokenIds.length; index++) { uint32 apeTokenId = apeTokenIds[index]; //just need to check ape ntoken owner @@ -488,64 +485,34 @@ library ApeStakingPairPoolLogic { } } - // check pair status - { - IParaApeStaking.TokenStatus memory localTokenStatus = poolState - .tokenStatus[apeTokenId]; - require( - localTokenStatus.bakcTokenId == bakcTokenIds[index] && - localTokenStatus.isPaired, - Errors.NOT_PAIRED_APE_AND_BAKC - ); - } + IParaApeStaking.TokenStatus memory tokenStatus = poolState + .tokenStatus[apeTokenId]; + + //check is in pool + require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - rewardShares += (accumulatedRewardsPerNft - - poolState.tokenStatus[apeTokenId].rewardsDebt); - } - pendingReward = ICApe(vars.cApe).getPooledApeByShares(rewardShares); + uint256 rewardShare = accumulatedRewardsPerNft - + tokenStatus.rewardsDebt; + totalRewardShares += rewardShare; + + if (needUpdateStatus) { + poolState + .tokenStatus[apeTokenId] + .rewardsDebt = accumulatedRewardsPerNft; + } - return (claimFor, pendingReward, accumulatedRewardsPerNft); - } + //emit event + emit PairNFTClaimed(isBAYC, apeTokenId, rewardShare); + } - function _claimPairNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool needUpdateStatus, - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) internal returns (address) { - ( - address owner, - uint256 pendingReward, - uint128 accumulatedRewardsPerNft - ) = calculatePendingReward( - poolState, - vars, - isBAYC, - apeTokenIds, - bakcTokenIds + if (totalRewardShares > 0) { + uint256 pendingReward = ICApe(vars.cApe).getPooledApeByShares( + totalRewardShares ); - - if (pendingReward > 0) { - for (uint256 index = 0; index < apeTokenIds.length; index++) { - uint32 apeTokenId = apeTokenIds[index]; - uint32 bakcTokenId = bakcTokenIds[index]; - - if (needUpdateStatus) { - poolState - .tokenStatus[apeTokenId] - .rewardsDebt = accumulatedRewardsPerNft; - } - - //emit event - emit PairNFTClaimed(isBAYC, apeTokenId, bakcTokenId); - } - - IERC20(vars.cApe).safeTransfer(owner, pendingReward); + IERC20(vars.cApe).safeTransfer(claimFor, pendingReward); } - return owner; + return claimFor; } } diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 29f96f01e..37f03a81f 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -705,102 +705,73 @@ library ApeStakingSinglePoolLogic { } function calculatePendingReward( - mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft, + IParaApeStaking.PoolState storage poolState, + address cApe, uint32[] calldata tokenIds ) external view returns (uint256) { - uint256 poolId = (nft == vars.bayc) - ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID - : (nft == vars.mayc) - ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID - : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; - IParaApeStaking.PoolState storage poolState = poolStates[poolId]; - (, uint256 pendingReward, ) = _calculatePendingReward( - poolState, - vars, - nft, - tokenIds - ); - return pendingReward; + return + ApeStakingCommonLogic.calculatePendingReward( + poolState, + cApe, + tokenIds + ); } - function _calculatePendingReward( + function _claimNFT( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, + bool needUpdateStatus, address nft, uint32[] memory tokenIds - ) - internal - view - returns ( - address claimFor, - uint256 pendingReward, - uint128 accumulatedRewardsPerNft - ) - { - uint256 rewardShares; - uint256 arrayLength = tokenIds.length; - accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + ) internal returns (address) { + address claimFor; + uint256 totalRewardShares; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) ? vars.nMayc : vars.nBakc; - for (uint256 index = 0; index < arrayLength; index++) { + for (uint256 index = 0; index < tokenIds.length; index++) { uint32 tokenId = tokenIds[index]; //just need to check ape ntoken owner { - address nTokenOwner = IERC721(nToken).ownerOf(tokenId); + address nApeOwner = IERC721(nToken).ownerOf(tokenId); if (claimFor == address(0)) { - claimFor = nTokenOwner; + claimFor = nApeOwner; } else { - require(nTokenOwner == claimFor, Errors.NOT_THE_SAME_OWNER); + require(nApeOwner == claimFor, Errors.NOT_THE_SAME_OWNER); } } IParaApeStaking.TokenStatus memory tokenStatus = poolState .tokenStatus[tokenId]; + + //check is in pool require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - rewardShares += (accumulatedRewardsPerNft - - tokenStatus.rewardsDebt); - } - pendingReward = ICApe(vars.cApe).getPooledApeByShares(rewardShares); - } - - function _claimNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool needUpdateStatus, - address nft, - uint32[] memory tokenIds - ) internal returns (address) { - ( - address owner, - uint256 pendingReward, - uint128 accumulatedRewardsPerNft - ) = _calculatePendingReward(poolState, vars, nft, tokenIds); - - if (pendingReward > 0) { - uint256 arrayLength = tokenIds.length; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 tokenId = tokenIds[index]; - - if (needUpdateStatus) { - poolState - .tokenStatus[tokenId] - .rewardsDebt = accumulatedRewardsPerNft; - } - - //emit event - emit NFTClaimed(nft, tokenId); + uint256 rewardShare = accumulatedRewardsPerNft - + tokenStatus.rewardsDebt; + totalRewardShares += rewardShare; + + if (needUpdateStatus) { + poolState + .tokenStatus[tokenId] + .rewardsDebt = accumulatedRewardsPerNft; } - IERC20(vars.cApe).safeTransfer(owner, pendingReward); + //emit event + emit NFTClaimed(nft, tokenId); + } + + if (totalRewardShares > 0) { + uint256 pendingReward = ICApe(vars.cApe).getPooledApeByShares( + totalRewardShares + ); + IERC20(vars.cApe).safeTransfer(claimFor, pendingReward); } - return owner; + return claimFor; } function _calculateRepayAndCompoundBAKC( diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index df50e07f3..c2b4a2bfb 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -57,26 +57,18 @@ interface IApeStakingVault { * @notice get Ape and BAKC pair staking unclaimed cApe reward * @param isBAYC if Ape is BAYC * @param apeTokenIds Ape token ids - * @param bakcTokenIds BAKC token ids */ - function pairNFTPendingReward( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external returns (uint256); + function pairNFTPendingReward(bool isBAYC, uint32[] calldata apeTokenIds) + external + returns (uint256); /** * @notice claim Ape and BAKC pair staking unclaimed cApe reward * to save gas we don't claim pending reward in ApeCoinStaking. * @param isBAYC if Ape is BAYC * @param apeTokenIds Ape token ids - * @param bakcTokenIds BAKC token ids */ - function claimPairNFT( - bool isBAYC, - uint32[] calldata apeTokenIds, - uint32[] calldata bakcTokenIds - ) external; + function claimPairNFT(bool isBAYC, uint32[] calldata apeTokenIds) external; /** * @notice withdraw Ape and BAKC pair from pool diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 89c5ae25c..1b83d09f5 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -181,14 +181,11 @@ describe("Para Ape Staking Test", () => { const user1PendingReward = await paraApeStaking.pairNFTPendingReward( true, - [0, 1], [0, 1] ); - const user2PendingReward = await paraApeStaking.pairNFTPendingReward( - true, - [2], - [2] - ); + const user2PendingReward = await paraApeStaking.pairNFTPendingReward(true, [ + 2, + ]); expect(user1PendingReward).to.be.closeTo( parseEther("4320"), parseEther("50") @@ -199,12 +196,10 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .claimPairNFT(true, [0, 1], [0, 1]) + await paraApeStaking.connect(user1.signer).claimPairNFT(true, [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimPairNFT(true, [2], [2]) + await paraApeStaking.connect(user2.signer).claimPairNFT(true, [2]) ); let user1Balance = await cApe.balanceOf(user1.address); let user2Balance = await cApe.balanceOf(user2.address); @@ -214,12 +209,10 @@ describe("Para Ape Staking Test", () => { const newUser1PendingReward = await paraApeStaking.pairNFTPendingReward( true, - [0, 1], [0, 1] ); const newUser2PendingReward = await paraApeStaking.pairNFTPendingReward( true, - [2], [2] ); expect(newUser1PendingReward).to.be.equal(0); @@ -358,12 +351,10 @@ describe("Para Ape Staking Test", () => { const user1PendingReward = await paraApeStaking.pairNFTPendingReward( false, - [0, 1], [0, 1] ); const user2PendingReward = await paraApeStaking.pairNFTPendingReward( false, - [2], [2] ); expect(user1PendingReward).to.be.closeTo( @@ -376,12 +367,10 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .claimPairNFT(false, [0, 1], [0, 1]) + await paraApeStaking.connect(user1.signer).claimPairNFT(false, [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimPairNFT(false, [2], [2]) + await paraApeStaking.connect(user2.signer).claimPairNFT(false, [2]) ); let user1Balance = await cApe.balanceOf(user1.address); let user2Balance = await cApe.balanceOf(user2.address); @@ -391,12 +380,10 @@ describe("Para Ape Staking Test", () => { const newUser1PendingReward = await paraApeStaking.pairNFTPendingReward( false, - [0, 1], [0, 1] ); const newUser2PendingReward = await paraApeStaking.pairNFTPendingReward( false, - [2], [2] ); expect(newUser1PendingReward).to.be.equal(0); @@ -799,7 +786,7 @@ describe("Para Ape Staking Test", () => { nBAYC, } = await loadFixture(fixture); - await supplyAndValidate(bayc, "3", user1, true); + await supplyAndValidate(bayc, "4", user1, true); await supplyAndValidate(bakc, "3", user1, true); await waitForTx( @@ -829,14 +816,12 @@ describe("Para Ape Staking Test", () => { ); await expect( - paraApeStaking - .connect(user1.signer) - .claimPairNFT(true, [0, 1, 2], [0, 1, 2]) + paraApeStaking.connect(user1.signer).claimPairNFT(true, [0, 1, 2]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); await expect( - paraApeStaking.connect(user1.signer).claimPairNFT(true, [1], [0]) - ).to.be.revertedWith(ProtocolErrors.NOT_PAIRED_APE_AND_BAKC); + paraApeStaking.connect(user1.signer).claimPairNFT(true, [3]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); }); it("withdrawPairNFT revert test", async () => { @@ -868,9 +853,9 @@ describe("Para Ape Staking Test", () => { .transferFrom(user1.address, user2.address, 2) ); - await expect( - paraApeStaking.connect(user1.signer).withdrawPairNFT(true, [0, 1], [1, 0]) - ).to.be.revertedWith(ProtocolErrors.NOT_PAIRED_APE_AND_BAKC); + // await expect( + // paraApeStaking.connect(user1.signer).withdrawPairNFT(true, [0, 1], [1, 0]) + // ).to.be.revertedWith(ProtocolErrors.NOT_PAIRED_APE_AND_BAKC); await expect( paraApeStaking @@ -1361,12 +1346,10 @@ describe("Para Ape Staking Test", () => { tx0 = paraApeStaking.interface.encodeFunctionData("claimPairNFT", [ true, [0, 1], - [0, 1], ]); tx1 = paraApeStaking.interface.encodeFunctionData("claimPairNFT", [ false, [0, 1], - [2, 3], ]); tx2 = paraApeStaking.interface.encodeFunctionData("claimNFT", [ bayc.address, diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 2d5de7d81..f6fa7f9df 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -1035,7 +1035,7 @@ describe("Para Ape staking ape coin pool test", () => { user1.address, 0, parseEther("100"), - false, + true, {gasLimit: 5000000} ) ); @@ -1072,7 +1072,7 @@ describe("Para Ape staking ape coin pool test", () => { user1.address, 0, parseEther("50"), - false, + true, {gasLimit: 5000000} ) ); @@ -1717,7 +1717,7 @@ describe("Para Ape staking ape coin pool test", () => { user1.address, 0, parseEther("100"), - false, + true, {gasLimit: 5000000} ) ); @@ -1841,16 +1841,10 @@ describe("Para Ape staking ape coin pool test", () => { }) ); - const baycPairReward = await paraApeStaking.pairNFTPendingReward( - true, - [0], - [0] - ); - const maycPairReward = await paraApeStaking.pairNFTPendingReward( - false, - [0], - [1] - ); + const baycPairReward = await paraApeStaking.pairNFTPendingReward(true, [0]); + const maycPairReward = await paraApeStaking.pairNFTPendingReward(false, [ + 0, + ]); const baycSingleReward = await paraApeStaking.nftPendingReward( bayc.address, [1] From 2dbac0961b7c44946bb3ddbd7721998d7ae5deee Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 2 Aug 2023 18:28:48 +0800 Subject: [PATCH 81/99] chore: fix compound fee. --- contracts/apestaking/ParaApeStaking.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 6555db4d5..211bd719a 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -522,6 +522,7 @@ contract ParaApeStaking is nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); + vars.compoundFee = compoundFee; uint32[] memory apeCoinPoolTokenIds = new uint32[](tokenIds.length); uint256 apeCoinPoolCount = 0; From 26ee4dff6b5d9ce493ecfb47eb0983b9a1c4c91f Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 3 Aug 2023 07:41:52 +0800 Subject: [PATCH 82/99] chore: refactor query and claim pendign reward --- contracts/apestaking/ParaApeStaking.sol | 188 +++--------------- .../apestaking/logic/ApeCoinPoolLogic.sol | 186 +++++++---------- .../logic/ApeStakingCommonLogic.sol | 82 +++++++- .../logic/ApeStakingPairPoolLogic.sol | 101 ++-------- .../logic/ApeStakingSinglePoolLogic.sol | 129 ++++-------- contracts/interfaces/IApeCoinPool.sol | 15 -- contracts/interfaces/IApeStakingVault.sol | 34 ---- test/para_ape_staking.spec.ts | 145 ++++++-------- test/para_pool_ape_staking.spec.ts | 129 +++--------- 9 files changed, 314 insertions(+), 695 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 211bd719a..0e220570b 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -241,6 +241,34 @@ contract ParaApeStaking is ); } + function getPendingReward(uint256 poolId, uint32[] calldata tokenIds) + external + view + returns (uint256) + { + return + ApeCoinPoolLogic.getPendingReward( + poolStates[poolId], + cApe, + tokenIds + ); + } + + function claimPendingReward(uint256 poolId, uint32[] calldata tokenIds) + external + whenNotPaused + nonReentrant + { + ApeStakingVaultCacheVars memory vars = _createCacheVars(); + return + ApeCoinPoolLogic.claimPendingReward( + poolStates[poolId], + vars, + poolId, + tokenIds + ); + } + /* *sApe Logic */ @@ -354,39 +382,6 @@ contract ParaApeStaking is ); } - function apeCoinPoolPendingReward(bool isBAYC, uint32[] calldata tokenIds) - external - view - returns (uint256) - { - uint256 poolId = isBAYC - ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; - return - ApeCoinPoolLogic.calculatePendingReward( - poolStates[poolId], - cApe, - tokenIds - ); - } - - function claimApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) - external - whenNotPaused - nonReentrant - { - ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = isBAYC - ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; - ApeCoinPoolLogic.claimApeCoinPool( - poolStates[poolId], - vars, - isBAYC, - tokenIds - ); - } - function withdrawApeCoinPool(ApeCoinWithdrawInfo calldata withdrawInfo) external whenNotPaused @@ -450,38 +445,6 @@ contract ParaApeStaking is ); } - function apeCoinPairPoolPendingReward( - bool isBAYC, - uint32[] calldata apeTokenIds - ) external view returns (uint256) { - uint256 poolId = isBAYC - ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; - return - ApeCoinPoolLogic.calculatePendingReward( - poolStates[poolId], - cApe, - apeTokenIds - ); - } - - function claimApeCoinPairPool(bool isBAYC, uint32[] calldata apeTokenIds) - external - whenNotPaused - nonReentrant - { - ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = isBAYC - ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; - ApeCoinPoolLogic.claimApeCoinPairPool( - poolStates[poolId], - vars, - isBAYC, - apeTokenIds - ); - } - function withdrawApeCoinPairPool( ApeCoinPairWithdrawInfo calldata withdrawInfo ) external whenNotPaused nonReentrant { @@ -511,6 +474,7 @@ contract ParaApeStaking is ApeStakingSinglePoolLogic.tryClaimNFT( poolStates[poolId], vars, + poolId, bakc, tokenIds ); @@ -571,10 +535,10 @@ contract ParaApeStaking is uint256 poolId = isBAYC ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; - ApeStakingPairPoolLogic.claimPairNFT( + ApeCoinPoolLogic.claimPendingReward( poolStates[poolId], vars, - isBAYC, + poolId, pairPoolTokenIds ); } @@ -586,10 +550,10 @@ contract ParaApeStaking is uint256 poolId = isBAYC ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID : ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID; - ApeStakingSinglePoolLogic.claimNFT( + ApeCoinPoolLogic.claimPendingReward( poolStates[poolId], vars, - isBAYC ? bayc : mayc, + poolId, singlePoolTokenIds ); } @@ -844,43 +808,6 @@ contract ParaApeStaking is ); } - /// @inheritdoc IApeStakingVault - function pairNFTPendingReward(bool isBAYC, uint32[] calldata apeTokenIds) - external - view - override - returns (uint256) - { - uint256 poolId = isBAYC - ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; - return - ApeStakingPairPoolLogic.calculatePendingReward( - poolStates[poolId], - cApe, - apeTokenIds - ); - } - - /// @inheritdoc IApeStakingVault - function claimPairNFT(bool isBAYC, uint32[] calldata apeTokenIds) - external - override - whenNotPaused - nonReentrant - { - ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = isBAYC - ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID - : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID; - ApeStakingPairPoolLogic.claimPairNFT( - poolStates[poolId], - vars, - isBAYC, - apeTokenIds - ); - } - /// @inheritdoc IApeStakingVault function withdrawPairNFT( bool isBAYC, @@ -994,55 +921,6 @@ contract ParaApeStaking is ); } - /// @inheritdoc IApeStakingVault - function nftPendingReward(address nft, uint32[] calldata tokenIds) - external - view - override - returns (uint256) - { - require( - nft == bayc || nft == mayc || nft == bakc, - Errors.NFT_NOT_ALLOWED - ); - uint256 poolId = (nft == bayc) - ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID - : (nft == mayc) - ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID - : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; - return - ApeStakingSinglePoolLogic.calculatePendingReward( - poolStates[poolId], - cApe, - tokenIds - ); - } - - /// @inheritdoc IApeStakingVault - function claimNFT(address nft, uint32[] calldata tokenIds) - external - override - whenNotPaused - nonReentrant - { - require( - nft == bayc || nft == mayc || nft == bakc, - Errors.NFT_NOT_ALLOWED - ); - ApeStakingVaultCacheVars memory vars = _createCacheVars(); - uint256 poolId = (nft == vars.bayc) - ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID - : (nft == vars.mayc) - ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID - : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; - ApeStakingSinglePoolLogic.claimNFT( - poolStates[poolId], - vars, - nft, - tokenIds - ); - } - /// @inheritdoc IApeStakingVault function withdrawNFT(address nft, uint32[] calldata tokenIds) external diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 210831079..e32d84c1e 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -43,11 +43,6 @@ library ApeCoinPoolLogic { uint256 apeTokenId, uint256 bakcTokenId ); - event ApeCoinPairPoolClaimed( - bool isBAYC, - uint256 apeTokenId, - uint256 rewardShare - ); event ApeCoinPairPoolWithdrew( bool isBAYC, uint256 apeTokenId, @@ -90,6 +85,49 @@ library ApeCoinPoolLogic { return false; } + function getPendingReward( + IParaApeStaking.PoolState storage poolState, + address cApe, + uint32[] memory tokenIds + ) external view returns (uint256) { + uint256 rewardShares; + uint256 arrayLength = tokenIds.length; + uint256 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + IParaApeStaking.TokenStatus memory tokenStatus = poolState + .tokenStatus[tokenId]; + require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); + + rewardShares += (accumulatedRewardsPerNft - + tokenStatus.rewardsDebt); + } + return ICApe(cApe).getPooledApeByShares(rewardShares); + } + + function claimPendingReward( + IParaApeStaking.PoolState storage poolState, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 poolId, + uint32[] calldata tokenIds + ) external { + ApeStakingCommonLogic.validateTokenIdArray(tokenIds); + (address nft, address nToken) = ApeStakingCommonLogic.getNftFromPoolId( + vars, + poolId + ); + ApeStakingCommonLogic.claimPendingReward( + poolState, + vars, + poolId, + nft, + nToken, + true, + tokenIds + ); + } + function depositFreeSApe( mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, address apeCoin, @@ -264,17 +302,6 @@ library ApeCoinPoolLogic { ); } - function claimApeCoinPool( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata tokenIds - ) external { - ApeStakingCommonLogic.validateTokenIdArray(tokenIds); - - _claimApeCoinPool(poolState, vars, isBAYC, true, true, tokenIds); - } - function withdrawApeCoinPool( IParaApeStaking.PoolState storage poolState, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, @@ -286,15 +313,6 @@ library ApeCoinPoolLogic { uint256 arrayLength = withdrawInfo.tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - address nApeOwner = _claimApeCoinPool( - poolState, - vars, - withdrawInfo.isBAYC, - true, - false, - withdrawInfo.tokenIds - ); - if (withdrawInfo.isBAYC) { vars.apeToken = vars.bayc; vars.nApe = vars.nBayc; @@ -304,6 +322,19 @@ library ApeCoinPoolLogic { vars.nApe = vars.nMayc; vars.positionCap = vars.maycMatchedCap; } + + address nApeOwner = ApeStakingCommonLogic.claimPendingReward( + poolState, + vars, + withdrawInfo.isBAYC + ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID, + vars.apeToken, + vars.nApe, + false, + withdrawInfo.tokenIds + ); + address msgSender = msg.sender; require( msgSender == nApeOwner || msgSender == vars.nApe, @@ -528,17 +559,6 @@ library ApeCoinPoolLogic { ); } - function claimApeCoinPairPool( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata tokenIds - ) external { - ApeStakingCommonLogic.validateTokenIdArray(tokenIds); - - _claimApeCoinPool(poolState, vars, isBAYC, false, true, tokenIds); - } - function withdrawApeCoinPairPool( IParaApeStaking.PoolState storage poolState, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, @@ -553,15 +573,6 @@ library ApeCoinPoolLogic { Errors.INVALID_PARAMETER ); - address nApeOwner = _claimApeCoinPool( - poolState, - vars, - withdrawInfo.isBAYC, - false, - false, - withdrawInfo.apeTokenIds - ); - if (withdrawInfo.isBAYC) { vars.apeToken = vars.bayc; vars.nApe = vars.nBayc; @@ -570,6 +581,18 @@ library ApeCoinPoolLogic { vars.nApe = vars.nMayc; } + address nApeOwner = ApeStakingCommonLogic.claimPendingReward( + poolState, + vars, + withdrawInfo.isBAYC + ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID, + vars.apeToken, + vars.nApe, + false, + withdrawInfo.apeTokenIds + ); + ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( arrayLength @@ -763,19 +786,6 @@ library ApeCoinPoolLogic { } } - function calculatePendingReward( - IParaApeStaking.PoolState storage poolState, - address cApe, - uint32[] memory tokenIds - ) external view returns (uint256) { - return - ApeStakingCommonLogic.calculatePendingReward( - poolState, - cApe, - tokenIds - ); - } - function _prepareApeCoin( mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, @@ -861,66 +871,6 @@ library ApeCoinPoolLogic { } } - function _claimApeCoinPool( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - bool isSinglePool, - bool needUpdateStatus, - uint32[] memory apeTokenIds - ) internal returns (address) { - address claimFor; - uint256 totalRewardShares; - uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - address nApe = isBAYC ? vars.nBayc : vars.nMayc; - for (uint256 index = 0; index < apeTokenIds.length; index++) { - uint32 apeTokenId = apeTokenIds[index]; - - //just need to check ape ntoken owner - { - address nApeOwner = IERC721(nApe).ownerOf(apeTokenId); - if (claimFor == address(0)) { - claimFor = nApeOwner; - } else { - require(nApeOwner == claimFor, Errors.NOT_THE_SAME_OWNER); - } - } - - IParaApeStaking.TokenStatus memory tokenStatus = poolState - .tokenStatus[apeTokenId]; - - //check is in pool - require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); - - //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - uint256 rewardShare = accumulatedRewardsPerNft - - tokenStatus.rewardsDebt; - totalRewardShares += rewardShare; - - if (needUpdateStatus) { - poolState - .tokenStatus[apeTokenId] - .rewardsDebt = accumulatedRewardsPerNft; - } - - //emit event - if (isSinglePool) { - emit ApeCoinPoolClaimed(isBAYC, apeTokenId, rewardShare); - } else { - emit ApeCoinPairPoolClaimed(isBAYC, apeTokenId, rewardShare); - } - } - - if (totalRewardShares > 0) { - uint256 pendingReward = ICApe(vars.cApe).getPooledApeByShares( - totalRewardShares - ); - IERC20(vars.cApe).safeTransfer(claimFor, pendingReward); - } - - return claimFor; - } - function _distributePoolReward( IParaApeStaking.PoolState storage poolState, mapping(address => uint256) storage cApeShareBalance, diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index dca6b13a9..723a76e93 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -9,6 +9,7 @@ import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; import {IPool} from "../../interfaces/IPool.sol"; import "../../protocol/libraries/helpers/Errors.sol"; +import {IERC20, SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; /** * @title ApeStakingVaultLogic library @@ -19,6 +20,14 @@ library ApeStakingCommonLogic { using PercentageMath for uint256; using SafeCast for uint256; using WadRayMath for uint256; + using SafeERC20 for IERC20; + + event PoolRewardClaimed( + uint256 poolId, + address nft, + uint256 tokenId, + uint256 rewardShare + ); /** * @dev Minimum health factor to consider a user position healthy @@ -157,24 +166,77 @@ library ApeStakingCommonLogic { apeMatchedCount[ape][tokenId] = matchedCount; } - function calculatePendingReward( + function claimPendingReward( IParaApeStaking.PoolState storage poolState, - address cApe, + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 poolId, + address nft, + address nToken, + bool needUpdateStatus, uint32[] memory tokenIds - ) internal view returns (uint256) { - uint256 rewardShares; - uint256 arrayLength = tokenIds.length; - uint256 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - for (uint256 index = 0; index < arrayLength; index++) { + ) internal returns (address) { + address claimFor; + uint256 totalRewardShares; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + for (uint256 index = 0; index < tokenIds.length; index++) { uint32 tokenId = tokenIds[index]; + //just need to check ape ntoken owner + { + address nApeOwner = IERC721(nToken).ownerOf(tokenId); + if (claimFor == address(0)) { + claimFor = nApeOwner; + } else { + require(nApeOwner == claimFor, Errors.NOT_THE_SAME_OWNER); + } + } + IParaApeStaking.TokenStatus memory tokenStatus = poolState .tokenStatus[tokenId]; + + //check is in pool require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); - rewardShares += (accumulatedRewardsPerNft - - tokenStatus.rewardsDebt); + //update reward, to save gas we don't claim pending reward in ApeCoinStaking. + uint256 rewardShare = accumulatedRewardsPerNft - + tokenStatus.rewardsDebt; + totalRewardShares += rewardShare; + + if (needUpdateStatus) { + poolState + .tokenStatus[tokenId] + .rewardsDebt = accumulatedRewardsPerNft; + } + + //emit event + emit PoolRewardClaimed(poolId, nft, tokenId, rewardShare); + } + + if (totalRewardShares > 0) { + uint256 pendingReward = ICApe(vars.cApe).getPooledApeByShares( + totalRewardShares + ); + IERC20(vars.cApe).safeTransfer(claimFor, pendingReward); + } + + return claimFor; + } + + function getNftFromPoolId( + IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 poolId + ) internal pure returns (address, address) { + if (poolId == ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID) { + return (vars.bakc, vars.nBakc); + } else if ( + poolId == ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID || + poolId == ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID || + poolId == ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID || + poolId == ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + ) { + return (vars.bayc, vars.nBayc); + } else { + return (vars.mayc, vars.nMayc); } - return ICApe(cApe).getPooledApeByShares(rewardShares); } } diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 70085f3e2..2074c76a5 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -31,7 +31,6 @@ library ApeStakingPairPoolLogic { ); event PairNFTStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); event PairNFTWithdrew(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); - event PairNFTClaimed(bool isBAYC, uint256 apeTokenId, uint256 rewardShare); event PairNFTCompounded( bool isBAYC, uint256 apeTokenId, @@ -211,14 +210,6 @@ library ApeStakingPairPoolLogic { Errors.INVALID_PARAMETER ); - vars.nApeOwner = _claimPairNFT( - poolState, - vars, - false, - isBAYC, - apeTokenIds - ); - if (isBAYC) { vars.apeStakingPoolId = ApeStakingCommonLogic.BAYC_POOL_ID; vars.apeToken = vars.bayc; @@ -230,6 +221,19 @@ library ApeStakingPairPoolLogic { vars.nApe = vars.nMayc; vars.positionCap = vars.maycMatchedCap; } + + vars.nApeOwner = ApeStakingCommonLogic.claimPendingReward( + poolState, + vars, + isBAYC + ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID, + vars.apeToken, + vars.nApe, + false, + apeTokenIds + ); + ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); ApeCoinStaking.PairNftWithdrawWithAmount[] @@ -354,17 +358,6 @@ library ApeStakingPairPoolLogic { } } - function claimPairNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool isBAYC, - uint32[] calldata apeTokenIds - ) external { - ApeStakingCommonLogic.validateTokenIdArray(apeTokenIds); - - _claimPairNFT(poolState, vars, true, isBAYC, apeTokenIds); - } - function compoundPairNFT( IParaApeStaking.PoolState storage poolState, mapping(address => uint256) storage cApeShareBalance, @@ -447,72 +440,4 @@ library ApeStakingPairPoolLogic { cApeShareBalance[address(this)] += vars.totalCompoundFee; } } - - function calculatePendingReward( - IParaApeStaking.PoolState storage poolState, - address cApe, - uint32[] memory tokenIds - ) external view returns (uint256) { - return - ApeStakingCommonLogic.calculatePendingReward( - poolState, - cApe, - tokenIds - ); - } - - function _claimPairNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool needUpdateStatus, - bool isBAYC, - uint32[] calldata apeTokenIds - ) internal returns (address) { - address claimFor; - uint256 totalRewardShares; - uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - address nApe = isBAYC ? vars.nBayc : vars.nMayc; - for (uint256 index = 0; index < apeTokenIds.length; index++) { - uint32 apeTokenId = apeTokenIds[index]; - - //just need to check ape ntoken owner - { - address nApeOwner = IERC721(nApe).ownerOf(apeTokenId); - if (claimFor == address(0)) { - claimFor = nApeOwner; - } else { - require(nApeOwner == claimFor, Errors.NOT_THE_SAME_OWNER); - } - } - - IParaApeStaking.TokenStatus memory tokenStatus = poolState - .tokenStatus[apeTokenId]; - - //check is in pool - require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); - - //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - uint256 rewardShare = accumulatedRewardsPerNft - - tokenStatus.rewardsDebt; - totalRewardShares += rewardShare; - - if (needUpdateStatus) { - poolState - .tokenStatus[apeTokenId] - .rewardsDebt = accumulatedRewardsPerNft; - } - - //emit event - emit PairNFTClaimed(isBAYC, apeTokenId, rewardShare); - } - - if (totalRewardShares > 0) { - uint256 pendingReward = ICApe(vars.cApe).getPooledApeByShares( - totalRewardShares - ); - IERC20(vars.cApe).safeTransfer(claimFor, pendingReward); - } - - return claimFor; - } } diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 37f03a81f..50b66e8cb 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -29,7 +29,6 @@ library ApeStakingSinglePoolLogic { event BakcStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); event ApeCompounded(bool isBAYC, uint256 tokenId); event BakcCompounded(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); - event NFTClaimed(address nft, uint256 tokenId); event NFTWithdrawn(address nft, uint256 tokenId); function depositNFT( @@ -382,16 +381,6 @@ library ApeStakingSinglePoolLogic { } } - function claimNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - address nft, - uint32[] calldata tokenIds - ) external { - ApeStakingCommonLogic.validateTokenIdArray(tokenIds); - _claimNFT(poolState, vars, true, nft, tokenIds); - } - function withdrawNFT( mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, mapping(address => uint256) storage cApeShareBalance, @@ -402,15 +391,30 @@ library ApeStakingSinglePoolLogic { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); - uint256 poolId = (nft == vars.bayc) - ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID - : (nft == vars.mayc) - ? ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID - : ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; + uint256 poolId; + address nToken; + if (nft == vars.bayc) { + poolId = ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID; + nToken = vars.nBayc; + } else if (nft == vars.mayc) { + poolId = ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID; + nToken = vars.nMayc; + } else { + poolId = ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; + nToken = vars.nBakc; + } //claim pending reward IParaApeStaking.PoolState storage poolState = poolStates[poolId]; - address nApeOwner = _claimNFT(poolState, vars, false, nft, tokenIds); + address nApeOwner = ApeStakingCommonLogic.claimPendingReward( + poolState, + vars, + poolId, + nft, + nToken, + false, + tokenIds + ); if (nft == vars.bayc) { _unstakeApe(poolStates, cApeShareBalance, vars, true, tokenIds); } else if (nft == vars.mayc) { @@ -421,10 +425,6 @@ library ApeStakingSinglePoolLogic { //transfer nft back to nToken require(msg.sender == nApeOwner, Errors.NOT_THE_OWNER); - - address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) - ? vars.nMayc - : vars.nBakc; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; @@ -440,6 +440,7 @@ library ApeStakingSinglePoolLogic { function tryClaimNFT( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, + uint256 poolId, address nft, uint32[] calldata tokenIds ) external { @@ -462,7 +463,21 @@ library ApeStakingSinglePoolLogic { mstore(singlePoolTokenIds, singlePoolCount) } - _claimNFT(poolState, vars, true, nft, singlePoolTokenIds); + address nToken = (nft == vars.bayc) + ? vars.nBayc + : (nft == vars.mayc) + ? vars.nMayc + : vars.nBakc; + + ApeStakingCommonLogic.claimPendingReward( + poolState, + vars, + poolId, + nft, + nToken, + true, + tokenIds + ); } } @@ -704,76 +719,6 @@ library ApeStakingSinglePoolLogic { } } - function calculatePendingReward( - IParaApeStaking.PoolState storage poolState, - address cApe, - uint32[] calldata tokenIds - ) external view returns (uint256) { - return - ApeStakingCommonLogic.calculatePendingReward( - poolState, - cApe, - tokenIds - ); - } - - function _claimNFT( - IParaApeStaking.PoolState storage poolState, - IParaApeStaking.ApeStakingVaultCacheVars memory vars, - bool needUpdateStatus, - address nft, - uint32[] memory tokenIds - ) internal returns (address) { - address claimFor; - uint256 totalRewardShares; - uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; - address nToken = (nft == vars.bayc) ? vars.nBayc : (nft == vars.mayc) - ? vars.nMayc - : vars.nBakc; - for (uint256 index = 0; index < tokenIds.length; index++) { - uint32 tokenId = tokenIds[index]; - - //just need to check ape ntoken owner - { - address nApeOwner = IERC721(nToken).ownerOf(tokenId); - if (claimFor == address(0)) { - claimFor = nApeOwner; - } else { - require(nApeOwner == claimFor, Errors.NOT_THE_SAME_OWNER); - } - } - - IParaApeStaking.TokenStatus memory tokenStatus = poolState - .tokenStatus[tokenId]; - - //check is in pool - require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); - - //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - uint256 rewardShare = accumulatedRewardsPerNft - - tokenStatus.rewardsDebt; - totalRewardShares += rewardShare; - - if (needUpdateStatus) { - poolState - .tokenStatus[tokenId] - .rewardsDebt = accumulatedRewardsPerNft; - } - - //emit event - emit NFTClaimed(nft, tokenId); - } - - if (totalRewardShares > 0) { - uint256 pendingReward = ICApe(vars.cApe).getPooledApeByShares( - totalRewardShares - ); - IERC20(vars.cApe).safeTransfer(claimFor, pendingReward); - } - - return claimFor; - } - function _calculateRepayAndCompoundBAKC( mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, IParaApeStaking.ApeStakingVaultCacheVars memory vars diff --git a/contracts/interfaces/IApeCoinPool.sol b/contracts/interfaces/IApeCoinPool.sol index 860db0c34..0d32fefad 100644 --- a/contracts/interfaces/IApeCoinPool.sol +++ b/contracts/interfaces/IApeCoinPool.sol @@ -40,13 +40,6 @@ interface IApeCoinPool { function compoundApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external; - function apeCoinPoolPendingReward(bool isBAYC, uint32[] calldata tokenIds) - external - view - returns (uint256); - - function claimApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external; - function withdrawApeCoinPool(ApeCoinWithdrawInfo calldata withdrawInfo) external; @@ -59,14 +52,6 @@ interface IApeCoinPool { uint32[] calldata bakcTokenIds ) external; - function apeCoinPairPoolPendingReward( - bool isBAYC, - uint32[] calldata apeTokenIds - ) external view returns (uint256); - - function claimApeCoinPairPool(bool isBAYC, uint32[] calldata apeTokenIds) - external; - function withdrawApeCoinPairPool( ApeCoinPairWithdrawInfo calldata withdrawInfo ) external; diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index c2b4a2bfb..cc7083cb5 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -53,23 +53,6 @@ interface IApeStakingVault { uint32[] calldata bakcTokenIds ) external; - /** - * @notice get Ape and BAKC pair staking unclaimed cApe reward - * @param isBAYC if Ape is BAYC - * @param apeTokenIds Ape token ids - */ - function pairNFTPendingReward(bool isBAYC, uint32[] calldata apeTokenIds) - external - returns (uint256); - - /** - * @notice claim Ape and BAKC pair staking unclaimed cApe reward - * to save gas we don't claim pending reward in ApeCoinStaking. - * @param isBAYC if Ape is BAYC - * @param apeTokenIds Ape token ids - */ - function claimPairNFT(bool isBAYC, uint32[] calldata apeTokenIds) external; - /** * @notice withdraw Ape and BAKC pair from pool * if the pair is staking it ApeCoinStaking, it will unstake from ApeCoinStaking first. @@ -118,23 +101,6 @@ interface IApeStakingVault { */ function compoundBAKC(BAKCPairActionInfo calldata actionInfo) external; - /** - * @notice get single pool nft unclaimed cApe reward - * @param nft Ape or BAKC token address - * @param tokenIds nft token ids - */ - function nftPendingReward(address nft, uint32[] calldata tokenIds) - external - returns (uint256); - - /** - * @notice claim single pool nft unclaimed cApe reward - * to save gas we don't claim pending reward in ApeCoinStaking. - * @param nft Ape or BAKC token address - * @param tokenIds nft token ids - */ - function claimNFT(address nft, uint32[] calldata tokenIds) external; - /** * @notice withdraw nft from single pool * if the nft is staking it ApeCoinStaking, it will unstake from ApeCoinStaking first. diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index 1b83d09f5..cb814f8d8 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -179,13 +179,8 @@ describe("Para Ape Staking Test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("10")); - const user1PendingReward = await paraApeStaking.pairNFTPendingReward( - true, - [0, 1] - ); - const user2PendingReward = await paraApeStaking.pairNFTPendingReward(true, [ - 2, - ]); + const user1PendingReward = await paraApeStaking.getPendingReward(1, [0, 1]); + const user2PendingReward = await paraApeStaking.getPendingReward(1, [2]); expect(user1PendingReward).to.be.closeTo( parseEther("4320"), parseEther("50") @@ -196,10 +191,10 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimPairNFT(true, [0, 1]) + await paraApeStaking.connect(user1.signer).claimPendingReward(1, [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimPairNFT(true, [2]) + await paraApeStaking.connect(user2.signer).claimPendingReward(1, [2]) ); let user1Balance = await cApe.balanceOf(user1.address); let user2Balance = await cApe.balanceOf(user2.address); @@ -207,14 +202,11 @@ describe("Para Ape Staking Test", () => { expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); - const newUser1PendingReward = await paraApeStaking.pairNFTPendingReward( - true, + const newUser1PendingReward = await paraApeStaking.getPendingReward( + 1, [0, 1] ); - const newUser2PendingReward = await paraApeStaking.pairNFTPendingReward( - true, - [2] - ); + const newUser2PendingReward = await paraApeStaking.getPendingReward(1, [2]); expect(newUser1PendingReward).to.be.equal(0); expect(newUser2PendingReward).to.be.equal(0); @@ -349,14 +341,8 @@ describe("Para Ape Staking Test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("10")); - const user1PendingReward = await paraApeStaking.pairNFTPendingReward( - false, - [0, 1] - ); - const user2PendingReward = await paraApeStaking.pairNFTPendingReward( - false, - [2] - ); + const user1PendingReward = await paraApeStaking.getPendingReward(2, [0, 1]); + const user2PendingReward = await paraApeStaking.getPendingReward(2, [2]); expect(user1PendingReward).to.be.closeTo( parseEther("4320"), parseEther("50") @@ -367,10 +353,10 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimPairNFT(false, [0, 1]) + await paraApeStaking.connect(user1.signer).claimPendingReward(2, [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimPairNFT(false, [2]) + await paraApeStaking.connect(user2.signer).claimPendingReward(2, [2]) ); let user1Balance = await cApe.balanceOf(user1.address); let user2Balance = await cApe.balanceOf(user2.address); @@ -378,14 +364,11 @@ describe("Para Ape Staking Test", () => { expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); expect(user1Balance).to.be.closeTo(user2Balance.mul(2), parseEther("10")); - const newUser1PendingReward = await paraApeStaking.pairNFTPendingReward( - false, + const newUser1PendingReward = await paraApeStaking.getPendingReward( + 2, [0, 1] ); - const newUser2PendingReward = await paraApeStaking.pairNFTPendingReward( - false, - [2] - ); + const newUser2PendingReward = await paraApeStaking.getPendingReward(2, [2]); expect(newUser1PendingReward).to.be.equal(0); expect(newUser2PendingReward).to.be.equal(0); @@ -551,16 +534,16 @@ describe("Para Ape Staking Test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("1080"), parseEther("10")); - const user1PendingReward = await paraApeStaking.nftPendingReward( - bayc.address, + const user1PendingReward = await paraApeStaking.getPendingReward( + 3, [0, 1, 2] ); - const user2PendingReward = await paraApeStaking.nftPendingReward( - mayc.address, + const user2PendingReward = await paraApeStaking.getPendingReward( + 4, [0, 1, 2] ); - const user3PendingReward = await paraApeStaking.nftPendingReward( - bakc.address, + const user3PendingReward = await paraApeStaking.getPendingReward( + 5, [0, 1, 2] ); expect(user1PendingReward).to.be.closeTo( @@ -579,17 +562,17 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .claimNFT(bayc.address, [0, 1, 2]) + .claimPendingReward(3, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user2.signer) - .claimNFT(mayc.address, [0, 1, 2]) + .claimPendingReward(4, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user2.signer) - .claimNFT(bakc.address, [0, 1, 2]) + .claimPendingReward(5, [0, 1, 2]) ); let user1Balance = await cApe.balanceOf(user1.address); let user2Balance = await cApe.balanceOf(user2.address); @@ -598,16 +581,16 @@ describe("Para Ape Staking Test", () => { expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("100")); expect(user3Balance).to.be.closeTo(user3PendingReward, parseEther("100")); - const newUser1PendingReward = await paraApeStaking.nftPendingReward( - bayc.address, + const newUser1PendingReward = await paraApeStaking.getPendingReward( + 3, [0, 1, 2] ); - const newUser2PendingReward = await paraApeStaking.nftPendingReward( - mayc.address, + const newUser2PendingReward = await paraApeStaking.getPendingReward( + 4, [0, 1, 2] ); - const newUser3PendingReward = await paraApeStaking.nftPendingReward( - bakc.address, + const newUser3PendingReward = await paraApeStaking.getPendingReward( + 5, [0, 1, 2] ); expect(newUser1PendingReward).to.be.equal(0); @@ -816,11 +799,11 @@ describe("Para Ape Staking Test", () => { ); await expect( - paraApeStaking.connect(user1.signer).claimPairNFT(true, [0, 1, 2]) + paraApeStaking.connect(user1.signer).claimPendingReward(1, [0, 1, 2]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); await expect( - paraApeStaking.connect(user1.signer).claimPairNFT(true, [3]) + paraApeStaking.connect(user1.signer).claimPendingReward(1, [3]) ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); }); @@ -1154,35 +1137,35 @@ describe("Para Ape Staking Test", () => { ); await expect( - paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [0, 1]) + paraApeStaking.connect(user1.signer).claimPendingReward(3, [0, 1]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); await expect( - paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [2]) + paraApeStaking.connect(user1.signer).claimPendingReward(3, [2]) ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await expect( - paraApeStaking.connect(user1.signer).claimNFT(bakc.address, [0, 1]) + paraApeStaking.connect(user1.signer).claimPendingReward(5, [0, 1]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); await expect( - paraApeStaking.connect(user1.signer).claimNFT(bakc.address, [2]) + paraApeStaking.connect(user1.signer).claimPendingReward(5, [2]) ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( - await paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [0]) + await paraApeStaking.connect(user1.signer).claimPendingReward(3, [0]) ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimNFT(bakc.address, [0]) + await paraApeStaking.connect(user1.signer).claimPendingReward(5, [0]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimNFT(bayc.address, [1]) + await paraApeStaking.connect(user2.signer).claimPendingReward(3, [1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimNFT(bakc.address, [1]) + await paraApeStaking.connect(user2.signer).claimPendingReward(5, [1]) ); }); @@ -1343,20 +1326,20 @@ describe("Para Ape Staking Test", () => { await paraApeStaking.connect(user4.signer).compoundApe(false, [2, 3]) ); - tx0 = paraApeStaking.interface.encodeFunctionData("claimPairNFT", [ - true, + tx0 = paraApeStaking.interface.encodeFunctionData("claimPendingReward", [ + 1, [0, 1], ]); - tx1 = paraApeStaking.interface.encodeFunctionData("claimPairNFT", [ - false, + tx1 = paraApeStaking.interface.encodeFunctionData("claimPendingReward", [ + 2, [0, 1], ]); - tx2 = paraApeStaking.interface.encodeFunctionData("claimNFT", [ - bayc.address, + tx2 = paraApeStaking.interface.encodeFunctionData("claimPendingReward", [ + 3, [2, 3], ]); - tx3 = paraApeStaking.interface.encodeFunctionData("claimNFT", [ - mayc.address, + tx3 = paraApeStaking.interface.encodeFunctionData("claimPendingReward", [ + 4, [2, 3], ]); @@ -1444,15 +1427,15 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimNFT(bayc.address, [0, 1]) + await paraApeStaking.connect(user1.signer).claimPendingReward(3, [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimNFT(mayc.address, [0, 1]) + await paraApeStaking.connect(user2.signer).claimPendingReward(4, [0, 1]) ); await waitForTx( await paraApeStaking .connect(user3.signer) - .claimNFT(bakc.address, [0, 1, 2, 3]) + .claimPendingReward(5, [0, 1, 2, 3]) ); //user1: 3600 * 0.5 * 0.5 @@ -1585,8 +1568,8 @@ describe("Para Ape Staking Test", () => { bakcPairMaycTokenIds: [], }) ); - const user3PendingReward0 = await paraApeStaking.nftPendingReward( - bakc.address, + const user3PendingReward0 = await paraApeStaking.getPendingReward( + 5, [0, 1, 2, 3] ); expect(user3PendingReward0).to.be.closeTo( @@ -1602,27 +1585,21 @@ describe("Para Ape Staking Test", () => { bakcPairMaycTokenIds: [2, 3], }) ); - const user3PendingReward1 = await paraApeStaking.nftPendingReward( - bakc.address, + const user3PendingReward1 = await paraApeStaking.getPendingReward( + 5, [0, 1, 2, 3] ); expect(user3PendingReward1).to.be.closeTo( parseEther("1800"), parseEther("10") ); - const user1PendingReward = await paraApeStaking.nftPendingReward( - bayc.address, - [0, 1] - ); + const user1PendingReward = await paraApeStaking.getPendingReward(3, [0, 1]); //900 * 2 / 3 expect(user1PendingReward).to.be.closeTo( parseEther("600"), parseEther("10") ); - const user2PendingReward = await paraApeStaking.nftPendingReward( - mayc.address, - [0, 1] - ); + const user2PendingReward = await paraApeStaking.getPendingReward(4, [0, 1]); //900 * 2 / 3 expect(user2PendingReward).to.be.closeTo( parseEther("600"), @@ -1690,19 +1667,13 @@ describe("Para Ape Staking Test", () => { .connect(user3.signer) .withdrawNFT(bakc.address, [0, 1, 2, 3]) ); - const user1PendingReward = await paraApeStaking.nftPendingReward( - bayc.address, - [0, 1] - ); + const user1PendingReward = await paraApeStaking.getPendingReward(3, [0, 1]); //900 * 2 / 3 expect(user1PendingReward).to.be.closeTo( parseEther("600"), parseEther("10") ); - const user2PendingReward = await paraApeStaking.nftPendingReward( - mayc.address, - [0, 1] - ); + const user2PendingReward = await paraApeStaking.getPendingReward(4, [0, 1]); expect(user1PendingReward).to.be.closeTo( user2PendingReward, parseEther("1") diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index f6fa7f9df..3e9de5a1f 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -469,14 +469,8 @@ describe("Para Ape staking ape coin pool test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("360"), parseEther("1")); - let user1PendingReward = await paraApeStaking.apeCoinPoolPendingReward( - true, - [0, 1] - ); - let user2PendingReward = await paraApeStaking.apeCoinPoolPendingReward( - true, - [2] - ); + let user1PendingReward = await paraApeStaking.getPendingReward(6, [0, 1]); + let user2PendingReward = await paraApeStaking.getPendingReward(6, [2]); expect(user1PendingReward).to.be.closeTo( parseEther("2160"), parseEther("1") @@ -487,23 +481,18 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimApeCoinPool(true, [0, 1]) + await paraApeStaking.connect(user1.signer).claimPendingReward(6, [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimApeCoinPool(true, [2]) + await paraApeStaking.connect(user2.signer).claimPendingReward(6, [2]) ); let user1Balance = await cApe.balanceOf(user1.address); let user2Balance = await cApe.balanceOf(user2.address); expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); - user1PendingReward = await paraApeStaking.apeCoinPoolPendingReward( - true, - [0, 1] - ); - user2PendingReward = await paraApeStaking.apeCoinPoolPendingReward(true, [ - 2, - ]); + user1PendingReward = await paraApeStaking.getPendingReward(6, [0, 1]); + user2PendingReward = await paraApeStaking.getPendingReward(6, [2]); expect(user1PendingReward).to.be.equal(0); expect(user2PendingReward).to.be.equal(0); @@ -517,14 +506,8 @@ describe("Para Ape staking ape coin pool test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("1")); - user1PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( - true, - [0, 1] - ); - user2PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( - true, - [2] - ); + user1PendingReward = await paraApeStaking.getPendingReward(8, [0, 1]); + user2PendingReward = await paraApeStaking.getPendingReward(8, [2]); expect(user1PendingReward).to.be.closeTo( parseEther("2160"), parseEther("1") @@ -535,12 +518,10 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .claimApeCoinPairPool(true, [0, 1]) + await paraApeStaking.connect(user1.signer).claimPendingReward(8, [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimApeCoinPairPool(true, [2]) + await paraApeStaking.connect(user2.signer).claimPendingReward(8, [2]) ); user1Balance = await cApe.balanceOf(user1.address); user2Balance = await cApe.balanceOf(user2.address); @@ -553,14 +534,8 @@ describe("Para Ape staking ape coin pool test", () => { parseEther("1") ); - user1PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( - true, - [0, 1] - ); - user2PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( - true, - [2] - ); + user1PendingReward = await paraApeStaking.getPendingReward(8, [0, 1]); + user2PendingReward = await paraApeStaking.getPendingReward(8, [2]); expect(user1PendingReward).to.be.equal(0); expect(user2PendingReward).to.be.equal(0); @@ -747,14 +722,8 @@ describe("Para Ape staking ape coin pool test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("360"), parseEther("1")); - let user1PendingReward = await paraApeStaking.apeCoinPoolPendingReward( - false, - [0, 1] - ); - let user2PendingReward = await paraApeStaking.apeCoinPoolPendingReward( - false, - [2] - ); + let user1PendingReward = await paraApeStaking.getPendingReward(7, [0, 1]); + let user2PendingReward = await paraApeStaking.getPendingReward(7, [2]); expect(user1PendingReward).to.be.closeTo( parseEther("2160"), parseEther("1") @@ -765,23 +734,18 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimApeCoinPool(false, [0, 1]) + await paraApeStaking.connect(user1.signer).claimPendingReward(7, [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimApeCoinPool(false, [2]) + await paraApeStaking.connect(user2.signer).claimPendingReward(7, [2]) ); let user1Balance = await cApe.balanceOf(user1.address); let user2Balance = await cApe.balanceOf(user2.address); expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("1")); expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("1")); - user1PendingReward = await paraApeStaking.apeCoinPoolPendingReward( - false, - [0, 1] - ); - user2PendingReward = await paraApeStaking.apeCoinPoolPendingReward(false, [ - 2, - ]); + user1PendingReward = await paraApeStaking.getPendingReward(7, [0, 1]); + user2PendingReward = await paraApeStaking.getPendingReward(7, [2]); expect(user1PendingReward).to.be.equal(0); expect(user2PendingReward).to.be.equal(0); @@ -795,14 +759,8 @@ describe("Para Ape staking ape coin pool test", () => { ); expect(compoundFee).to.be.closeTo(parseEther("720"), parseEther("1")); - user1PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( - false, - [0, 1] - ); - user2PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( - false, - [2] - ); + user1PendingReward = await paraApeStaking.getPendingReward(9, [0, 1]); + user2PendingReward = await paraApeStaking.getPendingReward(9, [2]); expect(user1PendingReward).to.be.closeTo( parseEther("2160"), parseEther("1") @@ -813,14 +771,10 @@ describe("Para Ape staking ape coin pool test", () => { ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .claimApeCoinPairPool(false, [0, 1]) + await paraApeStaking.connect(user1.signer).claimPendingReward(9, [0, 1]) ); await waitForTx( - await paraApeStaking - .connect(user2.signer) - .claimApeCoinPairPool(false, [2]) + await paraApeStaking.connect(user2.signer).claimPendingReward(9, [2]) ); user1Balance = await cApe.balanceOf(user1.address); user2Balance = await cApe.balanceOf(user2.address); @@ -833,14 +787,8 @@ describe("Para Ape staking ape coin pool test", () => { parseEther("1") ); - user1PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( - false, - [0, 1] - ); - user2PendingReward = await paraApeStaking.apeCoinPairPoolPendingReward( - false, - [2] - ); + user1PendingReward = await paraApeStaking.getPendingReward(9, [0, 1]); + user2PendingReward = await paraApeStaking.getPendingReward(9, [2]); expect(user1PendingReward).to.be.equal(0); expect(user2PendingReward).to.be.equal(0); @@ -1339,11 +1287,11 @@ describe("Para Ape staking ape coin pool test", () => { ); await expect( - paraApeStaking.connect(user1.signer).claimApeCoinPool(true, [0, 1]) + paraApeStaking.connect(user1.signer).claimPendingReward(6, [0, 1]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); await expect( - paraApeStaking.connect(user1.signer).claimApeCoinPool(true, [2]) + paraApeStaking.connect(user1.signer).claimPendingReward(6, [2]) ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); }); @@ -1545,11 +1493,11 @@ describe("Para Ape staking ape coin pool test", () => { ); await expect( - paraApeStaking.connect(user1.signer).claimApeCoinPairPool(true, [0, 1]) + paraApeStaking.connect(user1.signer).claimPendingReward(8, [0, 1]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_SAME_OWNER); await expect( - paraApeStaking.connect(user1.signer).claimApeCoinPairPool(true, [2]) + paraApeStaking.connect(user1.signer).claimPendingReward(8, [2]) ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); }); @@ -1841,22 +1789,11 @@ describe("Para Ape staking ape coin pool test", () => { }) ); - const baycPairReward = await paraApeStaking.pairNFTPendingReward(true, [0]); - const maycPairReward = await paraApeStaking.pairNFTPendingReward(false, [ - 0, - ]); - const baycSingleReward = await paraApeStaking.nftPendingReward( - bayc.address, - [1] - ); - const maycSingleReward = await paraApeStaking.nftPendingReward( - mayc.address, - [1] - ); - const bakcSingleReward = await paraApeStaking.nftPendingReward( - bakc.address, - [2] - ); + const baycPairReward = await paraApeStaking.getPendingReward(1, [0]); + const maycPairReward = await paraApeStaking.getPendingReward(2, [0]); + const baycSingleReward = await paraApeStaking.getPendingReward(3, [1]); + const maycSingleReward = await paraApeStaking.getPendingReward(4, [1]); + const bakcSingleReward = await paraApeStaking.getPendingReward(5, [2]); //1800 + 1200 expect(baycPairReward).to.be.closeTo(parseEther("3000"), parseEther("50")); //1800 + 1200 From 6d03eec7169148a086d4682a377f2531e9dc599d Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 3 Aug 2023 09:18:29 +0800 Subject: [PATCH 83/99] chore: update token status query interface --- contracts/apestaking/ParaApeStaking.sol | 32 ++++------ .../apestaking/logic/ApeCoinPoolLogic.sol | 62 ++++++++++--------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 0e220570b..636178a34 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -51,7 +51,7 @@ contract ParaApeStaking is address private immutable psApe; //record all pool states - mapping(uint256 => PoolState) private poolStates; + mapping(uint256 => PoolState) public poolStates; //record user sApe balance mapping(address => SApeBalance) private sApeBalance; @@ -224,20 +224,20 @@ contract ParaApeStaking is /* *common Logic */ - function isNFTInPoolId( + function poolTokenStatus( + uint256 poolId, address nft, - uint256 tokenId, - uint256 poolId - ) external view returns (bool) { + uint256 tokenId + ) external view returns (IParaApeStaking.TokenStatus memory) { return - ApeCoinPoolLogic.isNFTInPoolId( + ApeCoinPoolLogic.poolTokenStatus( poolStates, bayc, mayc, bakc, + poolId, nft, - tokenId, - poolId + tokenId ); } @@ -399,7 +399,8 @@ contract ParaApeStaking is sApeBalance, cApeShareBalance, vars, - withdrawInfo + withdrawInfo, + poolId ); } @@ -460,7 +461,8 @@ contract ParaApeStaking is sApeBalance, cApeShareBalance, vars, - withdrawInfo + withdrawInfo, + poolId ); } @@ -564,16 +566,8 @@ contract ParaApeStaking is mstore(apeCoinPoolTokenIds, apeCoinPoolCount) } - uint256 singlePoolId = isBAYC - ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; - uint256 PairPoolId = isBAYC - ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; - ApeCoinPoolLogic.tryUnstakeApeCoinPoolPosition( - poolStates[singlePoolId], - poolStates[PairPoolId], + poolStates, apeMatchedCount, sApeBalance, cApeShareBalance, diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index e32d84c1e..9c066a415 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -49,15 +49,16 @@ library ApeCoinPoolLogic { uint256 bakcTokenId ); - function isNFTInPoolId( + function poolTokenStatus( mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, address bayc, address mayc, address bakc, + uint256 poolId, address nft, - uint256 tokenId, - uint256 poolId - ) external view returns (bool) { + uint256 tokenId + ) external view returns (IParaApeStaking.TokenStatus memory) { + IParaApeStaking.TokenStatus memory tokenStatus; if (nft == bayc) { if ( poolId == ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID || @@ -65,7 +66,7 @@ library ApeCoinPoolLogic { poolId == ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID || poolId == ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID ) { - return poolStates[poolId].tokenStatus[tokenId].isInPool; + tokenStatus = poolStates[poolId].tokenStatus[tokenId]; } } else if (nft == mayc) { if ( @@ -74,15 +75,15 @@ library ApeCoinPoolLogic { poolId == ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID || poolId == ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID ) { - return poolStates[poolId].tokenStatus[tokenId].isInPool; + tokenStatus = poolStates[poolId].tokenStatus[tokenId]; } } else if (nft == bakc) { if (poolId == ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID) { - return poolStates[poolId].tokenStatus[tokenId].isInPool; + tokenStatus = poolStates[poolId].tokenStatus[tokenId]; } } - return false; + return tokenStatus; } function getPendingReward( @@ -308,7 +309,8 @@ library ApeCoinPoolLogic { mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - IParaApeStaking.ApeCoinWithdrawInfo memory withdrawInfo + IParaApeStaking.ApeCoinWithdrawInfo memory withdrawInfo, + uint256 poolId ) public { uint256 arrayLength = withdrawInfo.tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); @@ -326,9 +328,7 @@ library ApeCoinPoolLogic { address nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, vars, - withdrawInfo.isBAYC - ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID, + poolId, vars.apeToken, vars.nApe, false, @@ -565,7 +565,8 @@ library ApeCoinPoolLogic { mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, mapping(address => uint256) storage cApeShareBalance, IParaApeStaking.ApeStakingVaultCacheVars memory vars, - IParaApeStaking.ApeCoinPairWithdrawInfo memory withdrawInfo + IParaApeStaking.ApeCoinPairWithdrawInfo memory withdrawInfo, + uint256 poolId ) public { uint256 arrayLength = withdrawInfo.apeTokenIds.length; require( @@ -584,9 +585,7 @@ library ApeCoinPoolLogic { address nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, vars, - withdrawInfo.isBAYC - ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID - : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID, + poolId, vars.apeToken, vars.nApe, false, @@ -694,8 +693,7 @@ library ApeCoinPoolLogic { } function tryUnstakeApeCoinPoolPosition( - IParaApeStaking.PoolState storage singlePoolState, - IParaApeStaking.PoolState storage pairPoolState, + mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, mapping(address => mapping(uint32 => uint256)) storage apeMatchedCount, mapping(address => IParaApeStaking.SApeBalance) storage sApeBalance, mapping(address => uint256) storage cApeShareBalance, @@ -707,15 +705,18 @@ library ApeCoinPoolLogic { // check single { + uint256 singlePoolId = isBAYC + ? ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID; + uint32[] memory singlePoolTokenIds = new uint32[](tokenIds.length); uint256 singleCount = 0; for (uint256 index = 0; index < tokenIds.length; index++) { uint32 tokenId = tokenIds[index]; IParaApeStaking.TokenStatus - memory singlePoolTokenStatus = singlePoolState.tokenStatus[ - tokenId - ]; + memory singlePoolTokenStatus = poolStates[singlePoolId] + .tokenStatus[tokenId]; if (singlePoolTokenStatus.isInPool) { singlePoolTokenIds[singleCount] = tokenId; singleCount++; @@ -728,7 +729,7 @@ library ApeCoinPoolLogic { } withdrawApeCoinPool( - singlePoolState, + poolStates[singlePoolId], apeMatchedCount, sApeBalance, cApeShareBalance, @@ -738,13 +739,18 @@ library ApeCoinPoolLogic { cashAmount: 0, isBAYC: isBAYC, tokenIds: singlePoolTokenIds - }) + }), + singlePoolId ); } } // check pair { + uint256 pairPoolId = isBAYC + ? ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + : ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID; + uint32[] memory parePoolTokenIds = new uint32[](tokenIds.length); uint32[] memory bakcTokenIds = new uint32[](tokenIds.length); uint256 pairCount = 0; @@ -752,9 +758,8 @@ library ApeCoinPoolLogic { uint32 tokenId = tokenIds[index]; IParaApeStaking.TokenStatus - memory pairPoolTokenStatus = pairPoolState.tokenStatus[ - tokenId - ]; + memory pairPoolTokenStatus = poolStates[pairPoolId] + .tokenStatus[tokenId]; if (pairPoolTokenStatus.isInPool) { parePoolTokenIds[pairCount] = tokenId; bakcTokenIds[pairCount] = pairPoolTokenStatus.bakcTokenId; @@ -769,7 +774,7 @@ library ApeCoinPoolLogic { } withdrawApeCoinPairPool( - pairPoolState, + poolStates[pairPoolId], apeMatchedCount, sApeBalance, cApeShareBalance, @@ -780,7 +785,8 @@ library ApeCoinPoolLogic { isBAYC: isBAYC, apeTokenIds: parePoolTokenIds, bakcTokenIds: bakcTokenIds - }) + }), + pairPoolId ); } } From 7a4142ac10433d8a7b083ae1108b7b013269421d Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 3 Aug 2023 11:16:45 +0800 Subject: [PATCH 84/99] chore: simplify poolTokenStatus --- contracts/apestaking/ParaApeStaking.sol | 24 +++++------- .../apestaking/logic/ApeCoinPoolLogic.sol | 37 ------------------- contracts/interfaces/IParaApeStaking.sol | 33 +++++++++++++++++ 3 files changed, 42 insertions(+), 52 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 636178a34..5ff322006 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -224,23 +224,16 @@ contract ParaApeStaking is /* *common Logic */ - function poolTokenStatus( - uint256 poolId, - address nft, - uint256 tokenId - ) external view returns (IParaApeStaking.TokenStatus memory) { - return - ApeCoinPoolLogic.poolTokenStatus( - poolStates, - bayc, - mayc, - bakc, - poolId, - nft, - tokenId - ); + /// @inheritdoc IParaApeStaking + function poolTokenStatus(uint256 poolId, uint256 tokenId) + external + view + returns (IParaApeStaking.TokenStatus memory) + { + return poolStates[poolId].tokenStatus[tokenId]; } + /// @inheritdoc IParaApeStaking function getPendingReward(uint256 poolId, uint32[] calldata tokenIds) external view @@ -254,6 +247,7 @@ contract ParaApeStaking is ); } + /// @inheritdoc IParaApeStaking function claimPendingReward(uint256 poolId, uint32[] calldata tokenIds) external whenNotPaused diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 9c066a415..b007a66f5 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -49,43 +49,6 @@ library ApeCoinPoolLogic { uint256 bakcTokenId ); - function poolTokenStatus( - mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, - address bayc, - address mayc, - address bakc, - uint256 poolId, - address nft, - uint256 tokenId - ) external view returns (IParaApeStaking.TokenStatus memory) { - IParaApeStaking.TokenStatus memory tokenStatus; - if (nft == bayc) { - if ( - poolId == ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID || - poolId == ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID || - poolId == ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID || - poolId == ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID - ) { - tokenStatus = poolStates[poolId].tokenStatus[tokenId]; - } - } else if (nft == mayc) { - if ( - poolId == ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID || - poolId == ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID || - poolId == ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID || - poolId == ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID - ) { - tokenStatus = poolStates[poolId].tokenStatus[tokenId]; - } - } else if (nft == bakc) { - if (poolId == ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID) { - tokenStatus = poolStates[poolId].tokenStatus[tokenId]; - } - } - - return tokenStatus; - } - function getPendingReward( IParaApeStaking.PoolState storage poolState, address cApe, diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index b1cb752c7..afbb38396 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -80,8 +80,41 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { **/ event ApeStakingBotUpdated(address oldBot, address newBot); + /** + * @dev Emitted during setCompoundFee() + * @param oldValue The old value of compound fee + * @param newValue The new value of compound fee + **/ event CompoundFeeUpdated(uint64 oldValue, uint64 newValue); + /** + * @notice Query token status for the specified pool and nft + * @param poolId Identify pool + * @param tokenId The tokenId of the nft + */ + function poolTokenStatus(uint256 poolId, uint256 tokenId) + external + view + returns (IParaApeStaking.TokenStatus memory); + + /** + * @notice Query position pending reward in the pool, will revert if token id is not in the pool + * @param poolId Identify pool + * @param tokenIds The tokenIds of the nft + */ + function getPendingReward(uint256 poolId, uint32[] calldata tokenIds) + external + view + returns (uint256); + + /** + * @notice Claim position pending reward in the pool, will revert if token id is not in the pool + * @param poolId Identify pool + * @param tokenIds The tokenIds of the nft + */ + function claimPendingReward(uint256 poolId, uint32[] calldata tokenIds) + external; + function stakedSApeBalance(address user) external view returns (uint256); function freeSApeBalance(address user) external view returns (uint256); From 8b85cbc542423f1fcb78197cc3ee83fbc7fdbf4c Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 3 Aug 2023 16:29:52 +0800 Subject: [PATCH 85/99] chore: add comment --- contracts/apestaking/ParaApeStaking.sol | 36 +++++++++--- .../apestaking/logic/ApeCoinPoolLogic.sol | 21 +++---- contracts/interfaces/IApeCoinPool.sol | 58 +++++++++++++++++++ contracts/interfaces/IParaApeStaking.sol | 32 +++++++++- contracts/interfaces/IPoolApeStaking.sol | 18 ++++++ contracts/protocol/pool/PoolApeStaking.sol | 4 ++ test/para_ape_staking.spec.ts | 2 +- 7 files changed, 151 insertions(+), 20 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 5ff322006..645cbec5c 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -181,6 +181,8 @@ contract ParaApeStaking is } function setCompoundFee(uint64 _compoundFee) external onlyPoolAdmin { + //0.1e4 means 10% + require(_compoundFee <= 0.1e4, Errors.INVALID_PARAMETER); uint64 oldValue = compoundFee; if (oldValue != _compoundFee) { compoundFee = _compoundFee; @@ -254,23 +256,24 @@ contract ParaApeStaking is nonReentrant { ApeStakingVaultCacheVars memory vars = _createCacheVars(); - return - ApeCoinPoolLogic.claimPendingReward( - poolStates[poolId], - vars, - poolId, - tokenIds - ); + address owner = ApeCoinPoolLogic.claimPendingReward( + poolStates[poolId], + vars, + poolId, + tokenIds + ); + require(msg.sender == owner, Errors.CALLER_NOT_ALLOWED); } /* *sApe Logic */ - + /// @inheritdoc IParaApeStaking function stakedSApeBalance(address user) external view returns (uint256) { return sApeBalance[user].stakedBalance; } + /// @inheritdoc IParaApeStaking function freeSApeBalance(address user) external view returns (uint256) { uint256 freeShareBalance = sApeBalance[user].freeShareBalance; if (freeShareBalance == 0) { @@ -279,6 +282,7 @@ contract ParaApeStaking is return ICApe(cApe).getPooledApeByShares(freeShareBalance); } + /// @inheritdoc IParaApeStaking function totalSApeBalance(address user) external view returns (uint256) { IParaApeStaking.SApeBalance memory cache = sApeBalance[user]; uint256 freeShareBalance = cache.freeShareBalance; @@ -290,6 +294,7 @@ contract ParaApeStaking is cache.stakedBalance; } + /// @inheritdoc IParaApeStaking function transferFreeSApeBalance( address from, address to, @@ -301,6 +306,7 @@ contract ParaApeStaking is sApeBalance[to].freeShareBalance += shareAmount.toUint128(); } + /// @inheritdoc IParaApeStaking function depositFreeSApe(address cashAsset, uint128 amount) external whenNotPaused @@ -316,6 +322,7 @@ contract ParaApeStaking is ); } + /// @inheritdoc IParaApeStaking function withdrawFreeSApe(address receiveAsset, uint128 amount) external whenNotPaused @@ -336,6 +343,7 @@ contract ParaApeStaking is /* *Ape Coin Staking Pool Logic */ + /// @inheritdoc IApeCoinPool function depositApeCoinPool(ApeCoinDepositInfo calldata depositInfo) external whenNotPaused @@ -358,6 +366,7 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeCoinPool function compoundApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external onlyApeStakingBot @@ -376,6 +385,7 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeCoinPool function withdrawApeCoinPool(ApeCoinWithdrawInfo calldata withdrawInfo) external whenNotPaused @@ -398,6 +408,7 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeCoinPool function depositApeCoinPairPool(ApeCoinPairDepositInfo calldata depositInfo) external whenNotPaused @@ -420,6 +431,7 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeCoinPool function compoundApeCoinPairPool( bool isBAYC, uint32[] calldata apeTokenIds, @@ -440,6 +452,7 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeCoinPool function withdrawApeCoinPairPool( ApeCoinPairWithdrawInfo calldata withdrawInfo ) external whenNotPaused nonReentrant { @@ -460,11 +473,13 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeCoinPool function nBakcOwnerChangeCallback(uint32[] calldata tokenIds) external whenNotPaused nonReentrant { + require(msg.sender == nBakc, Errors.CALLER_NOT_ALLOWED); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID; ApeStakingSinglePoolLogic.tryClaimNFT( @@ -476,11 +491,16 @@ contract ParaApeStaking is ); } + /// @inheritdoc IApeCoinPool function nApeOwnerChangeCallback(bool isBAYC, uint32[] calldata tokenIds) external whenNotPaused nonReentrant { + require( + msg.sender == nBayc || msg.sender == nMayc, + Errors.CALLER_NOT_ALLOWED + ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); vars.compoundFee = compoundFee; diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index b007a66f5..08983c3a9 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -75,21 +75,22 @@ library ApeCoinPoolLogic { IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint256 poolId, uint32[] calldata tokenIds - ) external { + ) external returns (address) { ApeStakingCommonLogic.validateTokenIdArray(tokenIds); (address nft, address nToken) = ApeStakingCommonLogic.getNftFromPoolId( vars, poolId ); - ApeStakingCommonLogic.claimPendingReward( - poolState, - vars, - poolId, - nft, - nToken, - true, - tokenIds - ); + return + ApeStakingCommonLogic.claimPendingReward( + poolState, + vars, + poolId, + nft, + nToken, + true, + tokenIds + ); } function depositFreeSApe( diff --git a/contracts/interfaces/IApeCoinPool.sol b/contracts/interfaces/IApeCoinPool.sol index 0d32fefad..37e3c0a97 100644 --- a/contracts/interfaces/IApeCoinPool.sol +++ b/contracts/interfaces/IApeCoinPool.sol @@ -3,61 +3,119 @@ pragma solidity 0.8.10; interface IApeCoinPool { struct ApeCoinDepositInfo { + //deposit for address onBehalf; + //user payment token address cashToken; + //user cash amount uint256 cashAmount; + //isBAYC if Ape is BAYC bool isBAYC; + //Ape token ids uint32[] tokenIds; } struct ApeCoinPairDepositInfo { + //deposit for address onBehalf; + //user payment token address cashToken; + //user cash amount uint256 cashAmount; + //isBAYC if Ape is BAYC bool isBAYC; + //Ape token ids uint32[] apeTokenIds; + //BAKC token ids uint32[] bakcTokenIds; } struct ApeCoinWithdrawInfo { + //user receive token address cashToken; + //user receive token amount uint256 cashAmount; + //isBAYC if Ape is BAYC bool isBAYC; + //Ape token ids uint32[] tokenIds; } struct ApeCoinPairWithdrawInfo { + //user receive token address cashToken; + //user receive token amount uint256 cashAmount; + //isBAYC if Ape is BAYC bool isBAYC; + //Ape token ids uint32[] apeTokenIds; + //BAKC token ids uint32[] bakcTokenIds; } + /** + * @notice deposit Ape and ape coin position to Ape coin Pool. + * @param depositInfo Detail deposit info + **/ function depositApeCoinPool(ApeCoinDepositInfo calldata depositInfo) external; + /** + * @notice claim Ape staking reward from ApeCoinStaking and compound as cApe for user + * only ape staking bot can call this function + * @param isBAYC if Ape is BAYC + * @param tokenIds Ape token ids + */ function compoundApeCoinPool(bool isBAYC, uint32[] calldata tokenIds) external; + /** + * @notice withdraw Ape and ape coin position from Ape coin Pool + * @param withdrawInfo Detail withdraw info + */ function withdrawApeCoinPool(ApeCoinWithdrawInfo calldata withdrawInfo) external; + /** + * @notice deposit Ape and ape coin position to Ape coin Pool. + * @param depositInfo Detail deposit info + **/ function depositApeCoinPairPool(ApeCoinPairDepositInfo calldata depositInfo) external; + /** + * @notice claim Ape pair staking reward from ApeCoinStaking and compound as cApe for user + * only ape staking bot can call this function + * @param isBAYC if Ape is BAYC + * @param apeTokenIds Ape token ids + * @param bakcTokenIds BAKC token ids + */ function compoundApeCoinPairPool( bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external; + /** + * @notice withdraw Ape, BAKC and ape coin position from Ape coin Pair Pool + * @param withdrawInfo Detail withdraw info + */ function withdrawApeCoinPairPool( ApeCoinPairWithdrawInfo calldata withdrawInfo ) external; + /** + * @notice Callback function for Ape nToken owner change, will auto claim owner's pending reward or ApeCoin pool position + * @param isBAYC if Ape is BAYC + * @param tokenIds Ape token ids + */ function nApeOwnerChangeCallback(bool isBAYC, uint32[] calldata tokenIds) external; + /** + * @notice Callback function for BAKC nToken owner change, will auto claim owner's pending reward + * @param tokenIds BAKC tokenIds + */ function nBakcOwnerChangeCallback(uint32[] calldata tokenIds) external; } diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index afbb38396..71aaa7592 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -115,17 +115,47 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { function claimPendingReward(uint256 poolId, uint32[] calldata tokenIds) external; + /** + * @notice Query user's staked sApe balance, staked sApe cannot be liquidated directly + * @param user user address + */ function stakedSApeBalance(address user) external view returns (uint256); + /** + * @notice Query user's free sApe balance, free sApe can be liquidated + * @param user user address + */ function freeSApeBalance(address user) external view returns (uint256); + /** + * @notice Query user's total sApe balance, total sApe = staked sApe + free sApe + * @param user user address + */ function totalSApeBalance(address user) external view returns (uint256); + /** + * @notice transfer free sApe balance from 'from' to 'to', Only psApe can call this function during sApe liquidation + * @param from identify send address + * @param to identify receive address + * @param amount transfer amount + */ function transferFreeSApeBalance( address from, address to, uint256 amount ) external; - function withdrawFreeSApe(address receiver, uint128 amount) external; + /** + * @notice deposit an `amount` of free sApe. + * @param cashAsset The payment asset for the deposit. Can only be ApeCoin or cApe + * @param amount The amount of sApe to be deposit + **/ + function depositFreeSApe(address cashAsset, uint128 amount) external; + + /** + * @notice withdraw an `amount` of free sApe. + * @param receiveAsset The receive asset for the withdraw. Can only be ApeCoin or cApe + * @param amount The amount of sApe to be withdraw + **/ + function withdrawFreeSApe(address receiveAsset, uint128 amount) external; } diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index dbbb7b3f2..750c310a0 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -11,10 +11,25 @@ import {DataTypes} from "../protocol/libraries/types/DataTypes.sol"; * @notice Defines the basic interface for an ParaSpace Ape Staking Pool. **/ interface IPoolApeStaking { + /** + * @notice return ParaApeStaking contract address + */ function paraApeStaking() external view returns (address); + /** + * @notice Borrow cApe from lending pool, only ParaApeStaking contract can call this function + * @param amount Borrow amount of cApe from lending pool + */ function borrowPoolCApe(uint256 amount) external returns (uint256); + /** + * @notice Borrow ApeCoin/cApe from lending pool and stake ape in ParaApeStaking apecoin pool + * @param apeCoinDepositInfo Detail deposit info of the apecoin pool + * @param pairDepositInfo Detail deposit info of the apecoin pair pool + * @param borrowAsset address of borrowing asset, can be ApeCoin or cApe + * @param borrowAmount Borrow amount of ApeCoin/cApe from lending pool + * @dev Need check User health factor > 1. + */ function borrowAndStakingApeCoin( IParaApeStaking.ApeCoinDepositInfo[] calldata apeCoinDepositInfo, IParaApeStaking.ApeCoinPairDepositInfo[] calldata pairDepositInfo, @@ -23,6 +38,9 @@ interface IPoolApeStaking { bool openSApeCollateralFlag ) external; + /** + * @notice calculate TimeLock parameters for the specified asset, only ParaApeStaking contract can call this function + */ function calculateTimeLockParams(address asset, uint256 amount) external returns (DataTypes.TimeLockParams memory); diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 8a4053482..63ecf9e84 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -46,10 +46,12 @@ contract PoolApeStaking is return POOL_REVISION; } + /// @inheritdoc IPoolApeStaking function paraApeStaking() external view returns (address) { return PARA_APE_STAKING; } + /// @inheritdoc IPoolApeStaking function borrowPoolCApe(uint256 amount) external nonReentrant @@ -68,6 +70,7 @@ contract PoolApeStaking is return latestBorrowIndex; } + /// @inheritdoc IPoolApeStaking function calculateTimeLockParams(address asset, uint256 amount) external returns (DataTypes.TimeLockParams memory) @@ -87,6 +90,7 @@ contract PoolApeStaking is return timeLockParams; } + /// @inheritdoc IPoolApeStaking function borrowAndStakingApeCoin( IParaApeStaking.ApeCoinDepositInfo[] calldata apeCoinDepositInfo, IParaApeStaking.ApeCoinPairDepositInfo[] calldata pairDepositInfo, diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index cb814f8d8..fab7ca4d5 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -571,7 +571,7 @@ describe("Para Ape Staking Test", () => { ); await waitForTx( await paraApeStaking - .connect(user2.signer) + .connect(user3.signer) .claimPendingReward(5, [0, 1, 2]) ); let user1Balance = await cApe.balanceOf(user1.address); From c4b0cecc2916bcd8d12d0caab5245e9b474e4efc Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 4 Aug 2023 17:24:07 +0800 Subject: [PATCH 86/99] chore: fix review issue and some optimization --- .../apestaking/logic/ApeCoinPoolLogic.sol | 33 ++++++++++++------- .../logic/ApeStakingCommonLogic.sol | 31 ++++++++++++----- .../apestaking/logic/ApeStakingP2PLogic.sol | 8 ++--- .../logic/ApeStakingPairPoolLogic.sol | 15 ++++----- .../logic/ApeStakingSinglePoolLogic.sol | 26 +++++---------- contracts/interfaces/IPoolApeStaking.sol | 6 ++-- contracts/protocol/pool/PoolApeStaking.sol | 28 +++++++++++----- test/para_ape_staking.spec.ts | 6 ++-- test/para_pool_ape_staking.spec.ts | 26 +++++++++++++++ 9 files changed, 115 insertions(+), 64 deletions(-) diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index 08983c3a9..d473f44f6 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -31,7 +31,6 @@ library ApeCoinPoolLogic { event ApeCoinPoolDeposited(bool isBAYC, uint256 tokenId); event ApeCoinPoolCompounded(bool isBAYC, uint256 tokenId); - event ApeCoinPoolClaimed(bool isBAYC, uint256 tokenId, uint256 rewardShare); event ApeCoinPoolWithdrew(bool isBAYC, uint256 tokenId); event ApeCoinPairPoolDeposited( bool isBAYC, @@ -77,16 +76,15 @@ library ApeCoinPoolLogic { uint32[] calldata tokenIds ) external returns (address) { ApeStakingCommonLogic.validateTokenIdArray(tokenIds); - (address nft, address nToken) = ApeStakingCommonLogic.getNftFromPoolId( + (, address nToken) = ApeStakingCommonLogic.getNftFromPoolId( vars, poolId ); return ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, - nft, nToken, true, tokenIds @@ -291,9 +289,8 @@ library ApeCoinPoolLogic { address nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, - vars.apeToken, vars.nApe, false, withdrawInfo.tokenIds @@ -548,9 +545,8 @@ library ApeCoinPoolLogic { address nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, - vars.apeToken, vars.nApe, false, withdrawInfo.apeTokenIds @@ -771,7 +767,11 @@ library ApeCoinPoolLogic { require(cashAmount <= totalApeCoinNeeded, Errors.INVALID_CASH_AMOUNT); if (cashAmount != 0) { - IERC20(cashToken).safeTransferFrom(user, address(this), cashAmount); + IERC20(cashToken).safeTransferFrom( + msg.sender, + address(this), + cashAmount + ); } uint256 cApeWithdrawAmount = (cashToken == vars.apeCoin) @@ -818,9 +818,13 @@ library ApeCoinPoolLogic { : cashAmount; IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; if (cashAmount < totalApeCoinWithdrew) { + if (vars.cApeExchangeRate == 0) { + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + } uint256 freeSApeBalanceAdded = totalApeCoinWithdrew - cashAmount; - uint256 freeShareBalanceAdded = ICApe(vars.cApe) - .getShareByPooledApe(freeSApeBalanceAdded); + uint256 freeShareBalanceAdded = freeSApeBalanceAdded.rayDiv(vars.cApeExchangeRate); sApeBalanceCache.freeShareBalance += freeShareBalanceAdded .toUint128(); cApeDepositAmount += freeSApeBalanceAdded; @@ -848,9 +852,14 @@ library ApeCoinPoolLogic { uint256 rewardAmount, uint24 totalPosition ) internal { + if (vars.cApeExchangeRate == 0) { + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + } IAutoCompoundApe(vars.cApe).deposit(address(this), rewardAmount); - uint256 cApeShare = ICApe(vars.cApe).getShareByPooledApe(rewardAmount); + uint256 cApeShare = rewardAmount.rayDiv(vars.cApeExchangeRate); uint256 compoundFee = cApeShare; if (totalPosition != 0) { compoundFee = cApeShare.percentMul(vars.compoundFee); diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 723a76e93..7edd87e96 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -24,9 +24,8 @@ library ApeStakingCommonLogic { event PoolRewardClaimed( uint256 poolId, - address nft, uint256 tokenId, - uint256 rewardShare + uint256 rewardAmount ); /** @@ -77,6 +76,11 @@ library ApeStakingCommonLogic { IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint256 positionCap ) internal returns (uint256, uint256) { + if (vars.cApeExchangeRate == 0) { + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + } uint256 cApeDebtShare = poolState.cApeDebtShare; uint256 debtInterest = calculateCurrentPositionDebtInterest( cApeDebtShare, @@ -170,14 +174,19 @@ library ApeStakingCommonLogic { IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, uint256 poolId, - address nft, address nToken, bool needUpdateStatus, uint32[] memory tokenIds ) internal returns (address) { + if (vars.cApeExchangeRate == 0) { + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + } + address claimFor; uint256 totalRewardShares; - uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + vars.accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; for (uint256 index = 0; index < tokenIds.length; index++) { uint32 tokenId = tokenIds[index]; @@ -198,23 +207,27 @@ library ApeStakingCommonLogic { require(tokenStatus.isInPool, Errors.NFT_NOT_IN_POOL); //update reward, to save gas we don't claim pending reward in ApeCoinStaking. - uint256 rewardShare = accumulatedRewardsPerNft - + uint256 rewardShare = vars.accumulatedRewardsPerNft - tokenStatus.rewardsDebt; totalRewardShares += rewardShare; if (needUpdateStatus) { poolState .tokenStatus[tokenId] - .rewardsDebt = accumulatedRewardsPerNft; + .rewardsDebt = vars.accumulatedRewardsPerNft; } //emit event - emit PoolRewardClaimed(poolId, nft, tokenId, rewardShare); + emit PoolRewardClaimed( + poolId, + tokenId, + rewardShare.rayMul(vars.cApeExchangeRate) + ); } if (totalRewardShares > 0) { - uint256 pendingReward = ICApe(vars.cApe).getPooledApeByShares( - totalRewardShares + uint256 pendingReward = totalRewardShares.rayMul( + vars.cApeExchangeRate ); IERC20(vars.cApe).safeTransfer(claimFor, pendingReward); } diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index 8a1313d6d..fa3a476db 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -316,7 +316,7 @@ library ApeStakingP2PLogic { uint256 balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); uint256 withdrawAmount = balanceAfter - balanceBefore; - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WAD); + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WadRayMath.RAY); if (withdrawAmount > apeCoinCap) { uint256 _compoundFeeShare = _distributeReward( cApeShareBalance, @@ -376,7 +376,7 @@ library ApeStakingP2PLogic { bytes32[] memory orderHashes ) public { //ignore getShareByPooledApe return 0 case. - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WAD); + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WadRayMath.RAY); uint256 totalReward; uint256 totalFeeShare; uint256 orderCounts = orderHashes.length; @@ -477,7 +477,7 @@ library ApeStakingP2PLogic { uint256 cApeExchangeRate, uint256 rewardAmount ) internal returns (uint256) { - uint256 rewardShare = rewardAmount.wadDiv(cApeExchangeRate); + uint256 rewardShare = rewardAmount.rayDiv(cApeExchangeRate); //compound fee uint256 _compoundFeeShare = rewardShare.percentMul(vars.compoundFee); rewardShare -= _compoundFeeShare; @@ -626,7 +626,7 @@ library ApeStakingP2PLogic { uint256 apeCoinAmount, uint256 cApeExchangeRate ) internal { - uint256 freeSApeBalanceAdded = apeCoinAmount.wadDiv(cApeExchangeRate); + uint256 freeSApeBalanceAdded = apeCoinAmount.rayDiv(cApeExchangeRate); IParaApeStaking.SApeBalance memory sApeBalanceCache = sApeBalance[user]; sApeBalanceCache.freeShareBalance += freeSApeBalanceAdded.toUint128(); sApeBalanceCache.stakedBalance -= apeCoinAmount.toUint128(); diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 2074c76a5..eb3546af7 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -224,11 +224,10 @@ library ApeStakingPairPoolLogic { vars.nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, isBAYC ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID, - vars.apeToken, vars.nApe, false, apeTokenIds @@ -255,6 +254,12 @@ library ApeStakingPairPoolLogic { } } + // check pair status + require( + poolState.tokenStatus[apeTokenId].bakcTokenId == bakcTokenId, + Errors.NOT_PAIRED_APE_AND_BAKC + ); + // update pair status delete poolState.tokenStatus[apeTokenId]; @@ -313,9 +318,6 @@ library ApeStakingPairPoolLogic { vars.totalClaimedApe ); - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); vars.latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic @@ -421,9 +423,6 @@ library ApeStakingPairPoolLogic { //repay and compound vars.positionCap = isBAYC ? vars.baycMatchedCap : vars.maycMatchedCap; - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); vars.latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 50b66e8cb..6af4c9fc2 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -279,9 +279,6 @@ library ApeStakingSinglePoolLogic { ); //repay and compound - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); vars.latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); (vars.totalRepay, vars.totalCompoundFee) = ApeStakingCommonLogic @@ -363,9 +360,6 @@ library ApeStakingSinglePoolLogic { ); //repay and compound - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); vars.latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); ( @@ -408,9 +402,8 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage poolState = poolStates[poolId]; address nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, - nft, nToken, false, tokenIds @@ -471,12 +464,11 @@ library ApeStakingSinglePoolLogic { ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, - nft, nToken, true, - tokenIds + singlePoolTokenIds ); } } @@ -549,9 +541,6 @@ library ApeStakingSinglePoolLogic { } if (singleStakingCount > 0 || pairStakingCount > 0) { - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); vars.latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); } @@ -684,9 +673,6 @@ library ApeStakingSinglePoolLogic { mstore(maycPair, maycPairCount) } - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( - WadRayMath.RAY - ); vars.latestBorrowIndex = IPool(vars.pool) .getReserveNormalizedVariableDebt(vars.cApe); @@ -723,6 +709,12 @@ library ApeStakingSinglePoolLogic { mapping(uint256 => IParaApeStaking.PoolState) storage poolStates, IParaApeStaking.ApeStakingVaultCacheVars memory vars ) internal returns (uint256, uint256) { + if (vars.cApeExchangeRate == 0) { + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); + } + IParaApeStaking.PoolState storage bakcPoolState = poolStates[ ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID ]; diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 750c310a0..e2606e0b6 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -26,14 +26,16 @@ interface IPoolApeStaking { * @notice Borrow ApeCoin/cApe from lending pool and stake ape in ParaApeStaking apecoin pool * @param apeCoinDepositInfo Detail deposit info of the apecoin pool * @param pairDepositInfo Detail deposit info of the apecoin pair pool - * @param borrowAsset address of borrowing asset, can be ApeCoin or cApe + * @param asset address of deposit asset, can be ApeCoin or cApe + * @param cashAmount deposit amount from user wallet * @param borrowAmount Borrow amount of ApeCoin/cApe from lending pool * @dev Need check User health factor > 1. */ function borrowAndStakingApeCoin( IParaApeStaking.ApeCoinDepositInfo[] calldata apeCoinDepositInfo, IParaApeStaking.ApeCoinPairDepositInfo[] calldata pairDepositInfo, - address borrowAsset, + address asset, + uint256 cashAmount, uint256 borrowAmount, bool openSApeCollateralFlag ) external; diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 63ecf9e84..4194f0252 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -94,32 +94,39 @@ contract PoolApeStaking is function borrowAndStakingApeCoin( IParaApeStaking.ApeCoinDepositInfo[] calldata apeCoinDepositInfo, IParaApeStaking.ApeCoinPairDepositInfo[] calldata pairDepositInfo, - address borrowAsset, + address asset, + uint256 cashAmount, uint256 borrowAmount, bool openSApeCollateralFlag ) external nonReentrant { require( - borrowAsset == APE_COIN || borrowAsset == APE_COMPOUND, + asset == APE_COIN || asset == APE_COMPOUND, Errors.INVALID_ASSET_TYPE ); DataTypes.PoolStorage storage ps = poolStorage(); address msgSender = msg.sender; - // 1, prepare borrow asset. + uint256 balanceBefore = IERC20(asset).balanceOf(address(this)); + // 1, prepare cash part. + if (cashAmount > 0) { + IERC20(asset).transferFrom(msg.sender, address(this), cashAmount); + } + + // 2, prepare borrow part. if (borrowAmount > 0) { DataTypes.ReserveData storage borrowAssetReserve = ps._reserves[ - borrowAsset + asset ]; // no time lock needed here DataTypes.TimeLockParams memory timeLockParams; IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( - msgSender, + address(this), borrowAmount, timeLockParams ); } - // 2, stake + // 3, stake uint256 arrayLength = apeCoinDepositInfo.length; for (uint256 index = 0; index < arrayLength; index++) { IParaApeStaking.ApeCoinDepositInfo @@ -143,7 +150,7 @@ contract PoolApeStaking is ); } - // 3, check if need to collateralize sAPE + // 4, check if need to collateralize sAPE if (openSApeCollateralFlag) { DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ msgSender @@ -156,14 +163,14 @@ contract PoolApeStaking is ); } - // 4, execute borrow + // 5, execute borrow if (borrowAmount > 0) { BorrowLogic.executeBorrow( ps._reserves, ps._reservesList, ps._usersConfig[msgSender], DataTypes.ExecuteBorrowParams({ - asset: borrowAsset, + asset: asset, user: msgSender, onBehalfOf: msgSender, amount: borrowAmount, @@ -176,5 +183,8 @@ contract PoolApeStaking is }) ); } + + uint256 balanceAfter = IERC20(asset).balanceOf(address(this)); + require(balanceAfter == balanceBefore, Errors.INVALID_PARAMETER); } } diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index fab7ca4d5..db2816e04 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -836,9 +836,9 @@ describe("Para Ape Staking Test", () => { .transferFrom(user1.address, user2.address, 2) ); - // await expect( - // paraApeStaking.connect(user1.signer).withdrawPairNFT(true, [0, 1], [1, 0]) - // ).to.be.revertedWith(ProtocolErrors.NOT_PAIRED_APE_AND_BAKC); + await expect( + paraApeStaking.connect(user1.signer).withdrawPairNFT(true, [0, 1], [1, 0]) + ).to.be.revertedWith(ProtocolErrors.NOT_PAIRED_APE_AND_BAKC); await expect( paraApeStaking diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 3e9de5a1f..f3e2a4934 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -136,8 +136,26 @@ describe("Para Ape staking ape coin pool test", () => { apeCoinStaking, pool, protocolDataProvider, + poolAdmin, } = await loadFixture(fixture); + await waitForTx( + await pool + .connect(poolAdmin.signer) + .unlimitedApproveTo(ape.address, paraApeStaking.address) + ); + await waitForTx( + await pool + .connect(poolAdmin.signer) + .unlimitedApproveTo(cApe.address, paraApeStaking.address) + ); + await waitForTx( + await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + //prepare user3 asset await mintAndValidate(ape, "20000000", user4); await waitForTx( @@ -217,11 +235,16 @@ describe("Para Ape staking ape coin pool test", () => { }, ], ape.address, + parseEther("100000"), parseEther("150000"), true ) ).to.be.revertedWith(ProtocolErrors.COLLATERAL_CANNOT_COVER_NEW_BORROW); + await mintAndValidate(ape, "20000000", user2); + await waitForTx( + await ape.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); await expect( pool.connect(user2.signer).borrowAndStakingApeCoin( [ @@ -244,6 +267,7 @@ describe("Para Ape staking ape coin pool test", () => { }, ], ape.address, + parseEther("100000"), parseEther("150000"), true ) @@ -276,6 +300,7 @@ describe("Para Ape staking ape coin pool test", () => { }, ], ape.address, + parseEther("100000"), parseEther("150000"), true ) @@ -304,6 +329,7 @@ describe("Para Ape staking ape coin pool test", () => { }, ], cApe.address, + parseEther("0"), parseEther("150000"), true ) From cbdf4be5b3cbb9531c2f066add567be7789c9f50 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 7 Aug 2023 17:20:35 +0800 Subject: [PATCH 87/99] chore: unstake user ape staking position when hf < 1 --- contracts/apestaking/ParaApeStaking.sol | 2 +- .../apestaking/logic/ApeCoinPoolLogic.sol | 10 +- .../logic/ApeStakingCommonLogic.sol | 5 +- .../apestaking/logic/ApeStakingP2PLogic.sol | 8 +- .../logic/ApeStakingPairPoolLogic.sol | 2 +- .../logic/ApeStakingSinglePoolLogic.sol | 4 +- contracts/interfaces/IParaApeStaking.sol | 5 + .../tokenization/NTokenApeStaking.sol | 37 ++ test/para_pool_ape_staking.spec.ts | 405 ++++++++++++------ 9 files changed, 327 insertions(+), 151 deletions(-) diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 645cbec5c..546a1caf2 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -47,7 +47,7 @@ contract ParaApeStaking is uint256 private immutable maycMatchedCap; uint256 private immutable bakcMatchedCap; IACLManager private immutable aclManager; - uint16 private immutable sApeReserveId; + uint16 public immutable sApeReserveId; address private immutable psApe; //record all pool states diff --git a/contracts/apestaking/logic/ApeCoinPoolLogic.sol b/contracts/apestaking/logic/ApeCoinPoolLogic.sol index d473f44f6..923730089 100644 --- a/contracts/apestaking/logic/ApeCoinPoolLogic.sol +++ b/contracts/apestaking/logic/ApeCoinPoolLogic.sol @@ -83,7 +83,7 @@ library ApeCoinPoolLogic { return ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, nToken, true, @@ -289,7 +289,7 @@ library ApeCoinPoolLogic { address nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, vars.nApe, false, @@ -545,7 +545,7 @@ library ApeCoinPoolLogic { address nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, vars.nApe, false, @@ -824,7 +824,9 @@ library ApeCoinPoolLogic { ); } uint256 freeSApeBalanceAdded = totalApeCoinWithdrew - cashAmount; - uint256 freeShareBalanceAdded = freeSApeBalanceAdded.rayDiv(vars.cApeExchangeRate); + uint256 freeShareBalanceAdded = freeSApeBalanceAdded.rayDiv( + vars.cApeExchangeRate + ); sApeBalanceCache.freeShareBalance += freeShareBalanceAdded .toUint128(); cApeDepositAmount += freeSApeBalanceAdded; diff --git a/contracts/apestaking/logic/ApeStakingCommonLogic.sol b/contracts/apestaking/logic/ApeStakingCommonLogic.sol index 7edd87e96..f72ae4bf0 100644 --- a/contracts/apestaking/logic/ApeStakingCommonLogic.sol +++ b/contracts/apestaking/logic/ApeStakingCommonLogic.sol @@ -212,9 +212,8 @@ library ApeStakingCommonLogic { totalRewardShares += rewardShare; if (needUpdateStatus) { - poolState - .tokenStatus[tokenId] - .rewardsDebt = vars.accumulatedRewardsPerNft; + poolState.tokenStatus[tokenId].rewardsDebt = vars + .accumulatedRewardsPerNft; } //emit event diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index fa3a476db..f057f37dd 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -316,7 +316,9 @@ library ApeStakingP2PLogic { uint256 balanceAfter = IERC20(vars.apeCoin).balanceOf(address(this)); uint256 withdrawAmount = balanceAfter - balanceBefore; - vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WadRayMath.RAY); + vars.cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); if (withdrawAmount > apeCoinCap) { uint256 _compoundFeeShare = _distributeReward( cApeShareBalance, @@ -376,7 +378,9 @@ library ApeStakingP2PLogic { bytes32[] memory orderHashes ) public { //ignore getShareByPooledApe return 0 case. - uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares(WadRayMath.RAY); + uint256 cApeExchangeRate = ICApe(vars.cApe).getPooledApeByShares( + WadRayMath.RAY + ); uint256 totalReward; uint256 totalFeeShare; uint256 orderCounts = orderHashes.length; diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index eb3546af7..5166e562c 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -224,7 +224,7 @@ library ApeStakingPairPoolLogic { vars.nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, isBAYC ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID : ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID, diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index 6af4c9fc2..354b61859 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -402,7 +402,7 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage poolState = poolStates[poolId]; address nApeOwner = ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, nToken, false, @@ -464,7 +464,7 @@ library ApeStakingSinglePoolLogic { ApeStakingCommonLogic.claimPendingReward( poolState, - vars, + vars, poolId, nToken, true, diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index 71aaa7592..3848b0750 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -87,6 +87,11 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { **/ event CompoundFeeUpdated(uint64 oldValue, uint64 newValue); + /** + * @notice Query sApe reserve Id used by ParaApeStaking + */ + function sApeReserveId() external view returns(uint16); + /** * @notice Query token status for the specified pool and nft * @param poolId Identify pool diff --git a/contracts/protocol/tokenization/NTokenApeStaking.sol b/contracts/protocol/tokenization/NTokenApeStaking.sol index 514aeabb9..45b2ae3b8 100644 --- a/contracts/protocol/tokenization/NTokenApeStaking.sol +++ b/contracts/protocol/tokenization/NTokenApeStaking.sol @@ -6,8 +6,10 @@ import {IPool} from "../../interfaces/IPool.sol"; import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; import {IRewardController} from "../../interfaces/IRewardController.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; +import {UserConfiguration} from "../libraries/configuration/UserConfiguration.sol"; import "../../interfaces/IParaApeStaking.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import "../libraries/helpers/Errors.sol"; /** * @title ApeCoinStaking NToken @@ -16,6 +18,13 @@ import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; */ abstract contract NTokenApeStaking is NToken { using SafeCast for uint256; + using UserConfiguration for DataTypes.UserConfigurationMap; + + /** + * @dev Minimum health factor to consider a user position healthy + * A value of 1e18 results in 1 + */ + uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18; IParaApeStaking immutable paraApeStaking; @@ -76,4 +85,32 @@ abstract contract NTokenApeStaking is NToken { } super._transfer(from, to, tokenId, validate); } + + function unstakeApeStakingPosition(address user, uint32[] calldata tokenIds) + external + nonReentrant + { + uint256 arrayLength = tokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + require(user == ownerOf(tokenId), Errors.NOT_THE_OWNER); + } + + DataTypes.UserConfigurationMap memory userConfig = POOL + .getUserConfiguration(user); + uint16 sApeReserveId = paraApeStaking.sApeReserveId(); + bool usageAsCollateralEnabled = userConfig.isUsingAsCollateral( + sApeReserveId + ); + if (usageAsCollateralEnabled && userConfig.isBorrowingAny()) { + (, , , , , uint256 healthFactor, ) = POOL.getUserAccountData(user); + //need to check user health factor + require( + healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD, + Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD + ); + } + + paraApeStaking.nApeOwnerChangeCallback(isBayc(), tokenIds); + } } diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index f3e2a4934..89f100558 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -890,7 +890,136 @@ describe("Para Ape staking ape coin pool test", () => { ); }); - it("sApe test0: Ape coin pool sApe", async () => { + it("sApe test0: unstake sApe when user hf < 1", async () => { + const { + users: [user1, user2, liquidator], + ape, + bayc, + mayc, + bakc, + pool, + nBAYC, + nMAYC, + } = await loadFixture(fixture); + + await changePriceAndValidate(bayc, "100"); + await changePriceAndValidate(mayc, "50"); + await changePriceAndValidate(bakc, "25"); + await changePriceAndValidate(ape, "0.00001"); + + //user1 collateral 200eth + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(bakc, "2", user1, true); + + await supplyAndValidate(ape, "2000000", user2, true); + + //user1 borrow value 0.00001 * 1000000 = 10eth + await waitForTx( + await pool + .connect(user1.signer) + .borrow(ape.address, parseEther("1000000"), 0, user1.address) + ); + expect(await ape.balanceOf(user1.address)).to.be.equal( + parseEther("1000000") + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + tokenIds: [0], + }) + ); + await waitForTx( + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: false, + apeTokenIds: [0], + bakcTokenIds: [1], + }) + ); + + await waitForTx( + await pool + .connect(user1.signer) + .setUserUseERC20AsCollateral(sApeAddress, true) + ); + + expect(await ape.balanceOf(user1.address)).to.be.equal( + parseEther("600000") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.equal( + parseEther("400000") + ); + expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.equal( + parseEther("400000") + ); + expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.equal( + parseEther("0") + ); + + await expect( + nBAYC + .connect(liquidator.signer) + .unstakeApeStakingPosition(user2.address, [0]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + await expect( + nBAYC + .connect(liquidator.signer) + .unstakeApeStakingPosition(user1.address, [0]) + ).to.be.revertedWith(ProtocolErrors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD); + + //user1 borrow value = 200 eth + await changePriceAndValidate(ape, "0.002"); + await changeSApePriceAndValidate(sApeAddress, "0.002"); + + await waitForTx( + await nBAYC + .connect(liquidator.signer) + .unstakeApeStakingPosition(user1.address, [0]) + ); + await waitForTx( + await nMAYC + .connect(liquidator.signer) + .unstakeApeStakingPosition(user1.address, [0]) + ); + + expect(await pSApeCoin.balanceOf(user1.address)).to.be.equal( + parseEther("400000") + ); + expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.equal( + parseEther("0") + ); + expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.equal( + parseEther("400000") + ); + }); + + it("sApe test1: Ape coin pool sApe liquidation", async () => { const { users: [user1, user2, liquidator], ape, @@ -917,63 +1046,63 @@ describe("Para Ape staking ape coin pool test", () => { //user1 borrow value 0.00001 * 1000000 = 10eth await waitForTx( - await pool - .connect(user1.signer) - .borrow(ape.address, parseEther("1000000"), 0, user1.address) + await pool + .connect(user1.signer) + .borrow(ape.address, parseEther("1000000"), 0, user1.address) ); expect(await ape.balanceOf(user1.address)).to.be.equal( - parseEther("1000000") + parseEther("1000000") ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("200000"), - isBAYC: true, - tokenIds: [0], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("50000"), - isBAYC: true, - apeTokenIds: [0], - bakcTokenIds: [0], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("100000"), - isBAYC: false, - tokenIds: [0], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + tokenIds: [0], + }) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("50000"), - isBAYC: false, - apeTokenIds: [0], - bakcTokenIds: [1], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: false, + apeTokenIds: [0], + bakcTokenIds: [1], + }) ); expect(await ape.balanceOf(user1.address)).to.be.equal( - parseEther("600000") + parseEther("600000") ); expect(await pSApeCoin.balanceOf(user1.address)).to.be.equal( - parseEther("400000") + parseEther("400000") ); expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.equal( - parseEther("400000") + parseEther("400000") ); expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.equal( - parseEther("0") + parseEther("0") ); //user1 borrow value = 200 eth @@ -982,97 +1111,97 @@ describe("Para Ape staking ape coin pool test", () => { await mintAndValidate(weth, "200", liquidator); await waitForTx( - await weth - .connect(liquidator.signer) - .approve(pool.address, MAX_UINT_AMOUNT) + await weth + .connect(liquidator.signer) + .approve(pool.address, MAX_UINT_AMOUNT) ); // start auction await waitForTx( - await pool - .connect(liquidator.signer) - .startAuction(user1.address, bayc.address, 0) + await pool + .connect(liquidator.signer) + .startAuction(user1.address, bayc.address, 0) ); let auctionData = await pool.getAuctionData(nBAYC.address, 0); await advanceBlock( - auctionData.startTime - .add(auctionData.tickLength.mul(BigNumber.from(40))) - .toNumber() + auctionData.startTime + .add(auctionData.tickLength.mul(BigNumber.from(40))) + .toNumber() ); // try to liquidate the NFT expect( - await pool - .connect(liquidator.signer) - .liquidateERC721( - bayc.address, - user1.address, - 0, - parseEther("100"), - true, - {gasLimit: 5000000} - ) + await pool + .connect(liquidator.signer) + .liquidateERC721( + bayc.address, + user1.address, + 0, + parseEther("100"), + true, + {gasLimit: 5000000} + ) ); expect(await pSApeCoin.balanceOf(user1.address)).to.be.closeTo( - parseEther("400000"), - parseEther("1") + parseEther("400000"), + parseEther("1") ); expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.closeTo( - parseEther("150000"), - parseEther("1") + parseEther("150000"), + parseEther("1") ); expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.closeTo( - parseEther("250000"), - parseEther("1") + parseEther("250000"), + parseEther("1") ); await waitForTx( - await pool - .connect(liquidator.signer) - .startAuction(user1.address, mayc.address, 0) + await pool + .connect(liquidator.signer) + .startAuction(user1.address, mayc.address, 0) ); auctionData = await pool.getAuctionData(nMAYC.address, 0); await advanceBlock( - auctionData.startTime - .add(auctionData.tickLength.mul(BigNumber.from(40))) - .toNumber() + auctionData.startTime + .add(auctionData.tickLength.mul(BigNumber.from(40))) + .toNumber() ); expect( - await pool - .connect(liquidator.signer) - .liquidateERC721( - mayc.address, - user1.address, - 0, - parseEther("50"), - true, - {gasLimit: 5000000} - ) + await pool + .connect(liquidator.signer) + .liquidateERC721( + mayc.address, + user1.address, + 0, + parseEther("50"), + true, + {gasLimit: 5000000} + ) ); expect(await pSApeCoin.balanceOf(user1.address)).to.be.closeTo( - parseEther("400000"), - parseEther("1") + parseEther("400000"), + parseEther("1") ); expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.closeTo( - parseEther("0"), - parseEther("1") + parseEther("0"), + parseEther("1") ); expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.closeTo( - parseEther("400000"), - parseEther("1") + parseEther("400000"), + parseEther("1") ); const accountData0 = await pool.getUserAccountData(user1.address); await waitForTx( - await pool - .connect(user1.signer) - .setUserUseERC20AsCollateral(sApeAddress, true) + await pool + .connect(user1.signer) + .setUserUseERC20AsCollateral(sApeAddress, true) ); const accountData1 = await pool.getUserAccountData(user1.address); //400000 * 0.0002 = 80 expect( - accountData1.totalCollateralBase.sub(accountData0.totalCollateralBase) + accountData1.totalCollateralBase.sub(accountData0.totalCollateralBase) ).to.be.closeTo(parseEther("80"), parseEther("1")); await changePriceAndValidate(ape, "0.0004"); @@ -1081,32 +1210,32 @@ describe("Para Ape staking ape coin pool test", () => { //liquidate sApe await mintAndValidate(ape, "1000000", liquidator); await waitForTx( - await ape - .connect(liquidator.signer) - .approve(pool.address, MAX_UINT_AMOUNT) + await ape + .connect(liquidator.signer) + .approve(pool.address, MAX_UINT_AMOUNT) ); await waitForTx( - await pool - .connect(liquidator.signer) - .liquidateERC20( - sApeAddress, - ape.address, - user1.address, - parseEther("400000"), - true - ) + await pool + .connect(liquidator.signer) + .liquidateERC20( + sApeAddress, + ape.address, + user1.address, + parseEther("400000"), + true + ) ); const user1Balance = await pSApeCoin.balanceOf(user1.address); const liquidatorBalance = await pSApeCoin.balanceOf(liquidator.address); expect(user1Balance).to.be.closeTo("0", parseEther("1")); expect(liquidatorBalance).to.be.closeTo( - parseEther("400000"), - parseEther("1") + parseEther("400000"), + parseEther("1") ); }); - it("sApe test1", async () => { + it("sApe test2: sApe deposit and withdraw", async () => { const { users: [user1], ape, @@ -1114,75 +1243,75 @@ describe("Para Ape staking ape coin pool test", () => { await mintAndValidate(ape, "1000000", user1); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .depositFreeSApe(ape.address, parseEther("1000000")) + await paraApeStaking + .connect(user1.signer) + .depositFreeSApe(ape.address, parseEther("1000000")) ); expect(await ape.balanceOf(user1.address)).to.be.equal("0"); expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .withdrawFreeSApe(ape.address, parseEther("1000000")) + await paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(ape.address, parseEther("1000000")) ); expect(await ape.balanceOf(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( - "0", - parseEther("1") + "0", + parseEther("1") ); await waitForTx( - await ape.connect(user1.signer).approve(cApe.address, MAX_UINT_AMOUNT) + await ape.connect(user1.signer).approve(cApe.address, MAX_UINT_AMOUNT) ); await waitForTx( - await cApe - .connect(user1.signer) - .deposit(user1.address, parseEther("1000000")) + await cApe + .connect(user1.signer) + .deposit(user1.address, parseEther("1000000")) ); expect(await ape.balanceOf(user1.address)).to.be.equal("0"); expect(await cApe.balanceOf(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); await waitForTx( - await cApe - .connect(user1.signer) - .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + await cApe + .connect(user1.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .depositFreeSApe(cApe.address, parseEther("1000000")) + await paraApeStaking + .connect(user1.signer) + .depositFreeSApe(cApe.address, parseEther("1000000")) ); expect(await cApe.balanceOf(user1.address)).to.be.equal("0"); expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .withdrawFreeSApe(cApe.address, parseEther("1000000")) + await paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(cApe.address, parseEther("1000000")) ); expect(await cApe.balanceOf(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( - "0", - parseEther("1") + "0", + parseEther("1") ); }); From eaf96e2af22ac96ca99af37df0674c040ceb98a4 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 7 Aug 2023 22:19:51 +0800 Subject: [PATCH 88/99] chore: fix review issue --- .../apestaking/logic/ApeStakingP2PLogic.sol | 19 +- contracts/interfaces/IParaApeStaking.sol | 2 +- .../protocol/libraries/helpers/Errors.sol | 2 +- helpers/types.ts | 2 +- test/p2p_pair_staking.spec.ts | 48 ++- test/para_pool_ape_staking.spec.ts | 384 +++++++++--------- 6 files changed, 249 insertions(+), 208 deletions(-) diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index f057f37dd..181e512b2 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -79,8 +79,12 @@ library ApeStakingP2PLogic { //2 check if orders can match require( - apeOrder.stakingType == IApeStakingP2P.StakingType.MAYCStaking || - apeOrder.stakingType == IApeStakingP2P.StakingType.BAYCStaking, + (apeOrder.token == vars.mayc && + apeOrder.stakingType == + IApeStakingP2P.StakingType.MAYCStaking) || + (apeOrder.token == vars.bayc && + apeOrder.stakingType == + IApeStakingP2P.StakingType.BAYCStaking), Errors.INVALID_STAKING_TYPE ); require( @@ -523,9 +527,9 @@ library ApeStakingP2PLogic { orderHash = getListingOrderHash(listingOrder); require( - listingOrderStatus[orderHash] != - IApeStakingP2P.ListingOrderStatus.Cancelled, - Errors.ORDER_ALREADY_CANCELLED + listingOrderStatus[orderHash] == + IApeStakingP2P.ListingOrderStatus.Pending, + Errors.INVALID_ORDER_STATUS ); if (msg.sender != listingOrder.offerer) { @@ -577,11 +581,6 @@ library ApeStakingP2PLogic { apeCoinOrder.token == DataTypes.SApeAddress, Errors.INVALID_TOKEN ); - require( - listingOrderStatus[orderHash] != - IApeStakingP2P.ListingOrderStatus.Matched, - Errors.ORDER_ALREADY_MATCHED - ); } function _validateBakcOrder( diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index 3848b0750..099ad8a9d 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -90,7 +90,7 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { /** * @notice Query sApe reserve Id used by ParaApeStaking */ - function sApeReserveId() external view returns(uint16); + function sApeReserveId() external view returns (uint16); /** * @notice Query token status for the specified pool and nft diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index 49e3ab415..9aef1f67d 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -149,7 +149,7 @@ library Errors { string public constant ORDER_NOT_STARTED = "182"; //order not started string public constant ORDER_EXPIRED = "183"; //order expired string public constant INVALID_TOKEN = "184"; //invalid token - string public constant ORDER_ALREADY_MATCHED = "185"; //order matched + string public constant INVALID_ORDER_STATUS = "185"; //invalid order status string public constant INVALID_STAKING_TYPE = "186"; //invalid stake type string public constant ORDER_TYPE_MATCH_FAILED = "187"; //orders type match failed string public constant ORDER_SHARE_MATCH_FAILED = "188"; //orders share match failed diff --git a/helpers/types.ts b/helpers/types.ts index 904669aaa..885399318 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -434,7 +434,7 @@ export enum ProtocolErrors { ORDER_NOT_STARTED = "182", //order not started ORDER_EXPIRED = "183", //order expired INVALID_TOKEN = "184", //invalid token - ORDER_ALREADY_MATCHED = "185", //order matched + INVALID_ORDER_STATUS = "185", //invalid order status INVALID_STAKING_TYPE = "186", //invalid stake type ORDER_TYPE_MATCH_FAILED = "187", //orders type match failed ORDER_SHARE_MATCH_FAILED = "188", //orders share match failed diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index 4ed09002d..748ee7484 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -667,7 +667,7 @@ describe("P2P Pair Staking Test", () => { paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith(ProtocolErrors.ORDER_ALREADY_CANCELLED); + ).to.be.revertedWith(ProtocolErrors.INVALID_ORDER_STATUS); }); it("match failed when order was canceled 1", async () => { @@ -724,7 +724,7 @@ describe("P2P Pair Staking Test", () => { user2SignedOrder, user3SignedOrder ) - ).to.be.revertedWith(ProtocolErrors.ORDER_ALREADY_CANCELLED); + ).to.be.revertedWith(ProtocolErrors.INVALID_ORDER_STATUS); }); it("match failed when orders type match failed 0", async () => { @@ -1145,7 +1145,7 @@ describe("P2P Pair Staking Test", () => { paraApeStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder1, user2SignedOrder) - ).to.be.revertedWith(ProtocolErrors.ORDER_ALREADY_MATCHED); + ).to.be.revertedWith(ProtocolErrors.INVALID_ORDER_STATUS); await waitForTx( await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) @@ -1362,4 +1362,46 @@ describe("P2P Pair Staking Test", () => { await paraApeStaking.freeSApeBalance(liquidator.address) ).to.be.closeTo(apeAmount, parseEther("1")); }); + + it("test invalid order", async () => { + const { + users: [user1, user2], + ape, + bayc, + mayc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(mayc, "1", user1, true); + + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + mayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ).to.be.revertedWith(ProtocolErrors.INVALID_STAKING_TYPE); + }); }); diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 89f100558..1f08b91c2 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -916,81 +916,81 @@ describe("Para Ape staking ape coin pool test", () => { //user1 borrow value 0.00001 * 1000000 = 10eth await waitForTx( - await pool - .connect(user1.signer) - .borrow(ape.address, parseEther("1000000"), 0, user1.address) + await pool + .connect(user1.signer) + .borrow(ape.address, parseEther("1000000"), 0, user1.address) ); expect(await ape.balanceOf(user1.address)).to.be.equal( - parseEther("1000000") + parseEther("1000000") ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("200000"), - isBAYC: true, - tokenIds: [0], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("50000"), - isBAYC: true, - apeTokenIds: [0], - bakcTokenIds: [0], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("100000"), - isBAYC: false, - tokenIds: [0], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + tokenIds: [0], + }) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("50000"), - isBAYC: false, - apeTokenIds: [0], - bakcTokenIds: [1], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: false, + apeTokenIds: [0], + bakcTokenIds: [1], + }) ); await waitForTx( - await pool - .connect(user1.signer) - .setUserUseERC20AsCollateral(sApeAddress, true) + await pool + .connect(user1.signer) + .setUserUseERC20AsCollateral(sApeAddress, true) ); expect(await ape.balanceOf(user1.address)).to.be.equal( - parseEther("600000") + parseEther("600000") ); expect(await pSApeCoin.balanceOf(user1.address)).to.be.equal( - parseEther("400000") + parseEther("400000") ); expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.equal( - parseEther("400000") + parseEther("400000") ); expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.equal( - parseEther("0") + parseEther("0") ); await expect( - nBAYC - .connect(liquidator.signer) - .unstakeApeStakingPosition(user2.address, [0]) + nBAYC + .connect(liquidator.signer) + .unstakeApeStakingPosition(user2.address, [0]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); await expect( - nBAYC - .connect(liquidator.signer) - .unstakeApeStakingPosition(user1.address, [0]) + nBAYC + .connect(liquidator.signer) + .unstakeApeStakingPosition(user1.address, [0]) ).to.be.revertedWith(ProtocolErrors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD); //user1 borrow value = 200 eth @@ -998,24 +998,24 @@ describe("Para Ape staking ape coin pool test", () => { await changeSApePriceAndValidate(sApeAddress, "0.002"); await waitForTx( - await nBAYC - .connect(liquidator.signer) - .unstakeApeStakingPosition(user1.address, [0]) + await nBAYC + .connect(liquidator.signer) + .unstakeApeStakingPosition(user1.address, [0]) ); await waitForTx( - await nMAYC - .connect(liquidator.signer) - .unstakeApeStakingPosition(user1.address, [0]) + await nMAYC + .connect(liquidator.signer) + .unstakeApeStakingPosition(user1.address, [0]) ); expect(await pSApeCoin.balanceOf(user1.address)).to.be.equal( - parseEther("400000") + parseEther("400000") ); expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.equal( - parseEther("0") + parseEther("0") ); expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.equal( - parseEther("400000") + parseEther("400000") ); }); @@ -1046,63 +1046,63 @@ describe("Para Ape staking ape coin pool test", () => { //user1 borrow value 0.00001 * 1000000 = 10eth await waitForTx( - await pool - .connect(user1.signer) - .borrow(ape.address, parseEther("1000000"), 0, user1.address) + await pool + .connect(user1.signer) + .borrow(ape.address, parseEther("1000000"), 0, user1.address) ); expect(await ape.balanceOf(user1.address)).to.be.equal( - parseEther("1000000") + parseEther("1000000") ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("200000"), - isBAYC: true, - tokenIds: [0], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("200000"), + isBAYC: true, + tokenIds: [0], + }) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("50000"), - isBAYC: true, - apeTokenIds: [0], - bakcTokenIds: [0], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: true, + apeTokenIds: [0], + bakcTokenIds: [0], + }) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("100000"), - isBAYC: false, - tokenIds: [0], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("100000"), + isBAYC: false, + tokenIds: [0], + }) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ - onBehalf: user1.address, - cashToken: ape.address, - cashAmount: parseEther("50000"), - isBAYC: false, - apeTokenIds: [0], - bakcTokenIds: [1], - }) + await paraApeStaking.connect(user1.signer).depositApeCoinPairPool({ + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("50000"), + isBAYC: false, + apeTokenIds: [0], + bakcTokenIds: [1], + }) ); expect(await ape.balanceOf(user1.address)).to.be.equal( - parseEther("600000") + parseEther("600000") ); expect(await pSApeCoin.balanceOf(user1.address)).to.be.equal( - parseEther("400000") + parseEther("400000") ); expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.equal( - parseEther("400000") + parseEther("400000") ); expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.equal( - parseEther("0") + parseEther("0") ); //user1 borrow value = 200 eth @@ -1111,97 +1111,97 @@ describe("Para Ape staking ape coin pool test", () => { await mintAndValidate(weth, "200", liquidator); await waitForTx( - await weth - .connect(liquidator.signer) - .approve(pool.address, MAX_UINT_AMOUNT) + await weth + .connect(liquidator.signer) + .approve(pool.address, MAX_UINT_AMOUNT) ); // start auction await waitForTx( - await pool - .connect(liquidator.signer) - .startAuction(user1.address, bayc.address, 0) + await pool + .connect(liquidator.signer) + .startAuction(user1.address, bayc.address, 0) ); let auctionData = await pool.getAuctionData(nBAYC.address, 0); await advanceBlock( - auctionData.startTime - .add(auctionData.tickLength.mul(BigNumber.from(40))) - .toNumber() + auctionData.startTime + .add(auctionData.tickLength.mul(BigNumber.from(40))) + .toNumber() ); // try to liquidate the NFT expect( - await pool - .connect(liquidator.signer) - .liquidateERC721( - bayc.address, - user1.address, - 0, - parseEther("100"), - true, - {gasLimit: 5000000} - ) + await pool + .connect(liquidator.signer) + .liquidateERC721( + bayc.address, + user1.address, + 0, + parseEther("100"), + true, + {gasLimit: 5000000} + ) ); expect(await pSApeCoin.balanceOf(user1.address)).to.be.closeTo( - parseEther("400000"), - parseEther("1") + parseEther("400000"), + parseEther("1") ); expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.closeTo( - parseEther("150000"), - parseEther("1") + parseEther("150000"), + parseEther("1") ); expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.closeTo( - parseEther("250000"), - parseEther("1") + parseEther("250000"), + parseEther("1") ); await waitForTx( - await pool - .connect(liquidator.signer) - .startAuction(user1.address, mayc.address, 0) + await pool + .connect(liquidator.signer) + .startAuction(user1.address, mayc.address, 0) ); auctionData = await pool.getAuctionData(nMAYC.address, 0); await advanceBlock( - auctionData.startTime - .add(auctionData.tickLength.mul(BigNumber.from(40))) - .toNumber() + auctionData.startTime + .add(auctionData.tickLength.mul(BigNumber.from(40))) + .toNumber() ); expect( - await pool - .connect(liquidator.signer) - .liquidateERC721( - mayc.address, - user1.address, - 0, - parseEther("50"), - true, - {gasLimit: 5000000} - ) + await pool + .connect(liquidator.signer) + .liquidateERC721( + mayc.address, + user1.address, + 0, + parseEther("50"), + true, + {gasLimit: 5000000} + ) ); expect(await pSApeCoin.balanceOf(user1.address)).to.be.closeTo( - parseEther("400000"), - parseEther("1") + parseEther("400000"), + parseEther("1") ); expect(await paraApeStaking.stakedSApeBalance(user1.address)).to.be.closeTo( - parseEther("0"), - parseEther("1") + parseEther("0"), + parseEther("1") ); expect(await paraApeStaking.freeSApeBalance(user1.address)).to.be.closeTo( - parseEther("400000"), - parseEther("1") + parseEther("400000"), + parseEther("1") ); const accountData0 = await pool.getUserAccountData(user1.address); await waitForTx( - await pool - .connect(user1.signer) - .setUserUseERC20AsCollateral(sApeAddress, true) + await pool + .connect(user1.signer) + .setUserUseERC20AsCollateral(sApeAddress, true) ); const accountData1 = await pool.getUserAccountData(user1.address); //400000 * 0.0002 = 80 expect( - accountData1.totalCollateralBase.sub(accountData0.totalCollateralBase) + accountData1.totalCollateralBase.sub(accountData0.totalCollateralBase) ).to.be.closeTo(parseEther("80"), parseEther("1")); await changePriceAndValidate(ape, "0.0004"); @@ -1210,28 +1210,28 @@ describe("Para Ape staking ape coin pool test", () => { //liquidate sApe await mintAndValidate(ape, "1000000", liquidator); await waitForTx( - await ape - .connect(liquidator.signer) - .approve(pool.address, MAX_UINT_AMOUNT) + await ape + .connect(liquidator.signer) + .approve(pool.address, MAX_UINT_AMOUNT) ); await waitForTx( - await pool - .connect(liquidator.signer) - .liquidateERC20( - sApeAddress, - ape.address, - user1.address, - parseEther("400000"), - true - ) + await pool + .connect(liquidator.signer) + .liquidateERC20( + sApeAddress, + ape.address, + user1.address, + parseEther("400000"), + true + ) ); const user1Balance = await pSApeCoin.balanceOf(user1.address); const liquidatorBalance = await pSApeCoin.balanceOf(liquidator.address); expect(user1Balance).to.be.closeTo("0", parseEther("1")); expect(liquidatorBalance).to.be.closeTo( - parseEther("400000"), - parseEther("1") + parseEther("400000"), + parseEther("1") ); }); @@ -1243,75 +1243,75 @@ describe("Para Ape staking ape coin pool test", () => { await mintAndValidate(ape, "1000000", user1); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .depositFreeSApe(ape.address, parseEther("1000000")) + await paraApeStaking + .connect(user1.signer) + .depositFreeSApe(ape.address, parseEther("1000000")) ); expect(await ape.balanceOf(user1.address)).to.be.equal("0"); expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .withdrawFreeSApe(ape.address, parseEther("1000000")) + await paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(ape.address, parseEther("1000000")) ); expect(await ape.balanceOf(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( - "0", - parseEther("1") + "0", + parseEther("1") ); await waitForTx( - await ape.connect(user1.signer).approve(cApe.address, MAX_UINT_AMOUNT) + await ape.connect(user1.signer).approve(cApe.address, MAX_UINT_AMOUNT) ); await waitForTx( - await cApe - .connect(user1.signer) - .deposit(user1.address, parseEther("1000000")) + await cApe + .connect(user1.signer) + .deposit(user1.address, parseEther("1000000")) ); expect(await ape.balanceOf(user1.address)).to.be.equal("0"); expect(await cApe.balanceOf(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); await waitForTx( - await cApe - .connect(user1.signer) - .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + await cApe + .connect(user1.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .depositFreeSApe(cApe.address, parseEther("1000000")) + await paraApeStaking + .connect(user1.signer) + .depositFreeSApe(cApe.address, parseEther("1000000")) ); expect(await cApe.balanceOf(user1.address)).to.be.equal("0"); expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); await waitForTx( - await paraApeStaking - .connect(user1.signer) - .withdrawFreeSApe(cApe.address, parseEther("1000000")) + await paraApeStaking + .connect(user1.signer) + .withdrawFreeSApe(cApe.address, parseEther("1000000")) ); expect(await cApe.balanceOf(user1.address)).to.be.closeTo( - parseEther("1000000"), - parseEther("1") + parseEther("1000000"), + parseEther("1") ); expect(await paraApeStaking.totalSApeBalance(user1.address)).to.be.closeTo( - "0", - parseEther("1") + "0", + parseEther("1") ); }); From 13516b321c9c69c5d2a09e54577c13631e730fbd Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 8 Aug 2023 16:00:40 +0800 Subject: [PATCH 89/99] chore: add event definition and remove P2P contract --- contracts/apestaking/P2PPairStaking.sol | 772 ---------------------- contracts/interfaces/IApeCoinPool.sol | 19 + contracts/interfaces/IApeStakingVault.sol | 19 + contracts/interfaces/IParaApeStaking.sol | 12 + 4 files changed, 50 insertions(+), 772 deletions(-) delete mode 100644 contracts/apestaking/P2PPairStaking.sol diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol deleted file mode 100644 index 8c0ffb0d8..000000000 --- a/contracts/apestaking/P2PPairStaking.sol +++ /dev/null @@ -1,772 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity 0.8.10; - -import "../dependencies/openzeppelin/upgradeability/Initializable.sol"; -import "../dependencies/openzeppelin/upgradeability/OwnableUpgradeable.sol"; -import "../dependencies/openzeppelin/upgradeability/ReentrancyGuardUpgradeable.sol"; -import "../dependencies/yoga-labs/ApeCoinStaking.sol"; -import "../interfaces/IP2PPairStaking.sol"; -import "../dependencies/openzeppelin/contracts/SafeCast.sol"; -import "../interfaces/IAutoCompoundApe.sol"; -import "../interfaces/ICApe.sol"; -import {IERC721} from "../dependencies/openzeppelin/contracts/IERC721.sol"; -import {IERC20, SafeERC20} from "../dependencies/openzeppelin/contracts/SafeERC20.sol"; -import {PercentageMath} from "../protocol/libraries/math/PercentageMath.sol"; -import {SignatureChecker} from "../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; -import "../protocol/libraries/helpers/Errors.sol"; -import "../interfaces/IACLManager.sol"; - -contract P2PPairStaking is - Initializable, - OwnableUpgradeable, - ReentrancyGuardUpgradeable, - IP2PPairStaking -{ - using SafeERC20 for IERC20; - using PercentageMath for uint256; - using SafeCast for uint256; - - //keccak256("ListingOrder(uint8 stakingType,address offerer,address token,uint256 tokenId,uint256 share,uint256 startTime,uint256 endTime)"); - bytes32 internal constant LISTING_ORDER_HASH = - 0x227f9dd14259caacdbcf45411b33cf1c018f31bd3da27e613a66edf8ae45814f; - - //keccak256("MatchedOrder(uint8 stakingType,address apeToken,uint32 apeTokenId,uint32 apeShare,uint32 bakcTokenId,uint32 bakcShare,address apeCoinOfferer,uint32 apeCoinShare,uint256 apePrincipleAmount)"); - bytes32 internal constant MATCHED_ORDER_HASH = - 0x7db3dae7d89c86e6881a66a131841305c008b207e41ff86a804b4bb056652808; - - //keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - bytes32 internal constant EIP712_DOMAIN = - 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; - - uint256 internal constant WAD = 1e18; - - address internal immutable bayc; - address internal immutable mayc; - address internal immutable bakc; - address internal immutable nBayc; - address internal immutable nMayc; - address internal immutable nBakc; - address internal immutable apeCoin; - address internal immutable cApe; - ApeCoinStaking internal immutable apeCoinStaking; - uint256 public immutable compoundFee; - uint256 private immutable baycMatchedCap; - uint256 private immutable maycMatchedCap; - uint256 private immutable bakcMatchedCap; - - bytes32 internal DOMAIN_SEPARATOR; - mapping(bytes32 => ListingOrderStatus) public listingOrderStatus; - mapping(bytes32 => MatchedOrder) public matchedOrders; - mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; - mapping(address => uint256) private cApeShareBalance; - address public __matchingOperator; - uint256 public __compoundFee; - uint256 private __baycMatchedCap; - uint256 private __maycMatchedCap; - uint256 private __bakcMatchedCap; - address public compoundBot; - bool private paused; - IACLManager private immutable aclManager; - - constructor( - address _bayc, - address _mayc, - address _bakc, - address _nBayc, - address _nMayc, - address _nBakc, - address _apeCoin, - address _cApe, - address _apeCoinStaking, - address _aclManager, - uint256 _compoundFee - ) { - bayc = _bayc; - mayc = _mayc; - bakc = _bakc; - nBayc = _nBayc; - nMayc = _nMayc; - nBakc = _nBakc; - apeCoin = _apeCoin; - cApe = _cApe; - apeCoinStaking = ApeCoinStaking(_apeCoinStaking); - aclManager = IACLManager(_aclManager); - compoundFee = _compoundFee; - - ( - , - ApeCoinStaking.PoolUI memory baycPool, - ApeCoinStaking.PoolUI memory maycPool, - ApeCoinStaking.PoolUI memory bakcPool - ) = apeCoinStaking.getPoolsUI(); - - baycMatchedCap = baycPool.currentTimeRange.capPerPosition; - maycMatchedCap = maycPool.currentTimeRange.capPerPosition; - bakcMatchedCap = bakcPool.currentTimeRange.capPerPosition; - } - - function initialize() public initializer { - __Ownable_init(); - __ReentrancyGuard_init(); - - DOMAIN_SEPARATOR = keccak256( - abi.encode( - EIP712_DOMAIN, - //keccak256("ParaSpace"), - 0x88d989289235fb06c18e3c2f7ea914f41f773e86fb0073d632539f566f4df353, - //keccak256(bytes("1")), - 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, - block.chainid, - address(this) - ) - ); - - //approve ApeCoin for apeCoinStaking - IERC20(apeCoin).safeApprove(address(apeCoinStaking), type(uint256).max); - //approve ApeCoin for cApe - IERC20(apeCoin).safeApprove(cApe, type(uint256).max); - } - - function cancelListing(ListingOrder calldata listingOrder) - external - nonReentrant - { - require(msg.sender == listingOrder.offerer, "not order offerer"); - bytes32 orderHash = getListingOrderHash(listingOrder); - require( - listingOrderStatus[orderHash] != ListingOrderStatus.Cancelled, - "order already cancelled" - ); - listingOrderStatus[orderHash] = ListingOrderStatus.Cancelled; - - emit OrderCancelled(orderHash, listingOrder.offerer); - } - - function matchPairStakingList( - ListingOrder calldata apeOrder, - ListingOrder calldata apeCoinOrder - ) external nonReentrant whenNotPaused returns (bytes32 orderHash) { - //1 validate all order - _validateApeOrder(apeOrder); - bytes32 apeCoinListingOrderHash = _validateApeCoinOrder(apeCoinOrder); - - //2 check if orders can match - require( - apeOrder.stakingType == StakingType.MAYCStaking || - apeOrder.stakingType == StakingType.BAYCStaking, - "invalid stake type" - ); - require( - apeOrder.stakingType == apeCoinOrder.stakingType, - "orders type match failed" - ); - require( - apeOrder.share + apeCoinOrder.share == - PercentageMath.PERCENTAGE_FACTOR, - "orders share match failed" - ); - - //3 transfer token - _handleApeTransfer(apeOrder); - uint256 apeAmount = _handleCApeTransferAndConvert(apeCoinOrder); - - //4 create match order - MatchedOrder memory matchedOrder = MatchedOrder({ - stakingType: apeOrder.stakingType, - apeToken: apeOrder.token, - apeTokenId: apeOrder.tokenId, - apeShare: apeOrder.share, - bakcTokenId: 0, - bakcShare: 0, - apeCoinOfferer: apeCoinOrder.offerer, - apeCoinShare: apeCoinOrder.share, - apePrincipleAmount: apeAmount, - apeCoinListingOrderHash: apeCoinListingOrderHash - }); - orderHash = getMatchedOrderHash(matchedOrder); - matchedOrders[orderHash] = matchedOrder; - apeMatchedCount[apeOrder.token][apeOrder.tokenId] += 1; - - //5 stake for ApeCoinStaking - ApeCoinStaking.SingleNft[] - memory singleNft = new ApeCoinStaking.SingleNft[](1); - singleNft[0].tokenId = apeOrder.tokenId; - singleNft[0].amount = apeAmount.toUint224(); - if (apeOrder.stakingType == StakingType.BAYCStaking) { - apeCoinStaking.depositBAYC(singleNft); - } else { - apeCoinStaking.depositMAYC(singleNft); - } - - //6 update ape coin listing order status - listingOrderStatus[apeCoinListingOrderHash] = ListingOrderStatus - .Matched; - - //7 emit event - emit PairStakingMatched(orderHash); - - return orderHash; - } - - function matchBAKCPairStakingList( - ListingOrder calldata apeOrder, - ListingOrder calldata bakcOrder, - ListingOrder calldata apeCoinOrder - ) external nonReentrant whenNotPaused returns (bytes32 orderHash) { - //1 validate all order - _validateApeOrder(apeOrder); - _validateBakcOrder(bakcOrder); - bytes32 apeCoinListingOrderHash = _validateApeCoinOrder(apeCoinOrder); - - //2 check if orders can match - require( - apeOrder.stakingType == StakingType.BAKCPairStaking, - "invalid stake type" - ); - require( - apeOrder.stakingType == bakcOrder.stakingType && - apeOrder.stakingType == apeCoinOrder.stakingType, - "orders type match failed" - ); - require( - apeOrder.share + bakcOrder.share + apeCoinOrder.share == - PercentageMath.PERCENTAGE_FACTOR, - "share match failed" - ); - - //3 transfer token - _handleApeTransfer(apeOrder); - IERC721(bakc).safeTransferFrom(nBakc, address(this), bakcOrder.tokenId); - uint256 apeAmount = _handleCApeTransferAndConvert(apeCoinOrder); - - //4 create match order - MatchedOrder memory matchedOrder = MatchedOrder({ - stakingType: apeOrder.stakingType, - apeToken: apeOrder.token, - apeTokenId: apeOrder.tokenId, - apeShare: apeOrder.share, - bakcTokenId: bakcOrder.tokenId, - bakcShare: bakcOrder.share, - apeCoinOfferer: apeCoinOrder.offerer, - apeCoinShare: apeCoinOrder.share, - apePrincipleAmount: apeAmount, - apeCoinListingOrderHash: apeCoinListingOrderHash - }); - orderHash = getMatchedOrderHash(matchedOrder); - matchedOrders[orderHash] = matchedOrder; - apeMatchedCount[apeOrder.token][apeOrder.tokenId] += 1; - - //5 stake for ApeCoinStaking - ApeCoinStaking.PairNftDepositWithAmount[] - memory _stakingPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 1 - ); - _stakingPairs[0].mainTokenId = apeOrder.tokenId; - _stakingPairs[0].bakcTokenId = bakcOrder.tokenId; - _stakingPairs[0].amount = apeAmount.toUint184(); - ApeCoinStaking.PairNftDepositWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 0 - ); - if (apeOrder.token == bayc) { - apeCoinStaking.depositBAKC(_stakingPairs, _otherPairs); - } else { - apeCoinStaking.depositBAKC(_otherPairs, _stakingPairs); - } - - //6 update ape coin listing order status - listingOrderStatus[apeCoinListingOrderHash] = ListingOrderStatus - .Matched; - - //7 emit event - emit PairStakingMatched(orderHash); - - return orderHash; - } - - function breakUpMatchedOrder(bytes32 orderHash) - external - nonReentrant - whenNotPaused - { - MatchedOrder memory order = matchedOrders[orderHash]; - - //1 check if have permission to break up - address apeNToken = _getApeNTokenAddress(order.apeToken); - address apeNTokenOwner = IERC721(apeNToken).ownerOf(order.apeTokenId); - address nBakcOwner = IERC721(nBakc).ownerOf(order.bakcTokenId); - require( - msg.sender == apeNTokenOwner || - msg.sender == order.apeCoinOfferer || - (msg.sender == nBakcOwner && - order.stakingType == StakingType.BAKCPairStaking), - "no permission to break up" - ); - - //2 claim pending reward and compound - bytes32[] memory orderHashes = new bytes32[](1); - orderHashes[0] = orderHash; - _claimForMatchedOrdersAndCompound(orderHashes); - - //3 delete matched order - delete matchedOrders[orderHash]; - - //4 exit from ApeCoinStaking - if (order.stakingType < StakingType.BAKCPairStaking) { - ApeCoinStaking.SingleNft[] - memory _nfts = new ApeCoinStaking.SingleNft[](1); - _nfts[0].tokenId = order.apeTokenId; - _nfts[0].amount = order.apePrincipleAmount.toUint224(); - if (order.stakingType == StakingType.BAYCStaking) { - apeCoinStaking.withdrawSelfBAYC(_nfts); - } else { - apeCoinStaking.withdrawSelfMAYC(_nfts); - } - } else { - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _nfts = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 1 - ); - _nfts[0].mainTokenId = order.apeTokenId; - _nfts[0].bakcTokenId = order.bakcTokenId; - _nfts[0].amount = order.apePrincipleAmount.toUint184(); - _nfts[0].isUncommit = true; - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); - if (order.apeToken == bayc) { - apeCoinStaking.withdrawBAKC(_nfts, _otherPairs); - } else { - apeCoinStaking.withdrawBAKC(_otherPairs, _nfts); - } - } - //5 transfer token - uint256 matchedCount = apeMatchedCount[order.apeToken][ - order.apeTokenId - ]; - if (matchedCount == 1) { - IERC721(order.apeToken).safeTransferFrom( - address(this), - apeNToken, - order.apeTokenId - ); - } - apeMatchedCount[order.apeToken][order.apeTokenId] = matchedCount - 1; - - IAutoCompoundApe(cApe).deposit( - order.apeCoinOfferer, - order.apePrincipleAmount - ); - if (order.stakingType == StakingType.BAKCPairStaking) { - IERC721(bakc).safeTransferFrom( - address(this), - nBakc, - order.bakcTokenId - ); - } - - //6 reset ape coin listing order status - if ( - listingOrderStatus[order.apeCoinListingOrderHash] != - ListingOrderStatus.Cancelled - ) { - listingOrderStatus[ - order.apeCoinListingOrderHash - ] = ListingOrderStatus.Pending; - } - - //7 emit event - emit PairStakingBreakUp(orderHash); - } - - function claimForMatchedOrderAndCompound(bytes32[] calldata orderHashes) - external - nonReentrant - whenNotPaused - { - require(msg.sender == compoundBot, "no permission to compound"); - _claimForMatchedOrdersAndCompound(orderHashes); - } - - function _claimForMatchedOrdersAndCompound(bytes32[] memory orderHashes) - internal - { - //ignore getShareByPooledApe return 0 case. - uint256 cApeExchangeRate = ICApe(cApe).getPooledApeByShares(WAD); - uint256 _compoundFee = compoundFee; - uint256 totalReward; - uint256 totalFee; - uint256 orderCounts = orderHashes.length; - for (uint256 index = 0; index < orderCounts; index++) { - bytes32 orderHash = orderHashes[index]; - (uint256 reward, uint256 fee) = _claimForMatchedOrderAndCompound( - orderHash, - cApeExchangeRate, - _compoundFee - ); - totalReward += reward; - totalFee += fee; - } - if (totalReward > 0) { - IAutoCompoundApe(cApe).deposit(address(this), totalReward); - IERC20(apeCoin).safeTransfer(compoundBot, totalFee); - } - } - - function claimCApeReward(address receiver) - external - nonReentrant - whenNotPaused - { - uint256 cApeAmount = pendingCApeReward(msg.sender); - if (cApeAmount > 0) { - IERC20(cApe).safeTransfer(receiver, cApeAmount); - delete cApeShareBalance[msg.sender]; - emit CApeClaimed(msg.sender, receiver, cApeAmount); - } - } - - function pendingCApeReward(address user) public view returns (uint256) { - uint256 amount = 0; - uint256 shareBalance = cApeShareBalance[user]; - if (shareBalance > 0) { - amount = ICApe(cApe).getPooledApeByShares(shareBalance); - } - return amount; - } - - function getApeCoinStakingCap(StakingType stakingType) - public - view - returns (uint256) - { - if (stakingType == StakingType.BAYCStaking) { - return baycMatchedCap; - } else if (stakingType == StakingType.MAYCStaking) { - return maycMatchedCap; - } else { - return bakcMatchedCap; - } - } - - function getListingOrderHash(ListingOrder calldata order) - public - pure - returns (bytes32) - { - return - keccak256( - abi.encode( - LISTING_ORDER_HASH, - order.stakingType, - order.offerer, - order.token, - order.tokenId, - order.share, - order.startTime, - order.endTime - ) - ); - } - - function getMatchedOrderHash(MatchedOrder memory order) - public - pure - returns (bytes32) - { - return - keccak256( - abi.encode( - MATCHED_ORDER_HASH, - order.stakingType, - order.apeToken, - order.apeTokenId, - order.apeShare, - order.bakcTokenId, - order.bakcShare, - order.apeCoinOfferer, - order.apeCoinShare, - order.apePrincipleAmount - ) - ); - } - - function _getApeNTokenAddress(address apeToken) - internal - view - returns (address) - { - if (apeToken == bayc) { - return nBayc; - } else if (apeToken == mayc) { - return nMayc; - } else { - revert("unsupported ape token"); - } - } - - function _claimForMatchedOrderAndCompound( - bytes32 orderHash, - uint256 cApeExchangeRate, - uint256 _compoundFee - ) internal returns (uint256, uint256) { - MatchedOrder memory order = matchedOrders[orderHash]; - uint256 balanceBefore = IERC20(apeCoin).balanceOf(address(this)); - if ( - order.stakingType == StakingType.BAYCStaking || - order.stakingType == StakingType.MAYCStaking - ) { - uint256[] memory _nfts = new uint256[](1); - _nfts[0] = order.apeTokenId; - if (order.stakingType == StakingType.BAYCStaking) { - apeCoinStaking.claimSelfBAYC(_nfts); - } else { - apeCoinStaking.claimSelfMAYC(_nfts); - } - } else { - ApeCoinStaking.PairNft[] - memory _nfts = new ApeCoinStaking.PairNft[](1); - _nfts[0].mainTokenId = order.apeTokenId; - _nfts[0].bakcTokenId = order.bakcTokenId; - ApeCoinStaking.PairNft[] - memory _otherPairs = new ApeCoinStaking.PairNft[](0); - if (order.apeToken == bayc) { - apeCoinStaking.claimSelfBAKC(_nfts, _otherPairs); - } else { - apeCoinStaking.claimSelfBAKC(_otherPairs, _nfts); - } - } - uint256 balanceAfter = IERC20(apeCoin).balanceOf(address(this)); - uint256 rewardAmount = balanceAfter - balanceBefore; - if (rewardAmount == 0) { - return (0, 0); - } - - uint256 feeAmount = rewardAmount.percentMul(_compoundFee); - rewardAmount -= feeAmount; - uint256 rewardShare = (rewardAmount * WAD) / cApeExchangeRate; - - _depositCApeShareForUser( - IERC721(_getApeNTokenAddress(order.apeToken)).ownerOf( - order.apeTokenId - ), - rewardShare.percentMul(order.apeShare) - ); - _depositCApeShareForUser( - order.apeCoinOfferer, - rewardShare.percentMul(order.apeCoinShare) - ); - if (order.stakingType == StakingType.BAKCPairStaking) { - _depositCApeShareForUser( - IERC721(nBakc).ownerOf(order.bakcTokenId), - rewardShare.percentMul(order.bakcShare) - ); - } - - emit OrderClaimedAndCompounded(orderHash, rewardAmount + feeAmount); - - return (rewardAmount, feeAmount); - } - - function _depositCApeShareForUser(address user, uint256 amount) internal { - if (amount > 0) { - cApeShareBalance[user] += amount; - } - } - - function _handleApeTransfer(ListingOrder calldata order) internal { - address currentOwner = IERC721(order.token).ownerOf(order.tokenId); - if (currentOwner != address(this)) { - address nTokenAddress = _getApeNTokenAddress(order.token); - IERC721(order.token).safeTransferFrom( - nTokenAddress, - address(this), - order.tokenId - ); - } - } - - function _handleCApeTransferAndConvert(ListingOrder calldata apeCoinOrder) - internal - returns (uint256) - { - uint256 apeAmount = getApeCoinStakingCap(apeCoinOrder.stakingType); - IERC20(cApe).safeTransferFrom( - apeCoinOrder.offerer, - address(this), - apeAmount - ); - IAutoCompoundApe(cApe).withdraw(apeAmount); - return apeAmount; - } - - function _validateOrderBasicInfo(ListingOrder calldata listingOrder) - internal - view - returns (bytes32 orderHash) - { - require( - listingOrder.startTime <= block.timestamp, - "ape order not start" - ); - require(listingOrder.endTime >= block.timestamp, "ape offer expired"); - - orderHash = getListingOrderHash(listingOrder); - require( - listingOrderStatus[orderHash] != ListingOrderStatus.Cancelled, - "order already cancelled" - ); - - if (msg.sender != listingOrder.offerer) { - require( - validateOrderSignature( - listingOrder.offerer, - orderHash, - listingOrder.v, - listingOrder.r, - listingOrder.s - ), - "invalid signature" - ); - } - } - - function _validateApeOrder(ListingOrder calldata apeOrder) internal view { - _validateOrderBasicInfo(apeOrder); - - address nToken = _getApeNTokenAddress(apeOrder.token); - require( - IERC721(nToken).ownerOf(apeOrder.tokenId) == apeOrder.offerer, - "ape order invalid NToken owner" - ); - } - - function _validateBakcOrder(ListingOrder calldata bakcOrder) internal view { - _validateOrderBasicInfo(bakcOrder); - - require(bakcOrder.token == bakc, "bakc order invalid token"); - require( - IERC721(nBakc).ownerOf(bakcOrder.tokenId) == bakcOrder.offerer, - "bakc order invalid NToken owner" - ); - } - - function _validateApeCoinOrder(ListingOrder calldata apeCoinOrder) - internal - view - returns (bytes32 orderHash) - { - orderHash = _validateOrderBasicInfo(apeCoinOrder); - require(apeCoinOrder.token == cApe, "ape coin order invalid token"); - require( - listingOrderStatus[orderHash] != ListingOrderStatus.Matched, - "ape coin order already matched" - ); - } - - function validateOrderSignature( - address signer, - bytes32 orderHash, - uint8 v, - bytes32 r, - bytes32 s - ) public view returns (bool) { - return - SignatureChecker.verify( - orderHash, - signer, - v, - r, - s, - DOMAIN_SEPARATOR - ); - } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) external pure returns (bytes4) { - return this.onERC721Received.selector; - } - - function setCompoundBot(address _compoundBot) external onlyPoolAdmin { - address oldValue = compoundBot; - if (oldValue != _compoundBot) { - compoundBot = _compoundBot; - emit CompoundBotUpdated(oldValue, _compoundBot); - } - } - - function claimCompoundFee(address receiver) external onlyPoolAdmin { - this.claimCApeReward(receiver); - } - - /** - * @notice Pauses the contract. Only pool admin or emergency admin can call this function - **/ - function pause() external onlyEmergencyOrPoolAdmin whenNotPaused { - paused = true; - emit Paused(_msgSender()); - } - - /** - * @notice Unpause the contract. Only pool admin can call this function - **/ - function unpause() external onlyPoolAdmin whenPaused { - paused = false; - emit Unpaused(_msgSender()); - } - - function rescueERC20( - address token, - address to, - uint256 amount - ) external onlyPoolAdmin { - IERC20(token).safeTransfer(to, amount); - emit RescueERC20(token, to, amount); - } - - modifier whenNotPaused() { - require(!paused, "paused"); - _; - } - - modifier whenPaused() { - require(paused, "not paused"); - _; - } - - /** - * @dev Only pool admin can call functions marked by this modifier. - **/ - modifier onlyPoolAdmin() { - _onlyPoolAdmin(); - _; - } - - /** - * @dev Only emergency or pool admin can call functions marked by this modifier. - **/ - modifier onlyEmergencyOrPoolAdmin() { - _onlyPoolOrEmergencyAdmin(); - _; - } - - function _onlyPoolAdmin() internal view { - require( - aclManager.isPoolAdmin(msg.sender), - Errors.CALLER_NOT_POOL_ADMIN - ); - } - - function _onlyPoolOrEmergencyAdmin() internal view { - require( - aclManager.isPoolAdmin(msg.sender) || - aclManager.isEmergencyAdmin(msg.sender), - Errors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN - ); - } -} diff --git a/contracts/interfaces/IApeCoinPool.sol b/contracts/interfaces/IApeCoinPool.sol index 37e3c0a97..ba9eb4f78 100644 --- a/contracts/interfaces/IApeCoinPool.sol +++ b/contracts/interfaces/IApeCoinPool.sol @@ -54,6 +54,25 @@ interface IApeCoinPool { uint32[] bakcTokenIds; } + event ApeCoinPoolDeposited(bool isBAYC, uint256 tokenId); + event ApeCoinPoolCompounded(bool isBAYC, uint256 tokenId); + event ApeCoinPoolWithdrew(bool isBAYC, uint256 tokenId); + event ApeCoinPairPoolDeposited( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + event ApeCoinPairPoolCompounded( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + event ApeCoinPairPoolWithdrew( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + /** * @notice deposit Ape and ape coin position to Ape coin Pool. * @param depositInfo Detail deposit info diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index cc7083cb5..f3bafbed5 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -16,6 +16,25 @@ interface IApeStakingVault { **/ event ApePairStakingRewardRatioUpdated(uint256 oldRatio, uint256 newRatio); + event PairNFTDeposited( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + event PairNFTStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event PairNFTWithdrew(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event PairNFTCompounded( + bool isBAYC, + uint256 apeTokenId, + uint256 bakcTokenId + ); + event NFTDeposited(address nft, uint256 tokenId); + event ApeStaked(bool isBAYC, uint256 tokenId); + event BakcStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event ApeCompounded(bool isBAYC, uint256 tokenId); + event BakcCompounded(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event NFTWithdrawn(address nft, uint256 tokenId); + /** * @notice deposit Ape and BAKC pair into the pool * @param isBAYC if Ape is BAYC diff --git a/contracts/interfaces/IParaApeStaking.sol b/contracts/interfaces/IParaApeStaking.sol index 099ad8a9d..a1e3c40ea 100644 --- a/contracts/interfaces/IParaApeStaking.sol +++ b/contracts/interfaces/IParaApeStaking.sol @@ -87,6 +87,18 @@ interface IParaApeStaking is IApeStakingVault, IApeStakingP2P, IApeCoinPool { **/ event CompoundFeeUpdated(uint64 oldValue, uint64 newValue); + /** + * @dev Emitted during claimPendingReward() + * @param poolId identify which pool user claimed from + * @param tokenId identify position token id + * @param rewardAmount Reward amount claimed + **/ + event PoolRewardClaimed( + uint256 poolId, + uint256 tokenId, + uint256 rewardAmount + ); + /** * @notice Query sApe reserve Id used by ParaApeStaking */ From a784db1fa9813c7a5c3da664b0ccf193d70ddfe4 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 8 Aug 2023 21:32:31 +0800 Subject: [PATCH 90/99] chore: fix lint --- Makefile | 4 -- helpers/contracts-deployments.ts | 68 ------------------- helpers/contracts-getters.ts | 12 ---- helpers/contracts-helpers.ts | 2 - package.json | 2 +- .../deployments/steps/23_renounceOwnership.ts | 26 ------- scripts/upgrade/P2PPairStaking.ts | 41 ----------- tasks/upgrade/index.ts | 12 ---- test/_timelock.spec.ts | 2 +- 9 files changed, 2 insertions(+), 167 deletions(-) delete mode 100644 scripts/upgrade/P2PPairStaking.ts diff --git a/Makefile b/Makefile index 50b9be4ee..917ac85af 100644 --- a/Makefile +++ b/Makefile @@ -636,10 +636,6 @@ upgrade-auto-compound-ape: upgrade-timelock: make TASK_NAME=upgrade:timelock run-task -.PHONY: upgrade-p2p-pair-staking -upgrade-p2p-pair-staking: - make TASK_NAME=upgrade:p2p-pair-staking run-task - .PHONY: upgrade-para-ape-staking upgrade-para-ape-staking: make TASK_NAME=upgrade:para-ape-staking run-task diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 8f5647011..9a571564c 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -224,8 +224,6 @@ import { MockedDelegateRegistry__factory, NTokenBAKC, NTokenBAKC__factory, - P2PPairStaking__factory, - P2PPairStaking, AirdropFlashClaimReceiver__factory, AirdropFlashClaimReceiver, CLwstETHSynchronicityPriceAdapter__factory, @@ -314,7 +312,6 @@ import { getTimeLockProxy, getInitializableAdminUpgradeabilityProxy, getAutoCompoundApe, - getP2PPairStaking, getAutoYieldApe, getHelperContract, getParaApeStaking, @@ -2616,71 +2613,6 @@ export const deployAutoCompoundApeImplAndAssignItToProxy = async ( ); }; -export const deployP2PPairStakingImpl = async ( - compoundFee: number, - verify?: boolean -) => { - const allTokens = await getAllTokens(); - const protocolDataProvider = await getProtocolDataProvider(); - const nBAYC = ( - await protocolDataProvider.getReserveTokensAddresses(allTokens.BAYC.address) - ).xTokenAddress; - const nMAYC = ( - await protocolDataProvider.getReserveTokensAddresses(allTokens.MAYC.address) - ).xTokenAddress; - const nBAKC = ( - await protocolDataProvider.getReserveTokensAddresses(allTokens.BAKC.address) - ).xTokenAddress; - const apeCoinStaking = - (await getContractAddressInDb(eContractid.ApeCoinStaking)) || - (await deployApeCoinStaking(verify)).address; - const aclManager = await getACLManager(); - const args = [ - allTokens.BAYC.address, - allTokens.MAYC.address, - allTokens.BAKC.address, - nBAYC, - nMAYC, - nBAKC, - allTokens.APE.address, - allTokens.cAPE.address, - apeCoinStaking, - aclManager.address, - compoundFee, - ]; - - return withSaveAndVerify( - new P2PPairStaking__factory(await getFirstSigner()), - eContractid.P2PPairStakingImpl, - [...args], - verify - ) as Promise; -}; - -export const deployP2PPairStaking = async (verify?: boolean) => { - const p2pImplementation = await deployP2PPairStakingImpl(0, verify); - - const deployer = await getFirstSigner(); - const deployerAddress = await deployer.getAddress(); - - const initData = p2pImplementation.interface.encodeFunctionData("initialize"); - - const proxyInstance = await withSaveAndVerify( - new InitializableAdminUpgradeabilityProxy__factory(await getFirstSigner()), - eContractid.P2PPairStaking, - [], - verify - ); - - await waitForTx( - await (proxyInstance as InitializableAdminUpgradeabilityProxy)[ - "initialize(address,address,bytes)" - ](p2pImplementation.address, deployerAddress, initData, GLOBAL_OVERRIDES) - ); - - return await getP2PPairStaking(proxyInstance.address); -}; - export const deployApeStakingP2PLogic = async (verify?: boolean) => withSaveAndVerify( new ApeStakingP2PLogic__factory(await getFirstSigner()), diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index bd335ca81..9b4179958 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -76,7 +76,6 @@ import { StETHDebtToken__factory, MintableERC721Logic__factory, NTokenBAKC__factory, - P2PPairStaking__factory, ExecutorWithTimelock__factory, MultiSendCallOnly__factory, WstETHMocked__factory, @@ -1021,17 +1020,6 @@ export const getAutoYieldApe = async (address?: tEthereumAddress) => await getFirstSigner() ); -export const getP2PPairStaking = async (address?: tEthereumAddress) => - await P2PPairStaking__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.P2PPairStaking}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - export const getParaApeStaking = async (address?: tEthereumAddress) => await ParaApeStaking__factory.connect( address || diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 489408dca..adbda5dd1 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -78,7 +78,6 @@ import { Seaport__factory, NTokenOtherdeed__factory, TimeLock__factory, - P2PPairStaking__factory, ISafe__factory, } from "../types"; import {HardhatRuntimeEnvironment, HttpNetworkConfig} from "hardhat/types"; @@ -906,7 +905,6 @@ export const decodeInputData = (data: string) => { ...ICurve__factory.abi, ...NTokenOtherdeed__factory.abi, ...TimeLock__factory.abi, - ...P2PPairStaking__factory.abi, ...ISafe__factory.abi, ]; diff --git a/package.json b/package.json index cf21f5564..d1ac692b9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "coverage": "hardhat coverage --testfiles 'test/*.ts'", "format": "prettier --write 'contracts/**/*.sol' 'scripts/**/*.ts' 'helpers/**/*.ts' 'tasks/**/*.ts' 'test/**/*.ts' 'hardhat.config.ts' 'helper-hardhat-config.ts' 'market-config/**/*.ts'", "doc": "hardhat docgen", - "test": "hardhat test ./test/para_ape_staking.spec.ts", + "test": "hardhat test ./test/*.ts", "clean": "hardhat clean" }, "devDependencies": { diff --git a/scripts/deployments/steps/23_renounceOwnership.ts b/scripts/deployments/steps/23_renounceOwnership.ts index 7c492116a..950324e12 100644 --- a/scripts/deployments/steps/23_renounceOwnership.ts +++ b/scripts/deployments/steps/23_renounceOwnership.ts @@ -7,7 +7,6 @@ import { getHelperContract, getInitializableAdminUpgradeabilityProxy, getNFTFloorOracle, - getP2PPairStaking, getParaApeStaking, getPausableZoneController, getPoolAddressesProvider, @@ -372,31 +371,6 @@ export const step_23 = async ( console.log(); } - //////////////////////////////////////////////////////////////////////////////// - // P2PPairStaking - //////////////////////////////////////////////////////////////////////////////// - if (await getContractAddressInDb(eContractid.P2PPairStaking)) { - console.time("transferring P2PPairStaking ownership..."); - const p2pPairStaking = await getP2PPairStaking(); - const p2pPairStakingProxy = - await getInitializableAdminUpgradeabilityProxy(p2pPairStaking.address); - const signers = await getEthersSigners(); - const adminAddress = signers[5].getAddress(); - if (DRY_RUN) { - const encodedData1 = p2pPairStakingProxy.interface.encodeFunctionData( - "changeAdmin", - [adminAddress] - ); - await dryRunEncodedData(p2pPairStakingProxy.address, encodedData1); - } else { - await waitForTx( - await p2pPairStakingProxy.changeAdmin(adminAddress, GLOBAL_OVERRIDES) - ); - } - console.timeEnd("transferring P2PPairStaking ownership..."); - console.log(); - } - //////////////////////////////////////////////////////////////////////////////// // ParaApeStaking //////////////////////////////////////////////////////////////////////////////// diff --git a/scripts/upgrade/P2PPairStaking.ts b/scripts/upgrade/P2PPairStaking.ts deleted file mode 100644 index 365705a43..000000000 --- a/scripts/upgrade/P2PPairStaking.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {deployP2PPairStakingImpl} from "../../helpers/contracts-deployments"; -import { - getInitializableAdminUpgradeabilityProxy, - getP2PPairStaking, -} from "../../helpers/contracts-getters"; -import {dryRunEncodedData} from "../../helpers/contracts-helpers"; -import {DRY_RUN, GLOBAL_OVERRIDES} from "../../helpers/hardhat-constants"; -import {waitForTx} from "../../helpers/misc-utils"; - -export const upgradeP2PPairStaking = async ( - compoundFee: number, - verify = false -) => { - console.time("deploy P2PPairStaking"); - const p2pPairStakingImpl = await deployP2PPairStakingImpl( - compoundFee, - verify - ); - const p2pPairStaking = await getP2PPairStaking(); - const p2pPairStakingProxy = await getInitializableAdminUpgradeabilityProxy( - p2pPairStaking.address - ); - console.timeEnd("deploy P2PPairStaking"); - - console.time("upgrade P2PPairStaking"); - if (DRY_RUN) { - const encodedData = p2pPairStakingProxy.interface.encodeFunctionData( - "upgradeTo", - [p2pPairStakingImpl.address] - ); - await dryRunEncodedData(p2pPairStakingProxy.address, encodedData); - } else { - await waitForTx( - await p2pPairStakingProxy.upgradeTo( - p2pPairStakingImpl.address, - GLOBAL_OVERRIDES - ) - ); - } - console.timeEnd("upgrade P2PPairStaking"); -}; diff --git a/tasks/upgrade/index.ts b/tasks/upgrade/index.ts index b8f8b125e..cf49b2887 100644 --- a/tasks/upgrade/index.ts +++ b/tasks/upgrade/index.ts @@ -119,18 +119,6 @@ task("upgrade:timelock", "upgrade timelock").setAction(async (_, DRE) => { console.timeEnd("upgrade timelock"); }); -task("upgrade:p2p-pair-staking", "upgrade p2p pair staking") - .addPositionalParam("compoundFee", "new compound fee") - .setAction(async (compoundFee, DRE) => { - const {upgradeP2PPairStaking} = await import( - "../../scripts/upgrade/P2PPairStaking" - ); - await DRE.run("set-DRE"); - console.time("upgrade p2p pair staking"); - await upgradeP2PPairStaking(compoundFee, ETHERSCAN_VERIFICATION); - console.timeEnd("upgrade p2p pair staking"); - }); - task("upgrade:para-ape-staking", "upgrade para ape staking").setAction( async (_, DRE) => { const {upgradeParaApeStaking} = await import( diff --git a/test/_timelock.spec.ts b/test/_timelock.spec.ts index a97506a45..0dafcd47a 100644 --- a/test/_timelock.spec.ts +++ b/test/_timelock.spec.ts @@ -634,7 +634,7 @@ describe("TimeLock functionality tests", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .withdrawFreeSApe(user1.address, parseEther("200000")) + .withdrawFreeSApe(cApe.address, parseEther("200000")) ); expect(await cApe.balanceOf(user1.address)).to.be.equal("0"); From 82b12d8830a1a2caa9e55424327603d69d039a3b Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 15 Aug 2023 16:50:37 +0800 Subject: [PATCH 91/99] chore: fix typo --- .../logic/ApeStakingSinglePoolLogic.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index b5f8e6f3c..e5be91278 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -752,22 +752,22 @@ library ApeStakingSinglePoolLogic { IParaApeStaking.PoolState storage maycPoolState = poolStates[ ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID ]; - uint24 baycPositon = baycPoolState.totalPosition; - uint24 maycPositon = maycPoolState.totalPosition; - uint24 apeTotalPosition = baycPositon + maycPositon; + uint24 baycPosition = baycPoolState.totalPosition; + uint24 maycPosition = maycPoolState.totalPosition; + uint24 apeTotalPosition = baycPosition + maycPosition; if (apeTotalPosition != 0) { - uint256 baycShareAmount = (apeShareAmount * baycPositon) / + uint256 baycShareAmount = (apeShareAmount * baycPosition) / apeTotalPosition; uint256 maycShareAmount = apeShareAmount - baycShareAmount; - if (baycPositon != 0) { + if (baycPosition != 0) { baycPoolState.accumulatedRewardsPerNft += baycShareAmount.toUint104() / - baycPositon; + baycPosition; } - if (maycPositon != 0) { + if (maycPosition != 0) { maycPoolState.accumulatedRewardsPerNft += maycShareAmount.toUint104() / - maycPositon; + maycPosition; } } else { compoundFee += apeShareAmount; From 9fdd1e7787adbb0acc26d90307585c5ff2074f17 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 6 Sep 2023 16:07:13 +0800 Subject: [PATCH 92/99] chore: merge v1 and v2 logic --- .../protocol/tokenization/NTokenBAKC.sol | 88 + .../protocol/tokenization/PTokenSApe.sol | 12 +- helpers/contracts-deployments.ts | 75 +- helpers/contracts-getters.ts | 12 + helpers/init-helpers.ts | 33 +- helpers/types.ts | 1 + .../deployments/steps/20_paraApeStaking.ts | 66 +- scripts/upgrade/P2PPairStaking.ts | 35 + scripts/upgrade/ntoken.ts | 20 +- scripts/upgrade/ptoken.ts | 18 +- test/_pool_ape_staking.spec.ts | 2985 +++++++++++++++++ test/para_pool_ape_staking.spec.ts | 4 +- test/xtoken_ntoken_bakc.spec.ts | 404 +++ 13 files changed, 3733 insertions(+), 20 deletions(-) create mode 100644 scripts/upgrade/P2PPairStaking.ts create mode 100644 test/_pool_ape_staking.spec.ts create mode 100644 test/xtoken_ntoken_bakc.spec.ts diff --git a/contracts/protocol/tokenization/NTokenBAKC.sol b/contracts/protocol/tokenization/NTokenBAKC.sol index a9be3dee2..5341440d5 100644 --- a/contracts/protocol/tokenization/NTokenBAKC.sol +++ b/contracts/protocol/tokenization/NTokenBAKC.sol @@ -7,6 +7,9 @@ import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; +import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; +import {INTokenApeStaking} from "../../interfaces/INTokenApeStaking.sol"; +import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {INToken} from "../../interfaces/INToken.sol"; import {IRewardController} from "../../interfaces/IRewardController.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; @@ -22,6 +25,9 @@ contract NTokenBAKC is NToken { using SafeCast for uint256; IParaApeStaking immutable paraApeStaking; + ApeCoinStaking immutable _apeCoinStaking; + address private immutable nBAYC; + address private immutable nMAYC; /** * @dev Constructor. @@ -29,9 +35,15 @@ contract NTokenBAKC is NToken { */ constructor( IPool pool, + address apeCoinStaking, + address _nBAYC, + address _nMAYC, address delegateRegistry ) NToken(pool, false, delegateRegistry) { paraApeStaking = IParaApeStaking(pool.paraApeStaking()); + _apeCoinStaking = ApeCoinStaking(apeCoinStaking); + nBAYC = _nBAYC; + nMAYC = _nMAYC; } function initialize( @@ -51,6 +63,21 @@ contract NTokenBAKC is NToken { params ); + //v1 + IERC20 ape = _apeCoinStaking.apeCoin(); + //approve for nBAYC + uint256 allowance = ape.allowance(address(this), nBAYC); + if (allowance == 0) { + ape.approve(nBAYC, type(uint256).max); + } + //approve for Pool nMAYC + allowance = ape.allowance(address(this), nMAYC); + if (allowance == 0) { + ape.approve(nMAYC, type(uint256).max); + } + IERC721(underlyingAsset).setApprovalForAll(address(POOL), true); + + //v2 IERC721(underlyingAsset).setApprovalForAll( address(paraApeStaking), true @@ -70,10 +97,71 @@ contract NTokenBAKC is NToken { uint32[] memory tokenIds = new uint32[](1); tokenIds[0] = tokenId.toUint32(); paraApeStaking.nBakcOwnerChangeCallback(tokenIds); + } else { + _unStakePairedApePosition(tokenId); } super._transfer(from, to, tokenId, validate); } + /** + * @notice Overrides the burn from NToken to withdraw all staked and pending rewards before burning the NToken on liquidation/withdraw + */ + function burn( + address from, + address receiverOfUnderlying, + uint256[] calldata tokenIds, + DataTypes.TimeLockParams calldata timeLockParams + ) external virtual override onlyPool nonReentrant returns (uint64, uint64) { + if (from != receiverOfUnderlying) { + for (uint256 index = 0; index < tokenIds.length; index++) { + _unStakePairedApePosition(tokenIds[index]); + } + } + return _burn(from, receiverOfUnderlying, tokenIds, timeLockParams); + } + + function _unStakePairedApePosition(uint256 tokenId) internal { + //check if have ape pair position + (uint256 bakcStakedAmount, ) = _apeCoinStaking.nftPosition( + ApeStakingLogic.BAKC_POOL_ID, + tokenId + ); + if (bakcStakedAmount > 0) { + bool positionExisted = _tryUnstakeMainTokenPosition( + ApeStakingLogic.BAYC_POOL_ID, + nBAYC, + tokenId + ); + if (!positionExisted) { + _tryUnstakeMainTokenPosition( + ApeStakingLogic.MAYC_POOL_ID, + nMAYC, + tokenId + ); + } + } + } + + function _tryUnstakeMainTokenPosition( + uint256 poolId, + address nToken, + uint256 tokenId + ) internal returns (bool) { + (uint256 mainTokenId, bool positionExisted) = _apeCoinStaking + .bakcToMain(tokenId, poolId); + if (positionExisted) { + bool sameOwner = INToken(nToken).ownerOf(mainTokenId) == + ownerOf(tokenId); + if (sameOwner) { + INTokenApeStaking(nToken).unstakePositionAndRepay( + mainTokenId, + address(0) + ); + } + } + return positionExisted; + } + function getXTokenType() external pure override returns (XTokenType) { return XTokenType.NTokenBAKC; } diff --git a/contracts/protocol/tokenization/PTokenSApe.sol b/contracts/protocol/tokenization/PTokenSApe.sol index c1ef5fa26..5a7e5ecc2 100644 --- a/contracts/protocol/tokenization/PTokenSApe.sol +++ b/contracts/protocol/tokenization/PTokenSApe.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import {IPool} from "../../interfaces/IPool.sol"; import {PToken} from "./PToken.sol"; +import {INTokenApeStaking} from "../../interfaces/INTokenApeStaking.sol"; import {WadRayMath} from "../libraries/math/WadRayMath.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; @@ -24,9 +25,13 @@ contract PTokenSApe is PToken { using WadRayMath for uint256; IParaApeStaking immutable paraApeStaking; + INTokenApeStaking immutable nBAYC; + INTokenApeStaking immutable nMAYC; - constructor(IPool pool) PToken(pool) { + constructor(IPool pool, address _nBAYC, address _nMAYC) PToken(pool) { paraApeStaking = IParaApeStaking(pool.paraApeStaking()); + nBAYC = INTokenApeStaking(_nBAYC); + nMAYC = INTokenApeStaking(_nMAYC); } function mint( @@ -49,7 +54,10 @@ contract PTokenSApe is PToken { } function balanceOf(address user) public view override returns (uint256) { - return paraApeStaking.totalSApeBalance(user); + uint256 v1StakedAPE = nBAYC.getUserApeStakingAmount(user) + + nMAYC.getUserApeStakingAmount(user); + uint256 v2StakedAPE = paraApeStaking.totalSApeBalance(user); + return v1StakedAPE + v2StakedAPE; } function scaledBalanceOf( diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 27276b134..6e21fc123 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -159,6 +159,7 @@ import { ParaApeStaking, PoolBorrowAndStake__factory, PoolBorrowAndStake, + P2PPairStaking, } from "../types"; import { getACLManager, @@ -170,6 +171,7 @@ import { getFirstSigner, getHelperContract, getInitializableAdminUpgradeabilityProxy, + getP2PPairStaking, getParaApeStaking, getPoolProxy, getProtocolDataProvider, @@ -239,6 +241,9 @@ export const deployAllLibraries = async (verify?: boolean) => { "contracts/protocol/libraries/logic/PoolLogic.sol": { PoolLogic: poolLogic.address, }, + "contracts/protocol/tokenization/libraries/ApeStakingLogic.sol": { + ApeStakingLogic: apeStakingLogic.address, + }, "contracts/protocol/tokenization/libraries/MintableERC721Logic.sol": { MintableERC721Logic: mintableERC721Logic.address, }, @@ -875,9 +880,6 @@ export const deployPoolComponents = async ( const allTokens = await getAllTokens(); - const APE_WETH_FEE = 3000; - const WETH_USDC_FEE = 500; - const { poolCoreSelectors, poolParametersSelectors, @@ -922,8 +924,6 @@ export const deployPoolComponents = async ( poolMarketplaceSelectors )) as PoolMarketplace; - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; const cApe = await getAutoCompoundApe(); const poolApeStaking = allTokens.APE ? ((await withSaveAndVerify( @@ -933,12 +933,6 @@ export const deployPoolComponents = async ( provider, cApe.address, allTokens.APE.address, - allTokens.USDC.address, - (await getUniswapV3SwapRouter()).address, - allTokens.WETH.address, - APE_WETH_FEE, - WETH_USDC_FEE, - treasuryAddress, (await getParaApeStaking()).address, ], verify, @@ -2711,6 +2705,65 @@ export const deployAutoCompoundApeImplAndAssignItToProxy = async ( ); }; +export const deployP2PPairStakingImpl = async (verify?: boolean) => { + const allTokens = await getAllTokens(); + const protocolDataProvider = await getProtocolDataProvider(); + const nBAYC = ( + await protocolDataProvider.getReserveTokensAddresses(allTokens.BAYC.address) + ).xTokenAddress; + const nMAYC = ( + await protocolDataProvider.getReserveTokensAddresses(allTokens.MAYC.address) + ).xTokenAddress; + const nBAKC = ( + await protocolDataProvider.getReserveTokensAddresses(allTokens.BAKC.address) + ).xTokenAddress; + const apeCoinStaking = + (await getContractAddressInDb(eContractid.ApeCoinStaking)) || + (await deployApeCoinStaking(verify)).address; + const args = [ + allTokens.BAYC.address, + allTokens.MAYC.address, + allTokens.BAKC.address, + nBAYC, + nMAYC, + nBAKC, + allTokens.APE.address, + allTokens.cAPE.address, + apeCoinStaking, + ]; + + return withSaveAndVerify( + await getContractFactory("P2PPairStaking"), + eContractid.P2PPairStakingImpl, + [...args], + verify + ) as Promise; +}; + +export const deployP2PPairStaking = async (verify?: boolean) => { + const p2pImplementation = await deployP2PPairStakingImpl(verify); + + const deployer = await getFirstSigner(); + const deployerAddress = await deployer.getAddress(); + + const initData = p2pImplementation.interface.encodeFunctionData("initialize"); + + const proxyInstance = await withSaveAndVerify( + await getContractFactory("InitializableAdminUpgradeabilityProxy"), + eContractid.P2PPairStaking, + [], + verify + ); + + await waitForTx( + await (proxyInstance as InitializableAdminUpgradeabilityProxy)[ + "initialize(address,address,bytes)" + ](p2pImplementation.address, deployerAddress, initData, GLOBAL_OVERRIDES) + ); + + return await getP2PPairStaking(proxyInstance.address); +}; + export const deployApeStakingP2PLogic = async (verify?: boolean) => withSaveAndVerify( await getContractFactory("ApeStakingP2PLogic"), diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index 195f4c45d..6d195a270 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -102,6 +102,7 @@ import { PoolMarketplace__factory, Account__factory, AccountRegistry__factory, + P2PPairStaking__factory, } from "../types"; import { getEthersSigners, @@ -1137,6 +1138,17 @@ export const getAutoYieldApe = async (address?: tEthereumAddress) => await getFirstSigner() ); +export const getP2PPairStaking = async (address?: tEthereumAddress) => + await P2PPairStaking__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.P2PPairStaking}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); + export const getParaApeStaking = async (address?: tEthereumAddress) => await ParaApeStaking__factory.connect( address || diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index b306e1ef5..4be05e313 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -3,6 +3,7 @@ import { ERC20TokenContractId, ERC721TokenContractId, IReserveParams, + NTokenContractId, tEthereumAddress, } from "./types"; import {chunk, waitForTx} from "./misc-utils"; @@ -448,8 +449,20 @@ export const initReservesByHelper = async ( variableDebtTokenToUse = astETHVariableDebtTokenImplementationAddress; } else if (reserveSymbol === ERC20TokenContractId.sAPE) { if (!pTokenSApeImplementationAddress) { + const protocolDataProvider = await getProtocolDataProvider(); + const allTokens = await protocolDataProvider.getAllXTokens(); + const nBAYC = + // eslint-disable-next-line + allTokens.find( + (x) => x.symbol == NTokenContractId.nBAYC + )!.tokenAddress; + const nMAYC = + // eslint-disable-next-line + allTokens.find( + (x) => x.symbol == NTokenContractId.nMAYC + )!.tokenAddress; pTokenSApeImplementationAddress = ( - await deployPTokenSApe(pool.address, verify) + await deployPTokenSApe(pool.address, nBAYC, nMAYC, verify) ).address; } xTokenToUse = pTokenSApeImplementationAddress; @@ -542,9 +555,27 @@ export const initReservesByHelper = async ( xTokenToUse = nTokenMAYCImplementationAddress; } else if (reserveSymbol === ERC721TokenContractId.BAKC) { if (!nTokenBAKCImplementationAddress) { + const apeCoinStaking = + (await getContractAddressInDb(eContractid.ApeCoinStaking)) || + (await deployApeCoinStaking(verify)).address; + const protocolDataProvider = await getProtocolDataProvider(); + const allTokens = await protocolDataProvider.getAllXTokens(); + const nBAYC = + // eslint-disable-next-line + allTokens.find( + (x) => x.symbol == NTokenContractId.nBAYC + )!.tokenAddress; + const nMAYC = + // eslint-disable-next-line + allTokens.find( + (x) => x.symbol == NTokenContractId.nMAYC + )!.tokenAddress; nTokenBAKCImplementationAddress = ( await deployNTokenBAKCImpl( pool.address, + apeCoinStaking, + nBAYC, + nMAYC, delegationRegistryAddress, verify ) diff --git a/helpers/types.ts b/helpers/types.ts index fd14e666e..0a0185e49 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -242,6 +242,7 @@ export enum eContractid { StKSMDebtToken = "StKSMDebtToken", CApeDebtToken = "CApeDebtToken", AStETHDebtToken = "AStETHDebtToken", + ApeStakingLogic = "ApeStakingLogic", MintableERC721Logic = "MintableERC721Logic", MerkleVerifier = "MerkleVerifier", ExecutionDelegate = "ExecutionDelegate", diff --git a/scripts/deployments/steps/20_paraApeStaking.ts b/scripts/deployments/steps/20_paraApeStaking.ts index 5398da6cc..3e1b14574 100644 --- a/scripts/deployments/steps/20_paraApeStaking.ts +++ b/scripts/deployments/steps/20_paraApeStaking.ts @@ -1,14 +1,23 @@ import { + deployP2PPairStaking, deployParaApeStaking, deployParaApeStakingImpl, } from "../../../helpers/contracts-deployments"; import { + getAllTokens, getFirstSigner, getInitializableAdminUpgradeabilityProxy, + getNTokenBAKC, + getNTokenBAYC, + getNTokenMAYC, getParaApeStaking, + getPoolProxy, } from "../../../helpers/contracts-getters"; import {getParaSpaceConfig, waitForTx} from "../../../helpers/misc-utils"; -import {ERC20TokenContractId} from "../../../helpers/types"; +import { + ERC20TokenContractId, + ERC721TokenContractId, +} from "../../../helpers/types"; import {GLOBAL_OVERRIDES} from "../../../helpers/hardhat-constants"; import {InitializableAdminUpgradeabilityProxy} from "../../../types"; @@ -19,6 +28,61 @@ export const step_20 = async (verify = false) => { return; } + // deploy P2PPairStaking + const p2pPairStaking = await deployP2PPairStaking(verify); + const allTokens = await getAllTokens(); + const pool = await getPoolProxy(); + + const bayc = allTokens[ERC721TokenContractId.BAYC]; + const mayc = allTokens[ERC721TokenContractId.MAYC]; + const bakc = allTokens[ERC721TokenContractId.BAKC]; + + if (bayc) { + const nBAYC = await getNTokenBAYC( + ( + await pool.getReserveData(bayc.address) + ).xTokenAddress + ); + await waitForTx( + await nBAYC.setApprovalForAllTo( + bayc.address, + p2pPairStaking.address, + true + ) + ); + } + + if (mayc) { + const nMAYC = await getNTokenMAYC( + ( + await pool.getReserveData(mayc.address) + ).xTokenAddress + ); + await waitForTx( + await nMAYC.setApprovalForAllTo( + mayc.address, + p2pPairStaking.address, + true + ) + ); + } + + if (bakc) { + const nBAKC = await getNTokenBAKC( + ( + await pool.getReserveData(bakc.address) + ).xTokenAddress + ); + await waitForTx( + await nBAKC.setApprovalForAllTo( + bakc.address, + p2pPairStaking.address, + true + ) + ); + } + + //deploy ParaApeStaking const paraApeStaking = await getParaApeStaking(); //upgrade to non-fake implementation if (paraApeStaking) { diff --git a/scripts/upgrade/P2PPairStaking.ts b/scripts/upgrade/P2PPairStaking.ts new file mode 100644 index 000000000..042a37ff9 --- /dev/null +++ b/scripts/upgrade/P2PPairStaking.ts @@ -0,0 +1,35 @@ +import {deployP2PPairStakingImpl} from "../../helpers/contracts-deployments"; +import { + getInitializableAdminUpgradeabilityProxy, + getP2PPairStaking, +} from "../../helpers/contracts-getters"; +import {dryRunEncodedData} from "../../helpers/contracts-helpers"; +import {DRY_RUN, GLOBAL_OVERRIDES} from "../../helpers/hardhat-constants"; +import {waitForTx} from "../../helpers/misc-utils"; + +export const upgradeP2PPairStaking = async (verify = false) => { + console.time("deploy P2PPairStaking"); + const p2pPairStakingImpl = await deployP2PPairStakingImpl(verify); + const p2pPairStaking = await getP2PPairStaking(); + const p2pPairStakingProxy = await getInitializableAdminUpgradeabilityProxy( + p2pPairStaking.address + ); + console.timeEnd("deploy P2PPairStaking"); + + console.time("upgrade P2PPairStaking"); + if (DRY_RUN) { + const encodedData = p2pPairStakingProxy.interface.encodeFunctionData( + "upgradeTo", + [p2pPairStakingImpl.address] + ); + await dryRunEncodedData(p2pPairStakingProxy.address, encodedData); + } else { + await waitForTx( + await p2pPairStakingProxy.upgradeTo( + p2pPairStakingImpl.address, + GLOBAL_OVERRIDES + ) + ); + } + console.timeEnd("upgrade P2PPairStaking"); +}; diff --git a/scripts/upgrade/ntoken.ts b/scripts/upgrade/ntoken.ts index 6e8efb9eb..0dee68fd6 100644 --- a/scripts/upgrade/ntoken.ts +++ b/scripts/upgrade/ntoken.ts @@ -106,8 +106,26 @@ export const upgradeNToken = async (verify = false) => { } else if (xTokenType == XTokenType.NTokenBAKC) { if (!nTokenBAKCImplementationAddress) { console.log("deploy NTokenBAKC implementation"); + const apeCoinStaking = await getApeCoinStaking(); + const nBAYC = + // eslint-disable-next-line + allXTokens.find( + (x) => x.symbol == NTokenContractId.nBAYC + )!.tokenAddress; + const nMAYC = + // eslint-disable-next-line + allXTokens.find( + (x) => x.symbol == NTokenContractId.nMAYC + )!.tokenAddress; nTokenBAKCImplementationAddress = ( - await deployNTokenBAKCImpl(pool.address, delegationRegistry, verify) + await deployNTokenBAKCImpl( + pool.address, + apeCoinStaking.address, + nBAYC, + nMAYC, + delegationRegistry, + verify + ) ).address; } newImpl = nTokenBAKCImplementationAddress; diff --git a/scripts/upgrade/ptoken.ts b/scripts/upgrade/ptoken.ts index 764eae5f6..a8c7b0094 100644 --- a/scripts/upgrade/ptoken.ts +++ b/scripts/upgrade/ptoken.ts @@ -14,7 +14,11 @@ import { getProtocolDataProvider, getPToken, } from "../../helpers/contracts-getters"; -import {PTokenContractId, XTokenType} from "../../helpers/types"; +import { + NTokenContractId, + PTokenContractId, + XTokenType, +} from "../../helpers/types"; import dotenv from "dotenv"; import { @@ -88,8 +92,18 @@ export const upgradePToken = async (verify = false) => { } else if (xTokenType == XTokenType.PTokenSApe) { if (!pTokenSApeImplementationAddress) { console.log("deploy PTokenSApe implementation"); + const nBAYC = + // eslint-disable-next-line + allXTokens.find( + (x) => x.symbol == NTokenContractId.nBAYC + )!.tokenAddress; + const nMAYC = + // eslint-disable-next-line + allXTokens.find( + (x) => x.symbol == NTokenContractId.nMAYC + )!.tokenAddress; pTokenSApeImplementationAddress = ( - await deployPTokenSApe(poolAddress, verify) + await deployPTokenSApe(poolAddress, nBAYC, nMAYC, verify) ).address; } newImpl = pTokenSApeImplementationAddress; diff --git a/test/_pool_ape_staking.spec.ts b/test/_pool_ape_staking.spec.ts new file mode 100644 index 000000000..fc0ab19d4 --- /dev/null +++ b/test/_pool_ape_staking.spec.ts @@ -0,0 +1,2985 @@ +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect} from "chai"; +import {MAX_UINT_AMOUNT, ZERO_ADDRESS, ONE_ADDRESS} from "../helpers/constants"; +import { + getAutoCompoundApe, + getPToken, + getPTokenSApe, + getVariableDebtToken, +} from "../helpers/contracts-getters"; +import { + convertToCurrencyDecimals, + isUsingAsCollateral, +} from "../helpers/contracts-helpers"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; +import {VariableDebtToken, PTokenSApe, PToken, AutoCompoundApe} from "../types"; +import {TestEnv} from "./helpers/make-suite"; +import {testEnvFixture} from "./helpers/setup-env"; + +import { + borrowAndValidate, + changePriceAndValidate, + changeSApePriceAndValidate, + mintAndValidate, + supplyAndValidate, +} from "./helpers/validated-steps"; +import {almostEqual} from "./helpers/uniswapv3-helper"; +import {ProtocolErrors} from "../helpers/types"; +import {parseEther} from "ethers/lib/utils"; +import { + executeAcceptBidWithCredit, + executeSeaportBuyWithCredit, +} from "./helpers/marketplace-helper"; +import {BigNumber} from "ethers"; + +describe("APE Coin Staking Test", () => { + let testEnv: TestEnv; + let variableDebtApeCoin: VariableDebtToken; + let variableDebtCApeCoin: VariableDebtToken; + let pApeCoin: PToken; + let cApe: AutoCompoundApe; + let pcApeCoin: PToken; + let pSApeCoin: PTokenSApe; + const sApeAddress = ONE_ADDRESS; + const InitialNTokenApeBalance = parseEther("100"); + + const fixture = async () => { + testEnv = await loadFixture(testEnvFixture); + const { + ape, + mayc, + bayc, + users, + bakc, + protocolDataProvider, + pool, + apeCoinStaking, + nMAYC, + nBAYC, + } = testEnv; + const user1 = users[0]; + const depositor = users[1]; + const user4 = users[5]; + + const { + xTokenAddress: pApeCoinAddress, + variableDebtTokenAddress: variableDebtApeCoinAddress, + } = await protocolDataProvider.getReserveTokensAddresses(ape.address); + const {xTokenAddress: pSApeCoinAddress} = + await protocolDataProvider.getReserveTokensAddresses(sApeAddress); + + cApe = await getAutoCompoundApe(); + const { + xTokenAddress: pcApeCoinAddress, + variableDebtTokenAddress: variableDebtCApeCoinAddress, + } = await protocolDataProvider.getReserveTokensAddresses(cApe.address); + + variableDebtApeCoin = await getVariableDebtToken( + variableDebtApeCoinAddress + ); + variableDebtCApeCoin = await getVariableDebtToken( + variableDebtCApeCoinAddress + ); + pApeCoin = await getPToken(pApeCoinAddress); + pSApeCoin = await getPTokenSApe(pSApeCoinAddress); + pcApeCoin = await getPToken(pcApeCoinAddress); + + await supplyAndValidate(ape, "20000", depositor, true); + await changePriceAndValidate(ape, "0.001"); + await changePriceAndValidate(cApe, "0.001"); + await changeSApePriceAndValidate(sApeAddress, "0.001"); + + await changePriceAndValidate(mayc, "50"); + await changePriceAndValidate(bayc, "50"); + + await waitForTx(await bakc["mint(uint256,address)"]("2", user1.address)); + + await waitForTx( + await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await bakc.connect(user1.signer).setApprovalForAll(pool.address, true) + ); + + // send extra tokens to the apestaking contract for rewards + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"]( + apeCoinStaking.address, + parseEther("100000000000") + ) + ); + + // send extra tokens to the nToken contract for testing ape balance check + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"](nMAYC.address, InitialNTokenApeBalance) + ); + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"](nBAYC.address, InitialNTokenApeBalance) + ); + + await mintAndValidate(ape, "1", user4); + await waitForTx( + await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + // user4 deposit MINIMUM_LIQUIDITY to make test case easy + const MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + await waitForTx( + await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) + ); + + return testEnv; + }; + + it("TC-pool-ape-staking-01 test borrowApeAndStake: failed when borrow + cash < staking amount (revert expected)", async () => { + const { + users: [user1], + ape, + mayc, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "16000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "16000"); + await expect( + pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ).to.be.revertedWith(ProtocolErrors.TOTAL_STAKING_AMOUNT_WRONG); + }); + + it("TC-pool-ape-staking-02 test borrowApeAndStake: failed when borrow + cash > staking amount (revert expected)", async () => { + const { + users: [user1], + ape, + mayc, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "16000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "16000"); + await expect( + pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ).to.be.revertedWith(ProtocolErrors.TOTAL_STAKING_AMOUNT_WRONG); + }); + + it("TC-pool-ape-staking-03 test borrowApeAndStake: use 100% cash", async () => { + const { + users: [user1], + ape, + mayc, + pool, + nMAYC, + weth, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "15000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); + expect(apeDebt).equal(0); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + const userAccount = await pool.getUserAccountData(user1.address); + //50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "65") + ); + expect(userAccount.totalDebtBase).equal(0); + //50 * 0.325 + 15 * 0.2 = 19.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(weth.address, "19.25") + // ); + }); + + it("TC-pool-ape-staking-04 test borrowApeAndStake: part cash, part debt", async () => { + const { + users: [user1], + ape, + mayc, + pool, + nMAYC, + weth, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "7000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + // 50 * 0.3250 + 7000 * 0.001 * 0.2 = 17.65 + // 17.65 / 0.001 = 17650 + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); + expect(apeDebt).equal(amount2); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + const userAccount = await pool.getUserAccountData(user1.address); + //50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "65") + ); + //8000*0.001 = 8 + expect(userAccount.totalDebtBase).equal( + await convertToCurrencyDecimals(weth.address, "8") + ); + //50 * 0.325 + 15 * 0.2 - 8=11.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(weth.address, "11.25") + // ); + }); + + it("TC-pool-ape-staking-05 test borrowApeAndStake: use 100% debt", async () => { + const { + users: [user1], + ape, + mayc, + pool, + nMAYC, + weth, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); + expect(apeDebt).equal(amount); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + const userAccount = await pool.getUserAccountData(user1.address); + //50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "65") + ); + //15000*0.001 = 15 + expect(userAccount.totalDebtBase).equal( + await convertToCurrencyDecimals(weth.address, "15") + ); + //50 * 0.325 + 15 * 0.2 - 15=4.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(weth.address, "4.25") + // ); + }); + + it("TC-pool-ape-staking-06 test withdrawBAKC fails when hf < 1 (revert expected)", async () => { + const { + users: [user1], + ape, + mayc, + pool, + weth, + nMAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await changePriceAndValidate(mayc, "40"); + await changePriceAndValidate(ape, "0.002"); + await changeSApePriceAndValidate(sApeAddress, "0.002"); + const userAccount = await pool.getUserAccountData(user1.address); + //40 + 15000*0.002 = 70 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "70") + ); + //15000*0.002 = 30 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(weth.address, "30") + ); + //40 * 0.325 + 30 * 0.2 - 30=-11 + almostEqual(userAccount.availableBorrowsBase, 0); + + let withdrawAmount = await convertToCurrencyDecimals(ape.address, "3000"); + expect( + await pool + .connect(user1.signer) + .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: withdrawAmount}]) + ); + withdrawAmount = await convertToCurrencyDecimals(ape.address, "4000"); + expect( + await pool + .connect(user1.signer) + .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: withdrawAmount}]) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount2); + + await expect( + pool + .connect(user1.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, + ]) + ).to.be.revertedWith( + ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD + ); + }); + + it("TC-pool-ape-staking-07 test withdrawApeCoin fails when hf < 1 (revert expected)", async () => { + const { + users: [user1], + ape, + mayc, + pool, + weth, + nMAYC, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await changePriceAndValidate(mayc, "40"); + await changePriceAndValidate(ape, "0.002"); + await changeSApePriceAndValidate(sApeAddress, "0.002"); + const userAccount = await pool.getUserAccountData(user1.address); + //40 + 15000*0.002 = 70 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "70") + ); + //15000*0.002 = 30 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(weth.address, "30") + ); + //40 * 0.325 + 30 * 0.2 - 30=-11 + almostEqual(userAccount.availableBorrowsBase, 0); + + const withdrawAmount = await convertToCurrencyDecimals(ape.address, "4000"); + expect( + await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: withdrawAmount, + isUncommit: false, + }, + ]) + ); + expect( + await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: withdrawAmount, + isUncommit: true, + }, + ]) + ); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount1); + + await expect( + pool + .connect(user1.signer) + .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) + ).to.be.revertedWith( + ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD + ); + + const apeBalanceForNToken = await ape.balanceOf(nMAYC.address); + expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); + }); + + it("TC-pool-ape-staking-08 test withdrawBAKC fails when hf < 1 (revert expected)", async () => { + const { + users: [user1, user2], + ape, + mayc, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await expect( + pool + .connect(user2.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, + ]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + }); + + it("TC-pool-ape-staking-09 test withdrawApeCoin fails when user is not the owner (revert expected)", async () => { + const { + users: [user1, user2], + ape, + mayc, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await expect( + pool + .connect(user2.signer) + .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + }); + + it("TC-pool-ape-staking-10 test claimBAKC success when hf > 1", async () => { + const { + users: [user1], + ape, + mayc, + pool, + weth, + nMAYC, + apeCoinStaking, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}] + ) + ); + + await changePriceAndValidate(mayc, "40"); + await changePriceAndValidate(ape, "0.002"); + await changeSApePriceAndValidate(sApeAddress, "0.002"); + const userAccount = await pool.getUserAccountData(user1.address); + //40 + 15000*0.002 = 70 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "70") + ); + //15000*0.002 = 30 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(weth.address, "30") + ); + //40 * 0.325 + 30 * 0.2 - 30=-11 + almostEqual(userAccount.availableBorrowsBase, 0); + + // advance in time + await advanceTimeAndBlock(parseInt("86400")); + + const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( + 2, + nMAYC.address, + "0" + ); + expect(pendingRewardsPool2).to.be.gt(0); + + const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( + 3, + nMAYC.address, + "1" + ); + expect(pendingRewardsPool3).to.be.gt(0); + + const userBalance = await ape.balanceOf(user1.address); + + expect( + await pool + .connect(user1.signer) + .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 1}]) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount.add(pendingRewardsPool2)); + + expect(await ape.balanceOf(user1.address)).to.be.eq( + userBalance.add(pendingRewardsPool3) + ); + + const apeBalanceForNToken = await ape.balanceOf(nMAYC.address); + expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); + }); + + it("TC-pool-ape-staking-11 test claimBAKC success when hf < 1 (ape reward for bakc pool is not used as collateral)", async () => { + const { + users: [user1], + ape, + mayc, + pool, + weth, + apeCoinStaking, + nMAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}] + ) + ); + + await changePriceAndValidate(mayc, "40"); + await changePriceAndValidate(ape, "0.002"); + await changeSApePriceAndValidate(sApeAddress, "0.002"); + const userAccount = await pool.getUserAccountData(user1.address); + //40 + 15000*0.002 = 70 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "70") + ); + //15000*0.002 = 30 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(weth.address, "30") + ); + //40 * 0.325 + 30 * 0.2 - 30=-11 + almostEqual(userAccount.availableBorrowsBase, 0); + // advance in time + await advanceTimeAndBlock(parseInt("86400")); + + const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( + 2, + nMAYC.address, + "0" + ); + expect(pendingRewardsPool2).to.be.gt(0); + + const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( + 3, + nMAYC.address, + "1" + ); + expect(pendingRewardsPool3).to.be.gt(0); + + const userBalance = await ape.balanceOf(user1.address); + + // drop HF to liquidation levels + await changePriceAndValidate(mayc, "3"); + + expect( + await pool + .connect(user1.signer) + .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 1}]) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount.add(pendingRewardsPool2)); + + expect(await ape.balanceOf(user1.address)).to.be.eq( + userBalance.add(pendingRewardsPool3) + ); + }); + + it("TC-pool-ape-staking-12 test claimApeCoin succeeds when hf > 1", async () => { + const { + users: [user1], + ape, + mayc, + pool, + weth, + nMAYC, + apeCoinStaking, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await changePriceAndValidate(mayc, "40"); + await changePriceAndValidate(ape, "0.002"); + await changeSApePriceAndValidate(sApeAddress, "0.002"); + const userAccount = await pool.getUserAccountData(user1.address); + //40 + 15000*0.002 = 70 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "70") + ); + //15000*0.002 = 30 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(weth.address, "30") + ); + //40 * 0.325 + 30 * 0.2 - 30=-11 + almostEqual(userAccount.availableBorrowsBase, 0); + + // advance in time + await advanceTimeAndBlock(parseInt("86400")); + + const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( + 2, + nMAYC.address, + "0" + ); + + const userBalance = await ape.balanceOf(user1.address); + + expect(await pool.connect(user1.signer).claimApeCoin(mayc.address, [0])); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + expect(await ape.balanceOf(user1.address)).to.be.eq( + userBalance.add(pendingRewardsPool2) + ); + }); + + it("TC-pool-ape-staking-13 test claimApeCoin fails when hf < 1 (revert expected)", async () => { + const { + users: [user1], + ape, + mayc, + pool, + weth, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await changePriceAndValidate(mayc, "40"); + await changePriceAndValidate(ape, "0.002"); + await changeSApePriceAndValidate(sApeAddress, "0.002"); + const userAccount = await pool.getUserAccountData(user1.address); + //40 + 15000*0.002 = 70 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "70") + ); + //15000*0.002 = 30 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(weth.address, "30") + ); + //40 * 0.325 + 30 * 0.2 - 30=-11 + almostEqual(userAccount.availableBorrowsBase, 0); + + // drop HF to liquidation levels + await changePriceAndValidate(mayc, "3"); + + await expect( + pool.connect(user1.signer).claimApeCoin(mayc.address, [0]) + ).to.be.revertedWith( + ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD + ); + }); + + it("TC-pool-ape-staking-14 test unstakeApePositionAndRepay repays cape debt - no excess", async () => { + const { + users: [user1, user2], + ape, + mayc, + pool, + nMAYC, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "20000", user2); + await waitForTx( + await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user2.signer) + .deposit(user2.address, parseEther("20000")) + ); + await waitForTx( + await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await pool + .connect(user2.signer) + .supply(cApe.address, parseEther("20000"), user2.address, 0) + ); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: cApe.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + expect( + await pool + .connect(user1.signer) + .unstakeApePositionAndRepay(mayc.address, 0) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(0); + + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(0); + + const pApeBalance = await pApeCoin.balanceOf(user1.address); + expect(pApeBalance).equal(0); + + const cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); + const limit = await convertToCurrencyDecimals(cApe.address, "0.1"); + expect(cApeDebt.lt(limit)).equal(true); + }); + + it("TC-pool-ape-staking-15 test unstakeApePositionAndRepay repays cape debt", async () => { + const { + users: [user1, user2], + ape, + mayc, + pool, + nMAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "10000", user2); + await waitForTx( + await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user2.signer) + .deposit(user2.address, parseEther("10000")) + ); + await waitForTx( + await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await pool + .connect(user2.signer) + .supply(cApe.address, parseEther("10000"), user2.address, 0) + ); + + const amount1 = parseEther("7000"); + const amount2 = parseEther("8000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount1, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [] + ) + ); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: cApe.address, + borrowAmount: amount2, + cashAmount: 0, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); + almostEqual(apeDebt, amount1); + + let capeDebt = await variableDebtCApeCoin.balanceOf(user1.address); + almostEqual(capeDebt, amount2); + + expect( + await pool + .connect(user1.signer) + .unstakeApePositionAndRepay(mayc.address, 0) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(0); + + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(0); + + const pCApeBalance = await pcApeCoin.balanceOf(user1.address); + almostEqual(pCApeBalance, amount1); + + capeDebt = await variableDebtCApeCoin.balanceOf(user1.address); + expect(capeDebt).equal(0); + }); + + it("TC-pool-ape-staking-16 test unstakeApePositionAndRepay bakc reward should transfer to user wallet", async () => { + const { + users: [user1], + ape, + mayc, + pool, + apeCoinStaking, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "7000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + // advance in time + await advanceTimeAndBlock(parseInt("86400")); + + const pendingRewardsMaycPool = await apeCoinStaking.pendingRewards( + 2, + ZERO_ADDRESS, + "0" + ); + expect(pendingRewardsMaycPool).to.be.gt(0); + const pendingRewardsBakcPool = await apeCoinStaking.pendingRewards( + 3, + ZERO_ADDRESS, + "0" + ); + expect(pendingRewardsBakcPool).to.be.gt(0); + + expect( + await pool + .connect(user1.signer) + .unstakeApePositionAndRepay(mayc.address, 0) + ); + + const userBalance = await ape.balanceOf(user1.address); + + expect(userBalance).to.be.eq(pendingRewardsBakcPool); + }); + + it("TC-pool-ape-staking-17 test unstakeApePositionAndRepay by others fails when hf > 1(revert expected)", async () => { + const { + users: [user1, unstaker], + ape, + mayc, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await expect( + pool.connect(unstaker.signer).unstakeApePositionAndRepay(mayc.address, 0) + ).to.be.revertedWith(ProtocolErrors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD); + }); + + it("TC-pool-ape-staking-18 test unstakeApePositionAndRepay by others succeeds when hf < 1", async () => { + const { + users: [user1, unstaker, , , user2], + ape, + mayc, + pool, + nMAYC, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "20000", user2); + await waitForTx( + await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user2.signer) + .deposit(user2.address, parseEther("20000")) + ); + await waitForTx( + await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await pool + .connect(user2.signer) + .supply(cApe.address, parseEther("20000"), user2.address, 0) + ); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: cApe.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await changePriceAndValidate(mayc, "40"); + await changePriceAndValidate(cApe, "0.08"); + + expect( + await pool + .connect(unstaker.signer) + .unstakeApePositionAndRepay(mayc.address, 0) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(0); + + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(0); + + const pcApeBalance = await pcApeCoin.balanceOf(user1.address); + expect(pcApeBalance).equal(0); + + const cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); + const target = await convertToCurrencyDecimals(cApe.address, "45"); + almostEqual(cApeDebt, target); + }); + + it("TC-pool-ape-staking-19 test can stake multiple times and partially unstake afterwards", async () => { + const { + users: [user1, unstaker, , , user2], + ape, + mayc, + bayc, + pool, + nMAYC, + nBAYC, + weth, + bakc, + } = await loadFixture(fixture); + + await mintAndValidate(ape, "30000", user2); + await waitForTx( + await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user2.signer) + .deposit(user2.address, parseEther("30000")) + ); + await waitForTx( + await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await pool + .connect(user2.signer) + .supply(cApe.address, parseEther("30000"), user2.address, 0) + ); + + await supplyAndValidate(mayc, "2", user1, true); + await supplyAndValidate(bayc, "2", user1, true); + + const amount = await convertToCurrencyDecimals(cApe.address, "3000"); + const halfAmount = await convertToCurrencyDecimals(cApe.address, "9000"); + const totalAmount = await convertToCurrencyDecimals(cApe.address, "18000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: cApe.address, + borrowAmount: halfAmount, + cashAmount: 0, + }, + [ + {tokenId: 0, amount: amount}, + {tokenId: 1, amount: amount}, + ], + [{mainTokenId: 1, bakcTokenId: 0, amount: amount}] + ) + ); + + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: cApe.address, + borrowAmount: halfAmount, + cashAmount: 0, + }, + [ + {tokenId: 0, amount: amount}, + {tokenId: 1, amount: amount}, + ], + [{mainTokenId: 1, bakcTokenId: 1, amount: amount}] + ) + ); + + let maycStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(maycStake).equal(halfAmount); + + let baycStake = await nBAYC.getUserApeStakingAmount(user1.address); + expect(baycStake).equal(halfAmount); + + let pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(totalAmount); + + let cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); + almostEqual(cApeDebt, totalAmount); + + let bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + let userAccount = await pool.getUserAccountData(user1.address); + //50 * 4 + 18000*0.001 = 218 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "218") + ); + //18000*0.001 = 18 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(weth.address, "18") + ); + //50 * 2 * 0.4 + 50 * 2 * 0.325 + 18 * 0.2 - 18 = 58.1 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(weth.address, "58.1") + // ); + + await changePriceAndValidate(mayc, "10"); + await changePriceAndValidate(bayc, "10"); + await changePriceAndValidate(cApe, "0.01"); + await changeSApePriceAndValidate(sApeAddress, "0.01"); + + expect( + await pool + .connect(unstaker.signer) + .unstakeApePositionAndRepay(mayc.address, 1) + ); + + maycStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(maycStake).equal(amount); + + baycStake = await nBAYC.getUserApeStakingAmount(user1.address); + expect(baycStake).equal(halfAmount); + + pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount.add(halfAmount)); + + cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); + //12000 + 6000*3/1000 + almostEqual( + cApeDebt, + amount + .add(halfAmount) + .add(await convertToCurrencyDecimals(weth.address, "18")) + ); + + bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + userAccount = await pool.getUserAccountData(user1.address); + //10 * 4 + 12000*0.01 = 160 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "160") + ); + //12018*0.01 = 120.18 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(weth.address, "120.18") + ); + + let apeBalanceForNToken = await ape.balanceOf(nMAYC.address); + expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); + apeBalanceForNToken = await ape.balanceOf(nBAYC.address); + expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); + }); + + it("TC-pool-ape-staking-20 test can liquidate NFT with existing staking positions", async () => { + const { + users: [user1, liquidator], + ape, + mayc, + pool, + weth, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8"); + const amount = await convertToCurrencyDecimals(ape.address, "7008"); + + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const borrowAmount = await convertToCurrencyDecimals(ape.address, "8000"); + expect( + await pool + .connect(user1.signer) + .borrow(ape.address, borrowAmount, 0, user1.address) + ); + + await supplyAndValidate(weth, "91", liquidator, true, "200000"); + + // drop HF and ERC-721_HF below 1 + await changePriceAndValidate(mayc, "3"); + + // start auction + await waitForTx( + await pool + .connect(liquidator.signer) + .startAuction(user1.address, mayc.address, 0) + ); + + const apeDebtBefore = await variableDebtApeCoin.balanceOf(user1.address); + + // try to liquidate the NFT + expect( + await pool + .connect(liquidator.signer) + .liquidateERC721( + mayc.address, + user1.address, + 0, + await convertToCurrencyDecimals(weth.address, "13"), + false, + {gasLimit: 5000000} + ) + ); + + expect(await ape.balanceOf(user1.address)).to.be.eq(borrowAmount); + + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).to.be.eq(0); // whole position unstaked + + const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); + almostEqual(apeDebt, apeDebtBefore); // no debt repaid + + expect(await bakc.ownerOf("0")).to.be.eq(user1.address); + expect(await mayc.ownerOf("0")).to.be.eq(liquidator.address); + }); + + it("TC-pool-ape-staking-21 test cannot borrow and stake an amount over user's available to borrow (revert expected)", async () => { + const { + users: [user1, depositor], + ape, + mayc, + pool, + weth, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(weth, "5", depositor, true); + await changePriceAndValidate(mayc, "10"); + await borrowAndValidate(weth, "3", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + + await expect( + pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ).to.be.revertedWith(ProtocolErrors.COLLATERAL_CANNOT_COVER_NEW_BORROW); + }); + + it("TC-pool-ape-staking-22 test can transfer NFT with existing staking positions", async () => { + const { + users: [user1, user2], + ape, + mayc, + pool, + nMAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "15000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + expect(await nMAYC.balanceOf(user1.address)).to.be.equal(1); + expect(await nMAYC.balanceOf(user2.address)).to.be.equal(0); + expect(await pSApeCoin.balanceOf(user1.address)).equal(amount); + expect(await pSApeCoin.balanceOf(user2.address)).equal(0); + + expect( + await nMAYC + .connect(user1.signer) + ["safeTransferFrom(address,address,uint256)"]( + user1.address, + user2.address, + 0, + {gasLimit: 5000000} + ) + ); + + expect(await nMAYC.balanceOf(user1.address)).to.be.equal(0); + expect(await nMAYC.balanceOf(user2.address)).to.be.equal(1); + expect(await pSApeCoin.balanceOf(user1.address)).equal(0); + expect(await pSApeCoin.balanceOf(user2.address)).equal(0); + }); + + it("TC-pool-ape-staking-23 test market accept bid offer should success", async () => { + const { + bayc, + nBAYC, + usdc, + pool, + ape, + users: [taker, maker, middleman], + } = await loadFixture(fixture); + const makerInitialBalance = "800"; + const middlemanInitialBalance = "200"; + const payNowAmount = await convertToCurrencyDecimals(usdc.address, "800"); + const creditAmount = await convertToCurrencyDecimals(usdc.address, "200"); + + const startAmount = payNowAmount.add(creditAmount); + const endAmount = startAmount; // fixed price but offerer cannot afford this + const nftId = 0; + + // 1, mint USDC to maker + await mintAndValidate(usdc, makerInitialBalance, maker); + + // 2, middleman supplies USDC to pool to be borrowed by maker later + await supplyAndValidate(usdc, middlemanInitialBalance, middleman, true); + + // 3, mint ntoken for taker + await mintAndValidate(ape, "15000", taker); + await supplyAndValidate(bayc, "1", taker, true); + + // 4, ape staking for ntoken + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(taker.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + expect(await nBAYC.balanceOf(taker.address)).to.be.equal(1); + expect(await nBAYC.balanceOf(maker.address)).to.be.equal(0); + expect(await pSApeCoin.balanceOf(taker.address)).equal(amount); + expect(await pSApeCoin.balanceOf(maker.address)).equal(0); + + // 5, accept order + await executeAcceptBidWithCredit( + nBAYC, + usdc, + startAmount, + endAmount, + creditAmount, + nftId, + maker, + taker + ); + + // taker bayc should reduce + expect(await nBAYC.balanceOf(taker.address)).to.be.equal(0); + expect(await nBAYC.balanceOf(maker.address)).to.be.equal(1); + expect(await pSApeCoin.balanceOf(taker.address)).equal(0); + expect(await pSApeCoin.balanceOf(maker.address)).equal(0); + }); + + it("TC-pool-ape-staking-24 test market buy with credit should success", async () => { + const { + bayc, + nBAYC, + usdc, + pool, + ape, + users: [maker, taker, middleman], + } = await loadFixture(fixture); + const makerInitialBalance = "800"; + const middlemanInitialBalance = "200"; + const payNowAmount = await convertToCurrencyDecimals(usdc.address, "800"); + const creditAmount = await convertToCurrencyDecimals(usdc.address, "200"); + + const startAmount = payNowAmount.add(creditAmount); + const endAmount = startAmount; // fixed price but offerer cannot afford this + const nftId = 0; + + // 1, mint USDC to taker + await mintAndValidate(usdc, makerInitialBalance, taker); + + // 2, middleman supplies USDC to pool to be borrowed by taker later + await supplyAndValidate(usdc, middlemanInitialBalance, middleman, true); + + // 3, mint ntoken for maker + await mintAndValidate(ape, "15000", maker); + await supplyAndValidate(bayc, "1", maker, true); + + // 4, ape staking for ntoken + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + expect( + await pool.connect(maker.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + expect(await nBAYC.balanceOf(maker.address)).to.be.equal(1); + expect(await nBAYC.balanceOf(taker.address)).to.be.equal(0); + expect(await pSApeCoin.balanceOf(maker.address)).equal(amount); + expect(await pSApeCoin.balanceOf(taker.address)).equal(0); + + // 5, buy with credit + await waitForTx( + await usdc.connect(taker.signer).approve(pool.address, startAmount) + ); + await executeSeaportBuyWithCredit( + nBAYC, + usdc, + startAmount, + endAmount, + creditAmount, + nftId, + maker, + taker + ); + + // taker bayc should reduce + expect(await nBAYC.balanceOf(maker.address)).to.be.equal(0); + expect(await nBAYC.balanceOf(taker.address)).to.be.equal(1); + expect(await pSApeCoin.balanceOf(maker.address)).equal(0); + expect(await pSApeCoin.balanceOf(taker.address)).equal(0); + }); + + it("TC-pool-ape-staking-25 unstakeApePositionAndRepay should set cApe as collateral", async () => { + const { + users: [user1], + ape, + mayc, + pool, + } = await loadFixture(fixture); + + const apeData = await pool.getReserveData(cApe.address); + await supplyAndValidate(ape, "1", user1, true); + await waitForTx( + await pool + .connect(user1.signer) + .setUserUseERC20AsCollateral(ape.address, false) + ); + let userConfig = BigNumber.from( + (await pool.getUserConfiguration(user1.address)).data + ); + expect(isUsingAsCollateral(userConfig, apeData.id)).to.be.false; + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "7000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + expect( + await pool + .connect(user1.signer) + .unstakeApePositionAndRepay(mayc.address, 0) + ); + + userConfig = BigNumber.from( + (await pool.getUserConfiguration(user1.address)).data + ); + expect(isUsingAsCollateral(userConfig, apeData.id)).to.be.true; + }); + + it("TC-pool-ape-staking-26 test borrowApeAndStake: User tries to staking on not Supplying (revert expected)", async () => { + const { + users: [user1], + ape, + bayc, + pool, + } = await loadFixture(fixture); + + await mintAndValidate(bayc, "1", user1); + await mintAndValidate(ape, "15000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + await expect( + pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + }); + + it("TC-pool-ape-staking-27 test borrowApeAndStake: User tries to staking 0 ape icon for BAYC (revert expected)", async () => { + const { + users: [user1], + ape, + bayc, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await mintAndValidate(ape, "15000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "0"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = amount1.add(amount2); + await expect( + pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ).to.be.revertedWith("DepositMoreThanOneAPE()"); + }); + + it("TC-pool-ape-staking-28 test borrowApeAndStake: only staking BAKC", async () => { + const { + users: [user1], + ape, + bayc, + nBAYC, + pool, + weth, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + + await mintAndValidate(ape, "15000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "0"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = amount1.add(amount2); + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: amount1, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + + // User 1 - totalStake should increased in Stake amount + const totalStake = await nBAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + // User 1 - Debt should increased in borrowAmount + const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); + expect(apeDebt).equal(amount2); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + const userAccount = await pool.getUserAccountData(user1.address); + //50 + 8000*0.001 = 58 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "58") + ); + //8000*0.001 = 8 + expect(userAccount.totalDebtBase).equal( + await convertToCurrencyDecimals(weth.address, "8") + ); + + //50 * 0.4 + 8 * 0.2 - 8=13.6 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(weth.address, "13.6") + // ); + }); + + it("TC-pool-ape-staking-29 test borrowApeAndStake: BAYC staked Add BAKC after first Pairing", async () => { + const { + users: [user1], + bayc, + ape, + pool, + weth, + nBAYC, + bakc, + } = await loadFixture(fixture); + await supplyAndValidate(bayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = amount1.add(amount2); + + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"](user1.address, amount) + ); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [] + ) + ); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: 0, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + // User 1 - totalStake should increased in Stake amount + const totalStake = await nBAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + // User 1 - Debt should increased in borrowAmount + const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); + expect(apeDebt).equal(amount2); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + const userAccount = await pool.getUserAccountData(user1.address); + //50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "65") + ); + //8000*0.001 = 8 + expect(userAccount.totalDebtBase).equal( + await convertToCurrencyDecimals(weth.address, "8") + ); + //50 * 0.4 + 15 * 0.2 - 8=15 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(weth.address, "15") + // ); + }); + + it("TC-pool-ape-staking-30 test borrowApeAndStake: MAYC staked Add BAKC after first Pairing", async () => { + const { + users: [user1], + mayc, + weth, + nMAYC, + ape, + pool, + bakc, + } = await loadFixture(fixture); + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = amount1.add(amount2); + + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"](user1.address, amount) + ); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [] + ) + ); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: 0, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + // User 1 - totalStake should increased in Stake amount + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + // User 1 - Debt should increased in borrowAmount + const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); + expect(apeDebt).equal(amount2); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + const userAccount = await pool.getUserAccountData(user1.address); + //50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "65") + ); + //8000*0.001 = 8 + expect(userAccount.totalDebtBase).equal( + await convertToCurrencyDecimals(weth.address, "8") + ); + //50 * 0.325 + 15 * 0.2 - 8=11.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(weth.address, "11.25") + // ); + }); + + it("TC-pool-ape-staking-31 test borrowApeAndStake: Insufficient liquidity of borrow ape (revert expected)", async () => { + const { + users: [user1], + bayc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + + // reduce pool liquidity + await borrowAndValidate(ape, "13000", user1); + const amount1 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount = amount1.add(amount2); + + await ape + .connect(user1.signer) + ["mint(address,uint256)"](user1.address, amount); + + await expect( + pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: amount1, + cashAmount: amount2, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ).to.be.revertedWith("ERC20: transfer amount exceeds balance"); + }); + + it("TC-pool-ape-staking-32 test borrowApeAndStake: success use 100% cash when hf < 1", async () => { + const { + users: [user1, user2], + ape, + mayc, + pool, + usdt, + nMAYC, + weth, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(usdt, "1000", user2, true); + await borrowAndValidate(ape, "5000", user1); + await borrowAndValidate(usdt, "800", user1); + await mintAndValidate(ape, "7000", user1); + + const amount = await convertToCurrencyDecimals(ape.address, "7000"); + + await changePriceAndValidate(mayc, "20"); + await changePriceAndValidate(usdt, "0.0009"); + await changePriceAndValidate(ape, "0.005"); + await changeSApePriceAndValidate(sApeAddress, "0.005"); + + const healthFactor = (await pool.getUserAccountData(user1.address)) + .healthFactor; + + expect(healthFactor).to.be.lt(parseEther("1")); + + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + const healthFactorAfter = (await pool.getUserAccountData(user1.address)) + .healthFactor; + + // health factor should improve greater than 1 + expect(healthFactorAfter).to.be.gt(parseEther("1")); + + // User 1 - totalStake should increased in Stake amount + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(2); + + const userAccount = await pool.getUserAccountData(user1.address); + + //20 + 7000*0.005 = 55 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(weth.address, "55") + ); + + //5000*0.005 + 800 * 0.0009 = 25.72 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(weth.address, "25.72") + ); + + //availableBorrowsInBaseCurrency < totalDebtInBaseCurrency = 0 + almostEqual( + userAccount.availableBorrowsBase, + await convertToCurrencyDecimals(weth.address, "0") + ); + }); + + it("TC-pool-ape-staking-33 test safeTransferFrom BAKC: original owner withdraws all", async () => { + const { + users: [user1, , user3], + ape, + mayc, + pool, + nMAYC, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "7000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + const userBalance = await ape.balanceOf(user1.address); + const user3Balance = await ape.balanceOf(user3.address); + + await waitForTx( + await bakc + .connect(user1.signer) + ["safeTransferFrom(address,address,uint256)"]( + user1.address, + user3.address, + "0" + ) + ); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(1); + + const bakcBalanceUser3 = await bakc.balanceOf(user3.address); + expect(bakcBalanceUser3).equal(1); + + await waitForTx( + await pool + .connect(user1.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, + ]) + ); + + // User 1 - totalStake should have decreased in BAKC amount + const totalStakeAfter = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStakeAfter).equal(totalStake.sub(amount2)); + + // User 1 - totalStake should have increased in BAKC amount + const pSApeBalanceAfter = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalanceAfter).equal(pSApeBalance.sub(amount2)); + + // User 1 - Ape Balance should have increased in BAKC amount + const userBalanceAfter = await ape.balanceOf(user1.address); + expect(userBalanceAfter).equal(userBalance.add(amount2)); + + // User 3 - Ape Balance should remain the same + const user3BalanceAfter = await ape.balanceOf(user3.address); + expect(user3BalanceAfter).equal(user3Balance); + }); + + it("TC-pool-ape-staking-34 test safeTransferFrom BAKC: original owner withdraws part ape (revert expected)", async () => { + const { + users: [user1, , user3], + ape, + mayc, + pool, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "7000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + await waitForTx( + await bakc + .connect(user1.signer) + ["safeTransferFrom(address,address,uint256)"]( + user1.address, + user3.address, + "0" + ) + ); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(1); + + const bakcBalanceUser3 = await bakc.balanceOf(user3.address); + expect(bakcBalanceUser3).equal(1); + + // Only withdraw all + await expect( + pool + .connect(user1.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount1, isUncommit: false}, + ]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_BAKC_OWNER); + }); + + it("TC-pool-ape-staking-35 test safeTransferFrom BAKC: original owner claim bakc reward (revert expected)", async () => { + const { + users: [user1, , user3], + ape, + mayc, + pool, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "7000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + await waitForTx( + await bakc + .connect(user1.signer) + ["safeTransferFrom(address,address,uint256)"]( + user1.address, + user3.address, + "0" + ) + ); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(1); + + const bakcBalanceUser3 = await bakc.balanceOf(user3.address); + expect(bakcBalanceUser3).equal(1); + + // advance in time + await advanceTimeAndBlock(parseInt("86400")); + + await expect( + pool + .connect(user1.signer) + .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 0}]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_BAKC_OWNER); + }); + + it("TC-pool-ape-staking-36 test safeTransferFrom BAKC: new owner withdraw all (revert expected)", async () => { + const { + users: [user1, , user3], + ape, + mayc, + pool, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "7000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await waitForTx( + await bakc + .connect(user1.signer) + ["safeTransferFrom(address,address,uint256)"]( + user1.address, + user3.address, + "0" + ) + ); + + const bakcBalance = await bakc.balanceOf(user1.address); + expect(bakcBalance).equal(1); + + const bakcBalanceUser3 = await bakc.balanceOf(user3.address); + expect(bakcBalanceUser3).equal(1); + + // New owner + await expect( + pool + .connect(user3.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, + ]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + }); + + it("TC-pool-ape-staking-37 test safeTransferFrom: transfer fails when hf < 1 (revert expected)", async () => { + const { + users: [user1, user2, user3], + ape, + mayc, + nMAYC, + pool, + usdt, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(usdt, "1000", user2, true); + await borrowAndValidate(ape, "5000", user1); + await borrowAndValidate(usdt, "800", user1); + await mintAndValidate(ape, "7000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "100"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount2, + cashAmount: amount1, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + await changePriceAndValidate(mayc, "0.001"); + await changePriceAndValidate(ape, "0.1"); + await changeSApePriceAndValidate(sApeAddress, "0.002"); + + await expect( + nMAYC + .connect(user1.signer) + ["safeTransferFrom(address,address,uint256)"]( + user1.address, + user3.address, + 0, + {gasLimit: 5000000} + ) + ).to.be.revertedWith( + ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD + ); + }); + + it("TC-pool-ape-staking-38 test withdrawBAKC success when hf > 1 after withdrawBAKC", async () => { + const { + users: [user1], + ape, + mayc, + pool, + nMAYC, + bakc, + } = await loadFixture(fixture); + // 1. supply 1 mayc + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + // 2. stake one bakc and borrow 15000 ape + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const userAccount = await pool.getUserAccountData(user1.address); + const healthFactor = (await pool.getUserAccountData(user1.address)) + .healthFactor; + + expect(healthFactor.gt(parseEther("1"))).to.be.true; + // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(ape.address, "65") + ); + // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(ape.address, "15") + ); + // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(ape.address, "4.25") + // ); + let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + // User 1 - totalStake should increased in Stake amount + expect(totalStake).equal(amount); + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + const withdrawAmount = await convertToCurrencyDecimals(ape.address, "8000"); + await waitForTx( + await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: withdrawAmount, + isUncommit: true, + }, + ]) + ); + + const bakcBalance = await bakc.balanceOf(user1.address); + // User 1 - bakc balanace should increased 2 + expect(bakcBalance).equal(2); + // User1 - ape balance should increased amount2 + expect(await ape.balanceOf(user1.address)).eq(amount2); + totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + // User1 - total stake should increased amount1 + expect(totalStake).equal(amount1); + }); + + it("TC-pool-ape-staking-39 test withdrawApeCoin success when hf > 1 after withdrawApeCoin", async () => { + const { + users: [user1], + ape, + mayc, + pool, + nMAYC, + } = await loadFixture(fixture); + + // supply 1 mayc + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + // borrow and stake 15000 + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const userAccount = await pool.getUserAccountData(user1.address); + const healthFactor = (await pool.getUserAccountData(user1.address)) + .healthFactor; + + expect(healthFactor.gt(parseEther("1"))).to.be.true; + // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(ape.address, "65") + ); + // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(ape.address, "15") + ); + // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(ape.address, "4.25") + // ); + let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + // User 1 - totalStake should increased in Stake amount + expect(totalStake).equal(amount); + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + await waitForTx( + await pool + .connect(user1.signer) + .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) + ); + const apeBalance = await ape.balanceOf(user1.address); + expect(apeBalance).equal(amount1); + totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + // User 1 - totalStake should increased in amount2 + expect(totalStake).equal(amount2); + }); + + it("TC-pool-ape-staking-40 test withdrawBAKC fails when sender is not NFT owner (revert expected)", async () => { + const { + users: [user1], + ape, + mayc, + pool, + nMAYC, + } = await loadFixture(fixture); + // 1. supply 1 mayc + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + // 2. stake one bakc and borrow 15000 ape + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const userAccount = await pool.getUserAccountData(user1.address); + // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(ape.address, "65") + ); + // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(ape.address, "15") + ); + // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(ape.address, "4.25") + // ); + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + // User 1 - totalStake should increased in Stake amount + expect(totalStake).equal(amount); + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + await expect( + pool + .connect(user1.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 1, bakcTokenId: 0, amount: amount2, isUncommit: true}, + ]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + }); + + it("TC-pool-ape-staking-41 test withdrawBAKC fails when amount != total staking, the sender is the NFT owner, but the sender is not the BAKC owner(revert expected)", async () => { + const { + users: [user1, , user3], + ape, + mayc, + pool, + nMAYC, + bakc, + } = await loadFixture(fixture); + // 1. supply 1 mayc + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + // 2. stake one bakc and borrow 15000 ape + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const userAccount = await pool.getUserAccountData(user1.address); + // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(ape.address, "65") + ); + // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(ape.address, "15") + ); + // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(ape.address, "4.25") + // ); + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + // User 1 - totalStake should increased in Stake amount + expect(totalStake).equal(amount); + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + await waitForTx( + await bakc + .connect(user1.signer) + .transferFrom(user1.address, user3.address, 0) + ); + // User 3 - The NFT owner with bakc id 0 should be changed to user3 + expect(await bakc.balanceOf(user3.address)).equal(1); + expect(await bakc.ownerOf(0)).eq(user3.address); + + const withdrawAmount = await convertToCurrencyDecimals(ape.address, "6000"); + + await expect( + pool.connect(user1.signer).withdrawBAKC(mayc.address, [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: withdrawAmount, + isUncommit: false, + }, + ]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_BAKC_OWNER); + }); + + it("TC-pool-ape-staking-42 test withdrawBAKC success when withdraw amount == bakc staking amount, it will automatically claim and transfer the reward to the BACK owner", async () => { + const { + users: [user1, , user3], + ape, + mayc, + pool, + nMAYC, + apeCoinStaking, + bakc, + } = await loadFixture(fixture); + // 1. supply 1 mayc + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + // 2. stake one bakc and borrow 15000 ape + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const userAccount = await pool.getUserAccountData(user1.address); + + // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(ape.address, "65") + ); + // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(ape.address, "15") + ); + // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(ape.address, "4.25") + // ); + // User 1 - totalStake should increased in Stake amount + let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + await advanceTimeAndBlock(parseInt("86400")); + + // bayc rewards + const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( + 2, + nMAYC.address, + "0" + ); + // bakc rewards + const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( + 3, + nMAYC.address, + "0" + ); + + await waitForTx( + await bakc + .connect(user1.signer) + ["safeTransferFrom(address,address,uint256)"]( + user1.address, + user3.address, + 0 + ) + ); + // User 3 - The NFT owner with bakc id 0 should be changed to user3 + expect(await bakc.ownerOf(0)).eq(user3.address); + + await waitForTx( + await pool + .connect(user1.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, + ]) + ); + + // User1 - ape balance should increased amount2 + expect(await ape.balanceOf(user1.address)).eq(amount2); + // User 3 - ape balance should increased pendingRewardsPool3 + expect(await ape.balanceOf(user3.address)).eq(pendingRewardsPool3); + totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + // User1 - total stake should increased amount1 + pendingRewardsPool2 + expect(totalStake).equal(amount1.add(pendingRewardsPool2)); + }); + + it("TC-pool-ape-staking-43 test withdrawBAKC success when withdraw amount == bakc staking amount, and the sender is not the BAKC owner, it will automatically claim and transfer the reward to the BACK owner", async () => { + const { + users: [user1], + ape, + mayc, + pool, + nMAYC, + apeCoinStaking, + } = await loadFixture(fixture); + // 1. supply 1 mayc + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + // 2. stake one bakc and borrow 15000 ape + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const userAccount = await pool.getUserAccountData(user1.address); + + // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(ape.address, "65") + ); + // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(ape.address, "15") + ); + // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(ape.address, "4.25") + // ); + // User 1 - totalStake should increased in Stake amount + let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + await advanceTimeAndBlock(parseInt("86400")); + + // bayc rewards + const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( + 2, + nMAYC.address, + "0" + ); + // bakc rewards + const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( + 3, + nMAYC.address, + "0" + ); + + await waitForTx( + await pool + .connect(user1.signer) + .withdrawBAKC(mayc.address, [ + {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, + ]) + ); + + // User1 - ape balance should increased amount2 + pendingRewardsPool3 + expect(await ape.balanceOf(user1.address)).eq( + amount2.add(pendingRewardsPool3) + ); + totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + // User1 - total stake should increased amount1 + pendingRewardsPool2 + expect(totalStake).equal(amount1.add(pendingRewardsPool2)); + }); + + it("TC-pool-ape-staking-44 test withdrawApeCoin fails when the sender is not the NFT owner(revert expected)", async () => { + const { + users: [user1, , user3], + ape, + mayc, + pool, + nMAYC, + } = await loadFixture(fixture); + + // supply 1 mayc + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "1000", user1); + + const amount = await convertToCurrencyDecimals(ape.address, "1000"); + // 2. stake one bakc and borrow 15000 ape + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount}], + [] + ) + ); + + const userAccount = await pool.getUserAccountData(user1.address); + + // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 1000*0.001 = 51 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(ape.address, "51") + ); + // User1 - debt amount should increased 0 + almostEqual(userAccount.totalDebtBase, 0); + // User1 - available borrow should increased amount * baseLTVasCollateral = 50 * 0.325 + 1 * 0.2=16.45 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(ape.address, "16.45") + // ); + // User 1 - totalStake should increased in Stake amount + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + await waitForTx( + await nMAYC + .connect(user1.signer) + ["safeTransferFrom(address,address,uint256)"]( + user1.address, + user3.address, + 0 + ) + ); + expect(await nMAYC.balanceOf(user3.address)).eq(1); + expect(await nMAYC.ownerOf(0)).eq(user3.address); + + await expect( + pool + .connect(user1.signer) + .withdrawApeCoin(mayc.address, [{tokenId: 1, amount: amount}]) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + }); + + it("TC-pool-ape-staking-45 test withdrawApeCoin success when withdraw amount == NFT staking amount, it will automatically claim and transfer the reward to the user account", async () => { + const { + users: [user1], + ape, + mayc, + pool, + nMAYC, + apeCoinStaking, + } = await loadFixture(fixture); + // 1. supply 1 mayc + await supplyAndValidate(mayc, "1", user1, true); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + // 2. stake one bakc and borrow 15000 ape + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const userAccount = await pool.getUserAccountData(user1.address); + + // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 + expect(userAccount.totalCollateralBase).equal( + await convertToCurrencyDecimals(ape.address, "65") + ); + // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 + almostEqual( + userAccount.totalDebtBase, + await convertToCurrencyDecimals(ape.address, "15") + ); + // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 + // almostEqual( + // userAccount.availableBorrowsBase, + // await convertToCurrencyDecimals(ape.address, "4.25") + // ); + // User 1 - totalStake should increased in Stake amount + let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + // User 1 - pSape should increased in Stake amount + const pSApeBalance = await pSApeCoin.balanceOf(user1.address); + expect(pSApeBalance).equal(amount); + + // advance in time + await advanceTimeAndBlock(parseInt("86400")); + + const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( + 2, + nMAYC.address, + "0" + ); + await waitForTx( + await pool + .connect(user1.signer) + .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) + ); + + // User1 - ape balance should increased amount1 + pendingRewardsPool2 + expect(await ape.balanceOf(user1.address)).to.be.eq( + amount1.add(pendingRewardsPool2) + ); + // User1 - total stake should increased amount2 + totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount2); + }); + + it("TC-pool-ape-staking-46 test borrowApeAndStake will turn on the sAPE collateral", async () => { + const { + users: [user1, , user3], + ape, + mayc, + pool, + nMAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user3, true); + // transfer mayc#0 to user1 who hasn't collateralized sAPE + await waitForTx( + await nMAYC + .connect(user3.signer) + ["safeTransferFrom(address,address,uint256)"]( + user3.address, + user1.address, + "0" + ) + ); + await mintAndValidate(ape, "15000", user1); + + const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); + const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); + const amount = await convertToCurrencyDecimals(ape.address, "15000"); + const sApeReserveData = await pool.getReserveData(sApeAddress); + const configDataBefore = (await pool.getUserConfiguration(user1.address)) + .data; + expect(isUsingAsCollateral(configDataBefore, sApeReserveData.id)).false; + + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [{tokenId: 0, amount: amount1}], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] + ) + ); + + const configDataAfter = (await pool.getUserConfiguration(user1.address)) + .data; + expect(isUsingAsCollateral(configDataAfter, sApeReserveData.id)).true; + }); +}); diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index 1f08b91c2..a2524facc 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -235,8 +235,8 @@ describe("Para Ape staking ape coin pool test", () => { }, ], ape.address, - parseEther("100000"), - parseEther("150000"), + parseEther("0"), + parseEther("250000"), true ) ).to.be.revertedWith(ProtocolErrors.COLLATERAL_CANNOT_COVER_NEW_BORROW); diff --git a/test/xtoken_ntoken_bakc.spec.ts b/test/xtoken_ntoken_bakc.spec.ts new file mode 100644 index 000000000..c38c4cf51 --- /dev/null +++ b/test/xtoken_ntoken_bakc.spec.ts @@ -0,0 +1,404 @@ +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect} from "chai"; +import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; +import {convertToCurrencyDecimals} from "../helpers/contracts-helpers"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; +import {TestEnv} from "./helpers/make-suite"; +import {testEnvFixture} from "./helpers/setup-env"; +import { + changePriceAndValidate, + changeSApePriceAndValidate, + mintAndValidate, + supplyAndValidate, +} from "./helpers/validated-steps"; +import {parseEther} from "ethers/lib/utils"; +import {getAutoCompoundApe} from "../helpers/contracts-getters"; + +describe("APE Coin Staking Test", () => { + let testEnv: TestEnv; + const sApeAddress = ONE_ADDRESS; + + const fixture = async () => { + testEnv = await loadFixture(testEnvFixture); + const { + ape, + mayc, + bayc, + users: [user1, depositor, , , , user4], + pool, + apeCoinStaking, + bakc, + } = testEnv; + + const cApe = await getAutoCompoundApe(); + + await supplyAndValidate(ape, "20000", depositor, true); + await changePriceAndValidate(ape, "0.001"); + await changeSApePriceAndValidate(sApeAddress, "0.001"); + + await changePriceAndValidate(mayc, "50"); + await changePriceAndValidate(bayc, "50"); + + await waitForTx( + await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await bakc.connect(user1.signer).setApprovalForAll(pool.address, true) + ); + + // send extra tokens to the apestaking contract for rewards + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"]( + apeCoinStaking.address, + parseEther("100000000000") + ) + ); + + // user4 deposit MINIMUM_LIQUIDITY to make test case easy + await mintAndValidate(ape, "1", user4); + await waitForTx( + await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + const MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + await waitForTx( + await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) + ); + + return testEnv; + }; + + it("user can supply bakc first and stake paired nft", async () => { + const { + users: [user1], + ape, + mayc, + pool, + bakc, + nMAYC, + nBAKC, + } = await loadFixture(fixture); + await supplyAndValidate(bakc, "1", user1, true); + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "10000", user1); + + const amount = await convertToCurrencyDecimals(ape.address, "10000"); + const halfAmount = await convertToCurrencyDecimals(ape.address, "5000"); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + + const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + // advance in time + await advanceTimeAndBlock(parseInt("3600")); + await waitForTx( + await pool + .connect(user1.signer) + .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 0}]) + ); + let apeBalance = await ape.balanceOf(user1.address); + expect(apeBalance).to.be.equal(parseEther("3600")); + + await advanceTimeAndBlock(parseInt("3600")); + await waitForTx( + await pool + .connect(user1.signer) + .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 0}]) + ); + apeBalance = await ape.balanceOf(user1.address); + expect(apeBalance).to.be.equal(parseEther("7200")); + + await waitForTx( + await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: halfAmount, + isUncommit: false, + }, + ]) + ); + + apeBalance = await ape.balanceOf(user1.address); + expect(apeBalance).to.be.equal(parseEther("12200")); + + await waitForTx( + await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: halfAmount, + isUncommit: true, + }, + ]) + ); + + apeBalance = await ape.balanceOf(user1.address); + expect(apeBalance).to.be.equal(parseEther("17200")); + + await waitForTx( + await pool + .connect(user1.signer) + .withdrawERC721(bakc.address, [0], user1.address) + ); + expect(await nBAKC.balanceOf(user1.address)).to.be.equal(0); + expect(await bakc.balanceOf(user1.address)).to.be.equal(1); + }); + + it("unstakeApePositionAndRepay when bakc in user wallet: bakc reward should transfer to user wallet", async () => { + const { + users: [user1], + ape, + mayc, + pool, + bakc, + } = await loadFixture(fixture); + + await waitForTx(await bakc["mint(uint256,address)"]("1", user1.address)); + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "10000", user1); + const amount = await convertToCurrencyDecimals(ape.address, "10000"); + + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + + // advance in time + await advanceTimeAndBlock(parseInt("86400")); + + expect( + await pool + .connect(user1.signer) + .unstakeApePositionAndRepay(mayc.address, 0) + ); + + const userBalance = await ape.balanceOf(user1.address); + expect(userBalance).to.be.eq(parseEther("86400")); + }); + + it("unstakeApePositionAndRepay when bakc has been supplied: bakc reward should transfer to user wallet", async () => { + const { + users: [user1], + ape, + mayc, + pool, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bakc, "1", user1, true); + await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "10000", user1); + const amount = await convertToCurrencyDecimals(ape.address, "10000"); + + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + + // advance in time + await advanceTimeAndBlock(parseInt("86400")); + + expect( + await pool + .connect(user1.signer) + .unstakeApePositionAndRepay(mayc.address, 0) + ); + + const userBalance = await ape.balanceOf(user1.address); + expect(userBalance).to.be.eq(parseEther("86400")); + }); + + it("liquidate bakc will unstake user ape staking position", async () => { + const { + users: [user1, liquidator], + ape, + mayc, + pool, + weth, + bakc, + nMAYC, + } = await loadFixture(fixture); + await supplyAndValidate(ape, "20000", liquidator, true); + await changePriceAndValidate(ape, "0.001"); + await changeSApePriceAndValidate(sApeAddress, "0.001"); + await changePriceAndValidate(mayc, "50"); + await changePriceAndValidate(bakc, "5"); + + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + const amount = parseEther("10000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + await supplyAndValidate(weth, "91", liquidator, true, "200000"); + + await advanceTimeAndBlock(parseInt("3600")); + + // drop HF and ERC-721_HF below 1 + await changePriceAndValidate(ape, "1"); + await changeSApePriceAndValidate(sApeAddress, "1"); + await changePriceAndValidate(mayc, "1"); + await changePriceAndValidate(bakc, "1"); + + // start auction + await waitForTx( + await pool + .connect(liquidator.signer) + .startAuction(user1.address, bakc.address, 0) + ); + + expect(await ape.balanceOf(user1.address)).to.be.equal(0); + // try to liquidate the NFT + expect( + await pool + .connect(liquidator.signer) + .liquidateERC721( + bakc.address, + user1.address, + 0, + parseEther("10"), + false, + {gasLimit: 5000000} + ) + ); + expect(await bakc.ownerOf("0")).to.be.eq(liquidator.address); + totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(0); + expect(await ape.balanceOf(user1.address)).to.be.equal(parseEther("3600")); + }); + + it("transfer nbakc will unstake user ape staking position", async () => { + const { + users: [user1, user2], + ape, + mayc, + pool, + bakc, + nBAKC, + nMAYC, + } = await loadFixture(fixture); + await mintAndValidate(ape, "10000", user1); + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + const amount = parseEther("10000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAmount: amount, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + await advanceTimeAndBlock(parseInt("3600")); + + expect(await nBAKC.ownerOf("0")).to.be.eq(user1.address); + expect(await ape.balanceOf(user1.address)).to.be.equal(0); + expect( + await nBAKC + .connect(user1.signer) + ["safeTransferFrom(address,address,uint256)"]( + user1.address, + user2.address, + 0, + {gasLimit: 5000000} + ) + ); + expect(await nBAKC.ownerOf("0")).to.be.eq(user2.address); + expect(await ape.balanceOf(user1.address)).to.be.equal(parseEther("3600")); + totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(0); + }); + + it("withdraw bakc will not unstake user ape staking position", async () => { + const { + users: [user1, user2], + ape, + mayc, + pool, + bakc, + nMAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(ape, "20000", user2, true); + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + const amount = parseEther("10000"); + expect( + await pool.connect(user1.signer).borrowApeAndStake( + { + nftAsset: mayc.address, + borrowAsset: ape.address, + borrowAmount: amount, + cashAmount: 0, + }, + [], + [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] + ) + ); + let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + + // start auction + await waitForTx( + await pool + .connect(user1.signer) + .withdrawERC721(bakc.address, [0], user1.address) + ); + + expect(await bakc.ownerOf("0")).to.be.eq(user1.address); + totalStake = await nMAYC.getUserApeStakingAmount(user1.address); + expect(totalStake).equal(amount); + }); +}); From e28bf3eb4a1ce26973990b85e61dc358b2bfbc9b Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 6 Sep 2023 16:30:58 +0800 Subject: [PATCH 93/99] chore: keep deprecated data slot --- contracts/protocol/libraries/types/DataTypes.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/protocol/libraries/types/DataTypes.sol b/contracts/protocol/libraries/types/DataTypes.sol index 95622d752..9e8ded31d 100644 --- a/contracts/protocol/libraries/types/DataTypes.sol +++ b/contracts/protocol/libraries/types/DataTypes.sol @@ -402,6 +402,10 @@ library DataTypes { uint16 _reservesCount; // Auction recovery health factor uint64 _auctionRecoveryHealthFactor; + // deprecated. Incentive fee for claim ape reward to compound + uint16 _apeCompoundFee; + // deprecated. Map of user's ape compound strategies + mapping(address => ApeCompoundStrategy) _apeCompoundStrategies; } struct ReserveConfigData { From 57e6b26515420cc51b895c707009cbbc41f0906b Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 8 Sep 2023 16:32:16 +0800 Subject: [PATCH 94/99] chore: pool ape staking migration --- contracts/apestaking/ParaApeStaking.sol | 12 + .../logic/ApeStakingPairPoolLogic.sol | 4 +- .../logic/ApeStakingSinglePoolLogic.sol | 4 +- contracts/interfaces/IApeStakingVault.sol | 7 +- contracts/interfaces/IPoolApeStaking.sol | 40 +- contracts/protocol/pool/PoolApeStaking.sol | 433 +++-- .../protocol/tokenization/PTokenSApe.sol | 2 +- helpers/contracts-deployments.ts | 20 +- package.json | 5 +- test/_ape_staking_migration.spec.ts | 1635 +++++++++++++++++ test/_pool_ape_staking.spec.ts | 150 +- test/para_ape_staking.spec.ts | 108 +- 12 files changed, 2139 insertions(+), 281 deletions(-) create mode 100644 test/_ape_staking_migration.spec.ts diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 9a42fc5f6..6997b3e7a 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -727,10 +727,15 @@ contract ParaApeStaking is /// @inheritdoc IApeStakingVault function depositPairNFT( + address onBehalf, bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds ) external override whenNotPaused nonReentrant { + require( + msg.sender == pool || msg.sender == onBehalf, + Errors.CALLER_NOT_ALLOWED + ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = isBAYC ? ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID @@ -738,6 +743,7 @@ contract ParaApeStaking is ApeStakingPairPoolLogic.depositPairNFT( poolStates[poolId], vars, + onBehalf, isBAYC, apeTokenIds, bakcTokenIds @@ -807,6 +813,7 @@ contract ParaApeStaking is /// @inheritdoc IApeStakingVault function depositNFT( + address onBehalf, address nft, uint32[] calldata tokenIds ) external override whenNotPaused nonReentrant { @@ -814,6 +821,10 @@ contract ParaApeStaking is nft == bayc || nft == mayc || nft == bakc, Errors.NFT_NOT_ALLOWED ); + require( + msg.sender == pool || msg.sender == onBehalf, + Errors.CALLER_NOT_ALLOWED + ); ApeStakingVaultCacheVars memory vars = _createCacheVars(); uint256 poolId = (nft == bayc) ? ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID @@ -823,6 +834,7 @@ contract ParaApeStaking is ApeStakingSinglePoolLogic.depositNFT( poolStates[poolId], vars, + onBehalf, nft, tokenIds ); diff --git a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol index 672597c4a..e75265c9f 100644 --- a/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingPairPoolLogic.sol @@ -40,6 +40,7 @@ library ApeStakingPairPoolLogic { function depositPairNFT( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address onBehalf, bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds @@ -59,7 +60,6 @@ library ApeStakingPairPoolLogic { vars.apeToken = vars.mayc; vars.nApe = vars.nMayc; } - address msgSender = msg.sender; uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; for (uint256 index = 0; index < arrayLength; index++) { uint32 apeTokenId = apeTokenIds[index]; @@ -70,7 +70,7 @@ library ApeStakingPairPoolLogic { address nApeOwner = IERC721(vars.nApe).ownerOf(apeTokenId); address nBakcOwner = IERC721(vars.nBakc).ownerOf(bakcTokenId); require( - msgSender == nApeOwner && msgSender == nBakcOwner, + onBehalf == nApeOwner && onBehalf == nBakcOwner, Errors.NOT_THE_OWNER ); } diff --git a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol index e5be91278..ca609e63e 100644 --- a/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol +++ b/contracts/apestaking/logic/ApeStakingSinglePoolLogic.sol @@ -34,6 +34,7 @@ library ApeStakingSinglePoolLogic { function depositNFT( IParaApeStaking.PoolState storage poolState, IParaApeStaking.ApeStakingVaultCacheVars memory vars, + address onBehalf, address nft, uint32[] calldata tokenIds ) external { @@ -52,13 +53,12 @@ library ApeStakingSinglePoolLogic { nToken = vars.nBakc; apeStakingPoolId = ApeStakingCommonLogic.BAKC_POOL_ID; } - address msgSender = msg.sender; uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; require( - msgSender == IERC721(nToken).ownerOf(tokenId), + onBehalf == IERC721(nToken).ownerOf(tokenId), Errors.NOT_THE_OWNER ); diff --git a/contracts/interfaces/IApeStakingVault.sol b/contracts/interfaces/IApeStakingVault.sol index c08e9703d..0425418c3 100644 --- a/contracts/interfaces/IApeStakingVault.sol +++ b/contracts/interfaces/IApeStakingVault.sol @@ -42,6 +42,7 @@ interface IApeStakingVault { * @param bakcTokenIds BAKC token ids */ function depositPairNFT( + address onBehalf, bool isBAYC, uint32[] calldata apeTokenIds, uint32[] calldata bakcTokenIds @@ -90,7 +91,11 @@ interface IApeStakingVault { * @param nft Ape or BAKC token address * @param tokenIds nft token ids */ - function depositNFT(address nft, uint32[] calldata tokenIds) external; + function depositNFT( + address onBehalf, + address nft, + uint32[] calldata tokenIds + ) external; /** * @notice stake pool's Ape into ApeCoinStaking diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index aaa06a99d..7fc78b07f 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -48,28 +48,32 @@ interface IPoolApeStaking { uint256 amount ) external returns (DataTypes.TimeLockParams memory); - struct StakingInfo { - // Contract address of BAYC/MAYC + struct UnstakingInfo { address nftAsset; - // address of borrowing asset, can be Ape or cApe - address borrowAsset; - // Borrow amount of Ape from lending pool - uint256 borrowAmount; - // Cash amount of Ape from user wallet + ApeCoinStaking.SingleNft[] _nfts; + ApeCoinStaking.PairNftWithdrawWithAmount[] _nftPairs; + } + + struct ParaStakingInfo { + //Para Ape Staking Pool Id + uint256 PoolId; + //Ape token ids + uint32[] apeTokenIds; + //BAKC token ids + uint32[] bakcTokenIds; + } + + struct ApeCoinInfo { + address asset; uint256 cashAmount; + uint256 borrowAmount; + bool openSApeCollateralFlag; } - /** - * @notice Deposit ape coin to BAYC/MAYC pool or BAKC pool - * @param stakingInfo Detail info of the staking - * @param _nfts Array of BAYC/MAYC NFT's with staked amounts - * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts - * @dev Need check User health factor > 1. - */ - function borrowApeAndStake( - StakingInfo calldata stakingInfo, - ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs + function apeStakingMigration( + UnstakingInfo[] calldata unstakingInfos, + ParaStakingInfo[] calldata stakingInfos, + ApeCoinInfo calldata apeCoinInfo ) external; /** diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index ad8bd620b..a4466ddae 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -27,6 +27,10 @@ import {Math} from "../../dependencies/openzeppelin/contracts/Math.sol"; import {ISwapRouter} from "../../dependencies/uniswapv3-periphery/interfaces/ISwapRouter.sol"; import {IPriceOracleGetter} from "../../interfaces/IPriceOracleGetter.sol"; import {Helpers} from "../libraries/helpers/Helpers.sol"; +import "../../apestaking/logic/ApeStakingCommonLogic.sol"; +import "../../interfaces/IApeStakingP2P.sol"; +import "../../interfaces/IParaApeStaking.sol"; +import "../../interfaces/IApeCoinPool.sol"; contract PoolApeStaking is ParaVersionedInitializable, @@ -47,6 +51,9 @@ contract PoolApeStaking is IERC20 internal immutable APE_COIN; uint256 internal constant POOL_REVISION = 200; address internal immutable PARA_APE_STAKING; + address internal immutable BAYC; + address internal immutable MAYC; + address internal immutable BAKC; event ReserveUsedAsCollateralEnabled( address indexed reserve, @@ -78,11 +85,17 @@ contract PoolApeStaking is IPoolAddressesProvider provider, IAutoCompoundApe apeCompound, IERC20 apeCoin, + address bayc, + address mayc, + address bakc, address apeStakingVault ) { ADDRESSES_PROVIDER = provider; APE_COMPOUND = apeCompound; APE_COIN = apeCoin; + BAYC = bayc; + MAYC = mayc; + BAKC = bakc; PARA_APE_STAKING = apeStakingVault; } @@ -230,6 +243,276 @@ contract PoolApeStaking is require(balanceAfter == balanceBefore, Errors.INVALID_PARAMETER); } + function apeStakingMigration( + UnstakingInfo[] calldata unstakingInfos, + ParaStakingInfo[] calldata stakingInfos, + ApeCoinInfo calldata apeCoinInfo + ) external nonReentrant { + address onBehalf = msg.sender; + DataTypes.PoolStorage storage ps = poolStorage(); + uint256 beforeBalance = APE_COIN.balanceOf(address(this)); + //unstake in v1 + { + address nBakc; + uint256 unstakingLength = unstakingInfos.length; + for (uint256 index = 0; index < unstakingLength; index++) { + UnstakingInfo calldata unstakingInfo = unstakingInfos[index]; + + DataTypes.ReserveData storage nftReserve = ps._reserves[ + unstakingInfo.nftAsset + ]; + INTokenApeStaking nToken = INTokenApeStaking( + nftReserve.xTokenAddress + ); + if (unstakingInfo._nfts.length > 0) { + nToken.withdrawApeCoin(unstakingInfo._nfts, address(this)); + } + uint256 pairLength = unstakingInfo._nftPairs.length; + if (pairLength > 0) { + if (nBakc == address(0)) { + nBakc = ps._reserves[BAKC].xTokenAddress; + } + //transfer bakc from nBakc to ape + for (uint256 j = 0; j < pairLength; j++) { + IERC721(BAKC).safeTransferFrom( + nBakc, + address(nToken), + unstakingInfo._nftPairs[j].bakcTokenId + ); + } + + //unstake + nToken.withdrawBAKC(unstakingInfo._nftPairs, address(this)); + + //transfer bakc back to nBakc + for (uint256 j = 0; j < pairLength; j++) { + IERC721(BAKC).safeTransferFrom( + address(nToken), + nBakc, + unstakingInfo._nftPairs[j].bakcTokenId + ); + } + } + } + } + + //handle ape coin + { + require( + apeCoinInfo.asset == address(APE_COIN) || + apeCoinInfo.asset == address(APE_COMPOUND), + Errors.INVALID_ASSET_TYPE + ); + // 1, prepare cash part. + if (apeCoinInfo.cashAmount > 0) { + IERC20(apeCoinInfo.asset).transferFrom( + onBehalf, + address(this), + apeCoinInfo.cashAmount + ); + } + + // 2, prepare borrow part. + if (apeCoinInfo.borrowAmount > 0) { + DataTypes.ReserveData storage borrowAssetReserve = ps._reserves[ + apeCoinInfo.asset + ]; + // no time lock needed here + DataTypes.TimeLockParams memory timeLockParams; + IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( + address(this), + apeCoinInfo.borrowAmount, + timeLockParams + ); + } + + uint256 totalAmount = apeCoinInfo.cashAmount + + apeCoinInfo.borrowAmount; + if (apeCoinInfo.asset == address(APE_COMPOUND) && totalAmount > 0) { + APE_COMPOUND.withdraw(totalAmount); + } + } + + //staking in paraApeStaking + { + uint256 stakingLength = stakingInfos.length; + for (uint256 index = 0; index < stakingLength; index++) { + ParaStakingInfo calldata stakingInfo = stakingInfos[index]; + + if ( + stakingInfo.PoolId == + ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID + ) { + IParaApeStaking(PARA_APE_STAKING).depositPairNFT( + onBehalf, + true, + stakingInfo.apeTokenIds, + stakingInfo.bakcTokenIds + ); + } else if ( + stakingInfo.PoolId == + ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID + ) { + IParaApeStaking(PARA_APE_STAKING).depositPairNFT( + onBehalf, + false, + stakingInfo.apeTokenIds, + stakingInfo.bakcTokenIds + ); + } else if ( + stakingInfo.PoolId == + ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID + ) { + IParaApeStaking(PARA_APE_STAKING).depositNFT( + onBehalf, + BAYC, + stakingInfo.apeTokenIds + ); + } else if ( + stakingInfo.PoolId == + ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID + ) { + IParaApeStaking(PARA_APE_STAKING).depositNFT( + onBehalf, + MAYC, + stakingInfo.apeTokenIds + ); + } else if ( + stakingInfo.PoolId == + ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID + ) { + IParaApeStaking(PARA_APE_STAKING).depositNFT( + onBehalf, + BAKC, + stakingInfo.bakcTokenIds + ); + } else if ( + stakingInfo.PoolId == + ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID + ) { + uint256 cap = IParaApeStaking(PARA_APE_STAKING) + .getApeCoinStakingCap( + IApeStakingP2P.StakingType.BAYCStaking + ); + IParaApeStaking(PARA_APE_STAKING).depositApeCoinPool( + IApeCoinPool.ApeCoinDepositInfo({ + onBehalf: onBehalf, + cashToken: address(APE_COIN), + cashAmount: cap * stakingInfo.apeTokenIds.length, + isBAYC: true, + tokenIds: stakingInfo.apeTokenIds + }) + ); + } else if ( + stakingInfo.PoolId == + ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID + ) { + uint256 cap = IParaApeStaking(PARA_APE_STAKING) + .getApeCoinStakingCap( + IApeStakingP2P.StakingType.MAYCStaking + ); + IParaApeStaking(PARA_APE_STAKING).depositApeCoinPool( + IApeCoinPool.ApeCoinDepositInfo({ + onBehalf: onBehalf, + cashToken: address(APE_COIN), + cashAmount: cap * stakingInfo.apeTokenIds.length, + isBAYC: false, + tokenIds: stakingInfo.apeTokenIds + }) + ); + } else if ( + stakingInfo.PoolId == + ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID + ) { + uint256 cap = IParaApeStaking(PARA_APE_STAKING) + .getApeCoinStakingCap( + IApeStakingP2P.StakingType.BAKCPairStaking + ); + IParaApeStaking(PARA_APE_STAKING).depositApeCoinPairPool( + IApeCoinPool.ApeCoinPairDepositInfo({ + onBehalf: onBehalf, + cashToken: address(APE_COIN), + cashAmount: cap * stakingInfo.apeTokenIds.length, + isBAYC: true, + apeTokenIds: stakingInfo.apeTokenIds, + bakcTokenIds: stakingInfo.bakcTokenIds + }) + ); + } else if ( + stakingInfo.PoolId == + ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID + ) { + uint256 cap = IParaApeStaking(PARA_APE_STAKING) + .getApeCoinStakingCap( + IApeStakingP2P.StakingType.BAKCPairStaking + ); + IParaApeStaking(PARA_APE_STAKING).depositApeCoinPairPool( + IApeCoinPool.ApeCoinPairDepositInfo({ + onBehalf: onBehalf, + cashToken: address(APE_COIN), + cashAmount: cap * stakingInfo.apeTokenIds.length, + isBAYC: false, + apeTokenIds: stakingInfo.apeTokenIds, + bakcTokenIds: stakingInfo.bakcTokenIds + }) + ); + } + } + } + + // repay and supply remaining apecoin + uint256 diffBalance = APE_COIN.balanceOf(address(this)) - beforeBalance; + if (diffBalance > 0) { + //wrong cashAmount or borrowAmount + require( + apeCoinInfo.cashAmount + apeCoinInfo.borrowAmount == 0, + Errors.INVALID_PARAMETER + ); + APE_COMPOUND.deposit(address(this), diffBalance); + _repayAndSupplyForUser( + ps, + address(APE_COMPOUND), + address(this), + onBehalf, + diffBalance + ); + } + + // check if need to collateralize sAPE + if (apeCoinInfo.openSApeCollateralFlag) { + DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ + onBehalf + ]; + Helpers.setAssetUsedAsCollateral( + userConfig, + ps._reserves, + DataTypes.SApeAddress, + onBehalf + ); + } + + // execute borrow + if (apeCoinInfo.borrowAmount > 0) { + BorrowLogic.executeBorrow( + ps._reserves, + ps._reservesList, + ps._usersConfig[onBehalf], + DataTypes.ExecuteBorrowParams({ + asset: apeCoinInfo.asset, + user: onBehalf, + onBehalfOf: onBehalf, + amount: apeCoinInfo.borrowAmount, + referralCode: 0, + releaseUnderlying: false, + reservesCount: ps._reservesCount, + oracle: ADDRESSES_PROVIDER.getPriceOracle(), + priceOracleSentinel: ADDRESSES_PROVIDER + .getPriceOracleSentinel() + }) + ); + } + } + /// @inheritdoc IPoolApeStaking function withdrawApeCoin( address nftAsset, @@ -374,156 +657,6 @@ contract PoolApeStaking is } } - /// @inheritdoc IPoolApeStaking - function borrowApeAndStake( - StakingInfo calldata stakingInfo, - ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - require( - stakingInfo.borrowAsset == address(APE_COIN) || - stakingInfo.borrowAsset == address(APE_COMPOUND), - Errors.INVALID_ASSET_TYPE - ); - - ApeStakingLocalVars memory localVar = _generalCache( - ps, - stakingInfo.nftAsset - ); - localVar.transferredTokenOwners = new address[](_nftPairs.length); - localVar.balanceBefore = APE_COIN.balanceOf(localVar.xTokenAddress); - - DataTypes.ReserveData storage borrowAssetReserve = ps._reserves[ - stakingInfo.borrowAsset - ]; - // no time lock needed here - DataTypes.TimeLockParams memory timeLockParams; - // 1, handle borrow part - if (stakingInfo.borrowAmount > 0) { - if (stakingInfo.borrowAsset == address(APE_COIN)) { - IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( - localVar.xTokenAddress, - stakingInfo.borrowAmount, - timeLockParams - ); - } else { - IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( - address(this), - stakingInfo.borrowAmount, - timeLockParams - ); - APE_COMPOUND.withdraw(stakingInfo.borrowAmount); - APE_COIN.safeTransfer( - localVar.xTokenAddress, - stakingInfo.borrowAmount - ); - } - } - - // 2, send cash part to xTokenAddress - if (stakingInfo.cashAmount > 0) { - APE_COIN.safeTransferFrom( - msg.sender, - localVar.xTokenAddress, - stakingInfo.cashAmount - ); - } - - // 3, deposit bayc or mayc pool - { - uint256 nftsLength = _nfts.length; - for (uint256 index = 0; index < nftsLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nfts[index].tokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - } - - if (nftsLength > 0) { - INTokenApeStaking(localVar.xTokenAddress).depositApeCoin(_nfts); - } - } - - // 4, deposit bakc pool - { - uint256 nftPairsLength = _nftPairs.length; - for (uint256 index = 0; index < nftPairsLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[index].mainTokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - - localVar.transferredTokenOwners[ - index - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[index].bakcTokenId, - msg.sender - ); - } - - if (nftPairsLength > 0) { - INTokenApeStaking(localVar.xTokenAddress).depositBAKC( - _nftPairs - ); - } - //transfer BAKC back for user - for (uint256 index = 0; index < nftPairsLength; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - _nftPairs[index].bakcTokenId - ); - } - } - - // 5 mint debt token - if (stakingInfo.borrowAmount > 0) { - BorrowLogic.executeBorrow( - ps._reserves, - ps._reservesList, - ps._usersConfig[msg.sender], - DataTypes.ExecuteBorrowParams({ - asset: stakingInfo.borrowAsset, - user: msg.sender, - onBehalfOf: msg.sender, - amount: stakingInfo.borrowAmount, - referralCode: 0, - releaseUnderlying: false, - reservesCount: ps._reservesCount, - oracle: ADDRESSES_PROVIDER.getPriceOracle(), - priceOracleSentinel: ADDRESSES_PROVIDER - .getPriceOracleSentinel() - }) - ); - } - - //6 checkout ape balance - require( - APE_COIN.balanceOf(localVar.xTokenAddress) == - localVar.balanceBefore, - Errors.TOTAL_STAKING_AMOUNT_WRONG - ); - - //7 collateralize sAPE - DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ - msg.sender - ]; - Helpers.setAssetUsedAsCollateral( - userConfig, - ps._reserves, - DataTypes.SApeAddress, - msg.sender - ); - } - /// @inheritdoc IPoolApeStaking function unstakeApePositionAndRepay( address nftAsset, diff --git a/contracts/protocol/tokenization/PTokenSApe.sol b/contracts/protocol/tokenization/PTokenSApe.sol index 5a7e5ecc2..de8345120 100644 --- a/contracts/protocol/tokenization/PTokenSApe.sol +++ b/contracts/protocol/tokenization/PTokenSApe.sol @@ -55,7 +55,7 @@ contract PTokenSApe is PToken { function balanceOf(address user) public view override returns (uint256) { uint256 v1StakedAPE = nBAYC.getUserApeStakingAmount(user) + - nMAYC.getUserApeStakingAmount(user); + nMAYC.getUserApeStakingAmount(user); uint256 v2StakedAPE = paraApeStaking.totalSApeBalance(user); return v1StakedAPE + v2StakedAPE; } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 6e21fc123..01e17bd02 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -553,16 +553,9 @@ export const deployPoolApeStaking = async ( borrowLogic.address, }; - const APE_WETH_FEE = 3000; - const WETH_USDC_FEE = 500; - const {poolApeStakingSelectors} = await getPoolSignatures(); const allTokens = await getAllTokens(); - - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const cApe = await getAutoCompoundApe(); const poolApeStaking = (await withSaveAndVerify( await getContractFactory("PoolApeStaking", apeStakingLibraries), @@ -571,12 +564,10 @@ export const deployPoolApeStaking = async ( provider, cApe.address, allTokens.APE.address, - allTokens.USDC.address, - (await getUniswapV3SwapRouter()).address, - allTokens.WETH.address, - APE_WETH_FEE, - WETH_USDC_FEE, - treasuryAddress, + allTokens.BAYC.address, + allTokens.MAYC.address, + allTokens.BAKC.address, + (await getParaApeStaking()).address, ], verify, false, @@ -933,6 +924,9 @@ export const deployPoolComponents = async ( provider, cApe.address, allTokens.APE.address, + allTokens.BAYC.address, + allTokens.MAYC.address, + allTokens.BAKC.address, (await getParaApeStaking()).address, ], verify, diff --git a/package.json b/package.json index 03a7f757d..670a76dac 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,9 @@ "coverage": "hardhat coverage --testfiles 'test/*.ts'", "format": "prettier --write 'contracts/**/*.sol' 'scripts/**/*.ts' 'helpers/**/*.ts' 'tasks/**/*.ts' 'test/**/*.ts' 'hardhat.config.ts' 'helper-hardhat-config.ts' 'market-config/**/*.ts'", "doc": "hardhat docgen", - "test": "hardhat test ./test/para_ape_staking.spec.ts", + "test": "hardhat test ./test/*.spec.ts", "clean": "hardhat clean" }, - "resolutions": { - "ethereumjs-abi": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz" - }, "devDependencies": { "@commitlint/cli": "^17.0.3", "@commitlint/config-conventional": "^17.0.3", diff --git a/test/_ape_staking_migration.spec.ts b/test/_ape_staking_migration.spec.ts new file mode 100644 index 000000000..5b217e6fa --- /dev/null +++ b/test/_ape_staking_migration.spec.ts @@ -0,0 +1,1635 @@ +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect} from "chai"; +import { + AutoCompoundApe, + ParaApeStaking, + PToken, + PTokenSApe, + VariableDebtToken, +} from "../types"; +import {TestEnv} from "./helpers/make-suite"; +import {testEnvFixture} from "./helpers/setup-env"; +import { + changePriceAndValidate, + changeSApePriceAndValidate, + mintAndValidate, + supplyAndValidate, +} from "./helpers/validated-steps"; +import { + getAutoCompoundApe, + getParaApeStaking, + getPToken, + getPTokenSApe, + getVariableDebtToken, +} from "../helpers/contracts-getters"; +import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; +import {parseEther} from "ethers/lib/utils"; + +describe("Para Ape Staking Test", () => { + let testEnv: TestEnv; + let variableDebtCApeCoin: VariableDebtToken; + let paraApeStaking: ParaApeStaking; + let cApe: AutoCompoundApe; + let pcApeCoin: PToken; + let pSApeCoin: PTokenSApe; + const sApeAddress = ONE_ADDRESS; + let MINIMUM_LIQUIDITY; + + const fixture = async () => { + testEnv = await loadFixture(testEnvFixture); + const { + ape, + users: [user1, , , user4, , user6], + apeCoinStaking, + pool, + protocolDataProvider, + configurator, + poolAdmin, + } = testEnv; + + paraApeStaking = await getParaApeStaking(); + + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .setApeStakingBot(user4.address) + ); + + cApe = await getAutoCompoundApe(); + MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + + const { + xTokenAddress: pcApeCoinAddress, + variableDebtTokenAddress: variableDebtCApeCoinAddress, + } = await protocolDataProvider.getReserveTokensAddresses(cApe.address); + variableDebtCApeCoin = await getVariableDebtToken( + variableDebtCApeCoinAddress + ); + pcApeCoin = await getPToken(pcApeCoinAddress); + + const {xTokenAddress: pSApeCoinAddress} = + await protocolDataProvider.getReserveTokensAddresses(sApeAddress); + pSApeCoin = await getPTokenSApe(pSApeCoinAddress); + + // send extra tokens to the apestaking contract for rewards + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"]( + apeCoinStaking.address, + parseEther("100000000000") + ) + ); + + // user6 deposit MINIMUM_LIQUIDITY to make test case easy + await mintAndValidate(ape, "1", user6); + await waitForTx( + await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) + ); + + // user4 deposit and supply cApe to MM + expect( + await configurator + .connect(poolAdmin.signer) + .setSupplyCap(cApe.address, "20000000000") + ); + await mintAndValidate(ape, "10000000000", user4); + await waitForTx( + await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user4.signer) + .deposit(user4.address, parseEther("10000000000")) + ); + await waitForTx( + await cApe.connect(user4.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await pool + .connect(user4.signer) + .supply(cApe.address, parseEther("10000000000"), user4.address, 0) + ); + + await waitForTx( + await pool + .connect(poolAdmin.signer) + .unlimitedApproveTo(ape.address, paraApeStaking.address) + ); + await waitForTx( + await pool + .connect(poolAdmin.signer) + .unlimitedApproveTo(cApe.address, paraApeStaking.address) + ); + + await waitForTx( + await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + + await changePriceAndValidate(ape, "0.0001"); + await changePriceAndValidate(cApe, "0.0001"); + await changeSApePriceAndValidate(sApeAddress, "0.0001"); + + return testEnv; + }; + + it("Full position, without borrow in V1 migration to ApeCoin Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + await mintAndValidate(ape, "250000", user1); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("250000"), + }, + [{tokenId: 0, amount: parseEther("200000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("200000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0], + bakcTokenIds: [], + }, + { + PoolId: 8, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("14400") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + }); + + it("Full position, with borrow in V1 migration to ApeCoin Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: cApe.address, + borrowAmount: parseEther("250000"), + cashAsset: ape.address, + cashAmount: 0, + }, + [{tokenId: 0, amount: parseEther("200000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("250000"), + parseEther("10") + ); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("200000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0], + bakcTokenIds: [], + }, + { + PoolId: 8, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("235600"), + parseEther("100") + ); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + }); + + it("Not Full position, without borrow in V1 migration to ApeCoin Pool(need borrow during migration)", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + await mintAndValidate(ape, "150000", user1); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("150000"), + }, + [{tokenId: 0, amount: parseEther("100000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("150000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("100000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0], + bakcTokenIds: [], + }, + { + PoolId: 8, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + { + asset: cApe.address, + cashAmount: 0, + borrowAmount: parseEther("85600"), + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("85600"), + parseEther("100") + ); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + }); + + it("Not Full position, with borrow in V1 migration to ApeCoin Pool(need borrow during migration)", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: cApe.address, + borrowAmount: parseEther("150000"), + cashAsset: ape.address, + cashAmount: 0, + }, + [{tokenId: 0, amount: parseEther("100000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("150000"), + parseEther("10") + ); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("150000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("100000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0], + bakcTokenIds: [], + }, + { + PoolId: 8, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + { + asset: cApe.address, + cashAmount: 0, + borrowAmount: parseEther("85600"), + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("235600"), + parseEther("100") + ); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + }); + + it("Full position, without borrow in V1 migration to NFT Single Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + await mintAndValidate(ape, "250000", user1); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("250000"), + }, + [{tokenId: 0, amount: parseEther("200000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("200000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 3, + apeTokenIds: [0], + bakcTokenIds: [], + }, + { + PoolId: 5, + apeTokenIds: [], + bakcTokenIds: [0], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("264400") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq(parseEther("0")); + }); + + it("Full position, with borrow in V1 migration to NFT Single Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: cApe.address, + borrowAmount: parseEther("250000"), + cashAsset: ape.address, + cashAmount: 0, + }, + [{tokenId: 0, amount: parseEther("200000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("250000"), + parseEther("10") + ); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("200000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 3, + apeTokenIds: [0], + bakcTokenIds: [], + }, + { + PoolId: 5, + apeTokenIds: [], + bakcTokenIds: [0], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("14400"), + parseEther("100") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq(0); + }); + + it("Not Full position, without borrow in V1 migration to NFT Single Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + await mintAndValidate(ape, "150000", user1); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("150000"), + }, + [{tokenId: 0, amount: parseEther("100000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("150000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("100000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 3, + apeTokenIds: [0], + bakcTokenIds: [], + }, + { + PoolId: 5, + apeTokenIds: [], + bakcTokenIds: [0], + }, + ], + { + asset: cApe.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("164400"), + parseEther("100") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq(0); + }); + + it("Not Full position, with borrow in V1 migration to NFT Single Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: cApe.address, + borrowAmount: parseEther("150000"), + cashAsset: ape.address, + cashAmount: 0, + }, + [{tokenId: 0, amount: parseEther("100000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("150000"), + parseEther("10") + ); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("150000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("100000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 3, + apeTokenIds: [0], + bakcTokenIds: [], + }, + { + PoolId: 5, + apeTokenIds: [], + bakcTokenIds: [0], + }, + ], + { + asset: cApe.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("14400"), + parseEther("100") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq(0); + }); + + it("Full position, without borrow in V1 migration to NFT Pair Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + await mintAndValidate(ape, "250000", user1); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("250000"), + }, + [{tokenId: 0, amount: parseEther("200000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("200000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 1, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("264400") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq(parseEther("0")); + }); + + it("Full position, with borrow in V1 migration to NFT Pair Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: cApe.address, + borrowAmount: parseEther("250000"), + cashAsset: ape.address, + cashAmount: 0, + }, + [{tokenId: 0, amount: parseEther("200000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("250000"), + parseEther("10") + ); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("250000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("200000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 1, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("14400"), + parseEther("100") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq(0); + }); + + it("Not Full position, without borrow in V1 migration to NFT Pair Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + await mintAndValidate(ape, "150000", user1); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("150000"), + }, + [{tokenId: 0, amount: parseEther("100000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("150000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("100000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 1, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + { + asset: cApe.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("164400"), + parseEther("100") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq(0); + }); + + it("Not Full position, with borrow in V1 migration to NFT Pair Pool", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: cApe.address, + borrowAmount: parseEther("150000"), + cashAsset: ape.address, + cashAmount: 0, + }, + [{tokenId: 0, amount: parseEther("100000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("150000"), + parseEther("10") + ); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq( + parseEther("150000") + ); + + await advanceTimeAndBlock(7200); + + await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("100000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 1, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + { + asset: cApe.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + expect(await variableDebtCApeCoin.balanceOf(user1.address)).to.be.eq(0); + expect(await pcApeCoin.balanceOf(user1.address)).to.be.closeTo( + parseEther("14400"), + parseEther("100") + ); + expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq(0); + }); + + /* + it("gas test: test 1 pair of BAYC with BAKC position migration", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + await mintAndValidate(ape, "250000", user1); + + await waitForTx( + await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("250000"), + }, + [{tokenId: 0, amount: parseEther("200000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ); + + await advanceTimeAndBlock(7200); + + const txRecepient = await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("200000")}], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0], + bakcTokenIds: [], + }, + { + PoolId: 8, + apeTokenIds: [0], + bakcTokenIds: [0], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + console.log("-----------------gas used:", txRecepient.gasUsed.toString()); + }); + + it("gas test: test 5 pair of BAYC with BAKC position migration", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "5", user1, true); + await supplyAndValidate(bakc, "5", user1, true); + await mintAndValidate(ape, "1250000", user1); + + await waitForTx( + await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("1250000"), + }, + [ + {tokenId: 0, amount: parseEther("200000")}, + {tokenId: 1, amount: parseEther("200000")}, + {tokenId: 2, amount: parseEther("200000")}, + {tokenId: 3, amount: parseEther("200000")}, + {tokenId: 4, amount: parseEther("200000")}, + ], + [ + {mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}, + {mainTokenId: 1, bakcTokenId: 1, amount: parseEther("50000")}, + {mainTokenId: 2, bakcTokenId: 2, amount: parseEther("50000")}, + {mainTokenId: 3, bakcTokenId: 3, amount: parseEther("50000")}, + {mainTokenId: 4, bakcTokenId: 4, amount: parseEther("50000")}, + ] + ) + ); + + await advanceTimeAndBlock(7200); + + const txRecepient = await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [ + {tokenId: 0, amount: parseEther("200000")}, + {tokenId: 1, amount: parseEther("200000")}, + {tokenId: 2, amount: parseEther("200000")}, + {tokenId: 3, amount: parseEther("200000")}, + {tokenId: 4, amount: parseEther("200000")}, + ], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 1, + bakcTokenId: 1, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 2, + bakcTokenId: 2, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 3, + bakcTokenId: 3, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 4, + bakcTokenId: 4, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0, 1, 2, 3, 4], + bakcTokenIds: [], + }, + { + PoolId: 8, + apeTokenIds: [0, 1, 2, 3, 4], + bakcTokenIds: [0, 1, 2, 3, 4], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + console.log("-----------------gas used:", txRecepient.gasUsed.toString()); + }); + + it("gas test: test 10 pair of BAYC with BAKC position migration", async () => { + const { + users: [user1], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "10", user1, true); + await supplyAndValidate(bakc, "10", user1, true); + await mintAndValidate(ape, "2500000", user1); + + await waitForTx( + await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("2500000"), + }, + [ + {tokenId: 0, amount: parseEther("200000")}, + {tokenId: 1, amount: parseEther("200000")}, + {tokenId: 2, amount: parseEther("200000")}, + {tokenId: 3, amount: parseEther("200000")}, + {tokenId: 4, amount: parseEther("200000")}, + {tokenId: 5, amount: parseEther("200000")}, + {tokenId: 6, amount: parseEther("200000")}, + {tokenId: 7, amount: parseEther("200000")}, + {tokenId: 8, amount: parseEther("200000")}, + {tokenId: 9, amount: parseEther("200000")}, + ], + [ + {mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}, + {mainTokenId: 1, bakcTokenId: 1, amount: parseEther("50000")}, + {mainTokenId: 2, bakcTokenId: 2, amount: parseEther("50000")}, + {mainTokenId: 3, bakcTokenId: 3, amount: parseEther("50000")}, + {mainTokenId: 4, bakcTokenId: 4, amount: parseEther("50000")}, + {mainTokenId: 5, bakcTokenId: 5, amount: parseEther("50000")}, + {mainTokenId: 6, bakcTokenId: 6, amount: parseEther("50000")}, + {mainTokenId: 7, bakcTokenId: 7, amount: parseEther("50000")}, + {mainTokenId: 8, bakcTokenId: 8, amount: parseEther("50000")}, + {mainTokenId: 9, bakcTokenId: 9, amount: parseEther("50000")}, + ] + ) + ); + + await advanceTimeAndBlock(7200); + + const txRecepient = await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [ + {tokenId: 0, amount: parseEther("200000")}, + {tokenId: 1, amount: parseEther("200000")}, + {tokenId: 2, amount: parseEther("200000")}, + {tokenId: 3, amount: parseEther("200000")}, + {tokenId: 4, amount: parseEther("200000")}, + {tokenId: 5, amount: parseEther("200000")}, + {tokenId: 6, amount: parseEther("200000")}, + {tokenId: 7, amount: parseEther("200000")}, + {tokenId: 8, amount: parseEther("200000")}, + {tokenId: 9, amount: parseEther("200000")}, + ], + _nftPairs: [ + { + mainTokenId: 0, + bakcTokenId: 0, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 1, + bakcTokenId: 1, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 2, + bakcTokenId: 2, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 3, + bakcTokenId: 3, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 4, + bakcTokenId: 4, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 5, + bakcTokenId: 5, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 6, + bakcTokenId: 6, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 7, + bakcTokenId: 7, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 8, + bakcTokenId: 8, + amount: parseEther("50000"), + isUncommit: true, + }, + { + mainTokenId: 9, + bakcTokenId: 9, + amount: parseEther("50000"), + isUncommit: true, + }, + ], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + bakcTokenIds: [], + }, + { + PoolId: 8, + apeTokenIds: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + bakcTokenIds: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + console.log("-----------------gas used:", txRecepient.gasUsed.toString()); + }); + + it("gas test: test 1 pair of BAYC position migration", async () => { + const { + users: [user1], + bayc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await mintAndValidate(ape, "200000", user1); + + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("200000"), + }, + [{tokenId: 0, amount: parseEther("200000")}], + [] + ) + ); + + await advanceTimeAndBlock(7200); + + const txRecepient = await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [{tokenId: 0, amount: parseEther("200000")}], + _nftPairs: [], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0], + bakcTokenIds: [], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + console.log("-----------------gas used:", txRecepient.gasUsed.toString()); + }); + + it("gas test: test 5 pair of BAYC position migration", async () => { + const { + users: [user1], + bayc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "5", user1, true); + await mintAndValidate(ape, "1000000", user1); + + await waitForTx( + await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("1000000"), + }, + [ + {tokenId: 0, amount: parseEther("200000")}, + {tokenId: 1, amount: parseEther("200000")}, + {tokenId: 2, amount: parseEther("200000")}, + {tokenId: 3, amount: parseEther("200000")}, + {tokenId: 4, amount: parseEther("200000")}, + ], + [] + ) + ); + + await advanceTimeAndBlock(7200); + + const txRecepient = await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [ + {tokenId: 0, amount: parseEther("200000")}, + {tokenId: 1, amount: parseEther("200000")}, + {tokenId: 2, amount: parseEther("200000")}, + {tokenId: 3, amount: parseEther("200000")}, + {tokenId: 4, amount: parseEther("200000")}, + ], + _nftPairs: [], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0, 1, 2, 3, 4], + bakcTokenIds: [], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + console.log("-----------------gas used:", txRecepient.gasUsed.toString()); + }); + + it("gas test: test 10 pair of BAYC position migration", async () => { + const { + users: [user1], + bayc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "10", user1, true); + await mintAndValidate(ape, "2000000", user1); + + await waitForTx( + await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + + await waitForTx( + await pool.connect(user1.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("2000000"), + }, + [ + {tokenId: 0, amount: parseEther("200000")}, + {tokenId: 1, amount: parseEther("200000")}, + {tokenId: 2, amount: parseEther("200000")}, + {tokenId: 3, amount: parseEther("200000")}, + {tokenId: 4, amount: parseEther("200000")}, + {tokenId: 5, amount: parseEther("200000")}, + {tokenId: 6, amount: parseEther("200000")}, + {tokenId: 7, amount: parseEther("200000")}, + {tokenId: 8, amount: parseEther("200000")}, + {tokenId: 9, amount: parseEther("200000")}, + ], + [] + ) + ); + + await advanceTimeAndBlock(7200); + + const txRecepient = await waitForTx( + await pool.connect(user1.signer).apeStakingMigration( + [ + { + nftAsset: bayc.address, + _nfts: [ + {tokenId: 0, amount: parseEther("200000")}, + {tokenId: 1, amount: parseEther("200000")}, + {tokenId: 2, amount: parseEther("200000")}, + {tokenId: 3, amount: parseEther("200000")}, + {tokenId: 4, amount: parseEther("200000")}, + {tokenId: 5, amount: parseEther("200000")}, + {tokenId: 6, amount: parseEther("200000")}, + {tokenId: 7, amount: parseEther("200000")}, + {tokenId: 8, amount: parseEther("200000")}, + {tokenId: 9, amount: parseEther("200000")}, + ], + _nftPairs: [], + }, + ], + [ + { + PoolId: 6, + apeTokenIds: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + bakcTokenIds: [], + }, + ], + { + asset: ape.address, + cashAmount: 0, + borrowAmount: 0, + openSApeCollateralFlag: true, + } + ) + ); + console.log("-----------------gas used:", txRecepient.gasUsed.toString()); + });*/ +}); diff --git a/test/_pool_ape_staking.spec.ts b/test/_pool_ape_staking.spec.ts index fc0ab19d4..a0b22fa5e 100644 --- a/test/_pool_ape_staking.spec.ts +++ b/test/_pool_ape_staking.spec.ts @@ -151,11 +151,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "16000"); await expect( - pool.connect(user1.signer).borrowApeAndStake( + pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount1}], @@ -179,11 +180,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "16000"); await expect( - pool.connect(user1.signer).borrowApeAndStake( + pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount1}], @@ -210,11 +212,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount1}], @@ -267,11 +270,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -324,11 +328,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -380,11 +385,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -452,11 +458,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -534,11 +541,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -569,11 +577,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -605,11 +614,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -686,11 +696,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -766,11 +777,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -830,11 +842,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -901,11 +914,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: cApe.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -964,11 +978,12 @@ describe("APE Coin Staking Test", () => { const amount1 = parseEther("7000"); const amount2 = parseEther("8000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount1, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -976,11 +991,12 @@ describe("APE Coin Staking Test", () => { ) ); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: cApe.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: 0, }, [], @@ -1028,11 +1044,12 @@ describe("APE Coin Staking Test", () => { const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -1081,11 +1098,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -1131,11 +1149,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: cApe.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -1204,11 +1223,12 @@ describe("APE Coin Staking Test", () => { const halfAmount = await convertToCurrencyDecimals(cApe.address, "9000"); const totalAmount = await convertToCurrencyDecimals(cApe.address, "18000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: cApe.address, borrowAmount: halfAmount, + cashAsset: ape.address, cashAmount: 0, }, [ @@ -1220,11 +1240,12 @@ describe("APE Coin Staking Test", () => { ); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: bayc.address, borrowAsset: cApe.address, borrowAmount: halfAmount, + cashAsset: ape.address, cashAmount: 0, }, [ @@ -1332,11 +1353,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "7008"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -1410,11 +1432,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "15000"); await expect( - pool.connect(user1.signer).borrowApeAndStake( + pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -1440,11 +1463,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount1}], @@ -1507,11 +1531,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(taker.signer).borrowApeAndStake( + await pool.connect(taker.signer).borrowApeAndStakeV2( { nftAsset: bayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount1}], @@ -1576,11 +1601,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); expect( - await pool.connect(maker.signer).borrowApeAndStake( + await pool.connect(maker.signer).borrowApeAndStakeV2( { nftAsset: bayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount1}], @@ -1641,11 +1667,12 @@ describe("APE Coin Staking Test", () => { const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -1680,11 +1707,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = await convertToCurrencyDecimals(ape.address, "15000"); await expect( - pool.connect(user1.signer).borrowApeAndStake( + pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: bayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount1}], @@ -1708,11 +1736,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = amount1.add(amount2); await expect( - pool.connect(user1.signer).borrowApeAndStake( + pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: bayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount1}], @@ -1740,11 +1769,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); const amount = amount1.add(amount2); await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: bayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: amount1, }, [], @@ -1807,11 +1837,12 @@ describe("APE Coin Staking Test", () => { ); await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: bayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -1820,11 +1851,12 @@ describe("APE Coin Staking Test", () => { ); await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: bayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: 0, }, [], @@ -1886,11 +1918,12 @@ describe("APE Coin Staking Test", () => { ); await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -1899,11 +1932,12 @@ describe("APE Coin Staking Test", () => { ); await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: 0, }, [], @@ -1962,11 +1996,12 @@ describe("APE Coin Staking Test", () => { ["mint(address,uint256)"](user1.address, amount); await expect( - pool.connect(user1.signer).borrowApeAndStake( + pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: bayc.address, borrowAsset: ape.address, borrowAmount: amount1, + cashAsset: ape.address, cashAmount: amount2, }, [{tokenId: 0, amount: amount1}], @@ -2006,11 +2041,12 @@ describe("APE Coin Staking Test", () => { expect(healthFactor).to.be.lt(parseEther("1")); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [], @@ -2070,11 +2106,12 @@ describe("APE Coin Staking Test", () => { const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -2143,11 +2180,12 @@ describe("APE Coin Staking Test", () => { const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -2195,11 +2233,12 @@ describe("APE Coin Staking Test", () => { const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -2247,11 +2286,12 @@ describe("APE Coin Staking Test", () => { const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -2305,11 +2345,12 @@ describe("APE Coin Staking Test", () => { const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount2, + cashAsset: ape.address, cashAmount: amount1, }, [{tokenId: 0, amount: amount1}], @@ -2352,11 +2393,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "15000"); // 2. stake one bakc and borrow 15000 ape await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -2429,11 +2471,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "15000"); // borrow and stake 15000 await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -2495,11 +2538,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "15000"); // 2. stake one bakc and borrow 15000 ape await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -2555,11 +2599,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "15000"); // 2. stake one bakc and borrow 15000 ape await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -2630,11 +2675,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "15000"); // 2. stake one bakc and borrow 15000 ape await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -2726,11 +2772,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "15000"); // 2. stake one bakc and borrow 15000 ape await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -2809,11 +2856,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "1000"); // 2. stake one bakc and borrow 15000 ape await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount}], @@ -2877,11 +2925,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "15000"); // 2. stake one bakc and borrow 15000 ape await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [{tokenId: 0, amount: amount1}], @@ -2966,11 +3015,12 @@ describe("APE Coin Staking Test", () => { expect(isUsingAsCollateral(configDataBefore, sApeReserveData.id)).false; expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [{tokenId: 0, amount: amount1}], diff --git a/test/para_ape_staking.spec.ts b/test/para_ape_staking.spec.ts index db2816e04..6f645a50a 100644 --- a/test/para_ape_staking.spec.ts +++ b/test/para_ape_staking.spec.ts @@ -128,10 +128,12 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositPairNFT(true, [0, 1], [0, 1]) + .depositPairNFT(user1.address, true, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).depositPairNFT(true, [2], [2]) + await paraApeStaking + .connect(user2.signer) + .depositPairNFT(user2.address, true, [2], [2]) ); expect(await bayc.ownerOf(0)).to.be.equal(paraApeStaking.address); expect(await bayc.ownerOf(1)).to.be.equal(paraApeStaking.address); @@ -290,10 +292,12 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositPairNFT(false, [0, 1], [0, 1]) + .depositPairNFT(user1.address, false, [0, 1], [0, 1]) ); await waitForTx( - await paraApeStaking.connect(user2.signer).depositPairNFT(false, [2], [2]) + await paraApeStaking + .connect(user2.signer) + .depositPairNFT(user2.address, false, [2], [2]) ); expect(await mayc.ownerOf(0)).to.be.equal(paraApeStaking.address); expect(await mayc.ownerOf(1)).to.be.equal(paraApeStaking.address); @@ -446,17 +450,17 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1, 2]) + .depositNFT(user1.address, bayc.address, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user2.signer) - .depositNFT(mayc.address, [0, 1, 2]) + .depositNFT(user2.address, mayc.address, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user3.signer) - .depositNFT(bakc.address, [0, 1, 2]) + .depositNFT(user3.address, bakc.address, [0, 1, 2]) ); expect(await bayc.ownerOf(0)).to.be.equal(paraApeStaking.address); expect(await bayc.ownerOf(1)).to.be.equal(paraApeStaking.address); @@ -693,23 +697,33 @@ describe("Para Ape Staking Test", () => { await supplyAndValidate(bakc, "3", user1, false); await expect( - paraApeStaking.connect(user2.signer).depositPairNFT(true, [0, 1], [0, 1]) + paraApeStaking + .connect(user2.signer) + .depositPairNFT(user2.address, true, [0, 1], [0, 1]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); await expect( - paraApeStaking.connect(user1.signer).depositPairNFT(true, [0], [0]) + paraApeStaking + .connect(user1.signer) + .depositPairNFT(user1.address, true, [0], [0]) ).to.be.revertedWith(ProtocolErrors.APE_POSITION_EXISTED); await expect( - paraApeStaking.connect(user1.signer).depositPairNFT(true, [2], [1]) + paraApeStaking + .connect(user1.signer) + .depositPairNFT(user1.address, true, [2], [1]) ).to.be.revertedWith(ProtocolErrors.BAKC_POSITION_EXISTED); await expect( - paraApeStaking.connect(user1.signer).depositPairNFT(true, [1], [0]) + paraApeStaking + .connect(user1.signer) + .depositPairNFT(user1.address, true, [1], [0]) ).to.be.revertedWith(ProtocolErrors.PAIR_POSITION_EXISTED); await waitForTx( - await paraApeStaking.connect(user1.signer).depositPairNFT(true, [2], [2]) + await paraApeStaking + .connect(user1.signer) + .depositPairNFT(user1.address, true, [2], [2]) ); }); @@ -726,7 +740,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositPairNFT(true, [0, 1, 2], [0, 1, 2]) + .depositPairNFT(user1.address, true, [0, 1, 2], [0, 1, 2]) ); await expect( @@ -747,7 +761,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositPairNFT(true, [0, 1, 2], [0, 1, 2]) + .depositPairNFT(user1.address, true, [0, 1, 2], [0, 1, 2]) ); await expect( @@ -775,7 +789,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositPairNFT(true, [0, 1, 2], [0, 1, 2]) + .depositPairNFT(user1.address, true, [0, 1, 2], [0, 1, 2]) ); await waitForTx( @@ -821,7 +835,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositPairNFT(true, [0, 1, 2], [0, 1, 2]) + .depositPairNFT(user1.address, true, [0, 1, 2], [0, 1, 2]) ); await waitForTx( @@ -893,23 +907,33 @@ describe("Para Ape Staking Test", () => { await supplyAndValidate(bakc, "3", user1, false); await expect( - paraApeStaking.connect(user2.signer).depositNFT(bayc.address, [0]) + paraApeStaking + .connect(user2.signer) + .depositNFT(user2.address, bayc.address, [0]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); await expect( - paraApeStaking.connect(user1.signer).depositNFT(bayc.address, [0]) + paraApeStaking + .connect(user1.signer) + .depositNFT(user1.address, bayc.address, [0]) ).to.be.revertedWith(ProtocolErrors.APE_POSITION_EXISTED); await expect( - paraApeStaking.connect(user1.signer).depositNFT(bayc.address, [1]) + paraApeStaking + .connect(user1.signer) + .depositNFT(user1.address, bayc.address, [1]) ).to.be.revertedWith(ProtocolErrors.PAIR_POSITION_EXISTED); await expect( - paraApeStaking.connect(user2.signer).depositNFT(bakc.address, [0]) + paraApeStaking + .connect(user2.signer) + .depositNFT(user2.address, bakc.address, [0]) ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); await expect( - paraApeStaking.connect(user1.signer).depositNFT(bakc.address, [1]) + paraApeStaking + .connect(user1.signer) + .depositNFT(user1.address, bakc.address, [1]) ).to.be.revertedWith(ProtocolErrors.APE_POSITION_EXISTED); }); @@ -924,7 +948,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1]) + .depositNFT(user1.address, bayc.address, [0, 1]) ); await expect( @@ -949,12 +973,12 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1]) + .depositNFT(user1.address, bayc.address, [0, 1]) ); await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bakc.address, [0, 1]) + .depositNFT(user1.address, bakc.address, [0, 1]) ); await expect( @@ -996,7 +1020,7 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1]) + .depositNFT(user1.address, bayc.address, [0, 1]) ); await waitForTx( @@ -1027,12 +1051,12 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1]) + .depositNFT(user1.address, bayc.address, [0, 1]) ); await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bakc.address, [0, 1]) + .depositNFT(user1.address, bakc.address, [0, 1]) ); await waitForTx( @@ -1089,12 +1113,12 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1]) + .depositNFT(user1.address, bayc.address, [0, 1]) ); await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bakc.address, [0, 1]) + .depositNFT(user1.address, bakc.address, [0, 1]) ); await waitForTx( @@ -1184,12 +1208,12 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1]) + .depositNFT(user1.address, bayc.address, [0, 1]) ); await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bakc.address, [0, 1]) + .depositNFT(user1.address, bakc.address, [0, 1]) ); await waitForTx( @@ -1262,20 +1286,24 @@ describe("Para Ape Staking Test", () => { await supplyAndValidate(bakc, "4", user1, true); let tx0 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ + user1.address, true, [0, 1], [0, 1], ]); let tx1 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ + user1.address, false, [0, 1], [2, 3], ]); let tx2 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + user1.address, bayc.address, [2, 3], ]); let tx3 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + user1.address, mayc.address, [2, 3], ]); @@ -1393,17 +1421,17 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1]) + .depositNFT(user1.address, bayc.address, [0, 1]) ); await waitForTx( await paraApeStaking .connect(user2.signer) - .depositNFT(mayc.address, [0, 1]) + .depositNFT(user2.address, mayc.address, [0, 1]) ); await waitForTx( await paraApeStaking .connect(user3.signer) - .depositNFT(bakc.address, [0, 1, 2, 3]) + .depositNFT(user3.address, bakc.address, [0, 1, 2, 3]) ); await waitForTx( @@ -1527,17 +1555,17 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1, 2]) + .depositNFT(user1.address, bayc.address, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user2.signer) - .depositNFT(mayc.address, [0, 1, 2]) + .depositNFT(user2.address, mayc.address, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user3.signer) - .depositNFT(bakc.address, [0, 1, 2, 3]) + .depositNFT(user3.address, bakc.address, [0, 1, 2, 3]) ); await waitForTx( @@ -1629,17 +1657,17 @@ describe("Para Ape Staking Test", () => { await waitForTx( await paraApeStaking .connect(user1.signer) - .depositNFT(bayc.address, [0, 1, 2]) + .depositNFT(user1.address, bayc.address, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user2.signer) - .depositNFT(mayc.address, [0, 1, 2]) + .depositNFT(user2.address, mayc.address, [0, 1, 2]) ); await waitForTx( await paraApeStaking .connect(user3.signer) - .depositNFT(bakc.address, [0, 1, 2, 3]) + .depositNFT(user3.address, bakc.address, [0, 1, 2, 3]) ); await waitForTx( From c108a66327488c7b5997abc8a3f62771733c2879 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 13 Sep 2023 09:00:05 +0800 Subject: [PATCH 95/99] chore: p2p migration --- contracts/apestaking/ParaApeStaking.sol | 63 +- .../apestaking/logic/ApeStakingP2PLogic.sol | 2 + contracts/interfaces/IApeStakingP2P.sol | 2 + .../deployments/steps/23_renounceOwnership.ts | 42 + test/_ape_staking_p2p_migration.spec.ts | 418 +++++ test/helpers/p2ppairstaking-helper.ts | 4 +- test/p2p_pair_staking.spec.ts | 934 +++++------ test/para_p2p_ape_staking.spec.ts | 1407 +++++++++++++++++ 8 files changed, 2338 insertions(+), 534 deletions(-) create mode 100644 test/_ape_staking_p2p_migration.spec.ts create mode 100644 test/para_p2p_ape_staking.spec.ts diff --git a/contracts/apestaking/ParaApeStaking.sol b/contracts/apestaking/ParaApeStaking.sol index 6997b3e7a..f7f90675a 100644 --- a/contracts/apestaking/ParaApeStaking.sol +++ b/contracts/apestaking/ParaApeStaking.sol @@ -50,21 +50,21 @@ contract ParaApeStaking is uint16 public immutable sApeReserveId; address private immutable psApe; - //record all pool states - mapping(uint256 => PoolState) public poolStates; - - //record user sApe balance - mapping(address => SApeBalance) private sApeBalance; - //P2P storage bytes32 internal DOMAIN_SEPARATOR; mapping(bytes32 => ListingOrderStatus) public listingOrderStatus; mapping(bytes32 => MatchedOrder) public matchedOrders; - - //record Ape in P2P and ApeCoin pool mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; mapping(address => uint256) private cApeShareBalance; + uint256[5] private __gap; + + //record all pool states + mapping(uint256 => PoolState) public poolStates; + + //record user sApe balance + mapping(address => SApeBalance) private sApeBalance; + address public apeStakingBot; uint64 public compoundFee; uint32 apePairStakingRewardRatio; @@ -129,11 +129,52 @@ contract ParaApeStaking is ); //approve ApeCoin for apeCoinStaking - IERC20(apeCoin).safeApprove(address(apeCoinStaking), type(uint256).max); + uint256 allowance = IERC20(apeCoin).allowance( + address(this), + address(apeCoinStaking) + ); + if (allowance == 0) { + IERC20(apeCoin).safeApprove( + address(apeCoinStaking), + type(uint256).max + ); + } + //approve ApeCoin for cApe - IERC20(apeCoin).safeApprove(cApe, type(uint256).max); + allowance = IERC20(apeCoin).allowance(address(this), address(cApe)); + if (allowance == 0) { + IERC20(apeCoin).safeApprove(cApe, type(uint256).max); + } + //approve cApe for pool - IERC20(cApe).safeApprove(pool, type(uint256).max); + allowance = IERC20(cApe).allowance(address(this), pool); + if (allowance == 0) { + IERC20(cApe).safeApprove(pool, type(uint256).max); + } + } + + //only for migration + function reset_initialize() external onlyPoolAdmin { + assembly { + sstore(0, 0) + } + } + + //only for migration + function updateP2PSApeBalance( + bytes32[] calldata orderHashes + ) external onlyPoolAdmin { + uint256 orderlength = orderHashes.length; + for (uint256 i = 0; i < orderlength; i++) { + bytes32 orderHash = orderHashes[i]; + MatchedOrder storage order = matchedOrders[orderHash]; + uint128 apePrincipleAmount = order.apePrincipleAmount.toUint128(); + if (apePrincipleAmount > 0) { + order.apePrincipleAmount = 0; + sApeBalance[order.apeCoinOfferer] + .stakedBalance += apePrincipleAmount; + } + } } /** diff --git a/contracts/apestaking/logic/ApeStakingP2PLogic.sol b/contracts/apestaking/logic/ApeStakingP2PLogic.sol index 578897de1..3d13ec728 100644 --- a/contracts/apestaking/logic/ApeStakingP2PLogic.sol +++ b/contracts/apestaking/logic/ApeStakingP2PLogic.sol @@ -127,6 +127,7 @@ library ApeStakingP2PLogic { bakcShare: 0, apeCoinOfferer: apeCoinOrder.offerer, apeCoinShare: apeCoinOrder.share, + apePrincipleAmount: 0, apeCoinListingOrderHash: apeCoinListingOrderHash }); orderHash = getMatchedOrderHash(matchedOrder); @@ -222,6 +223,7 @@ library ApeStakingP2PLogic { bakcShare: bakcOrder.share, apeCoinOfferer: apeCoinOrder.offerer, apeCoinShare: apeCoinOrder.share, + apePrincipleAmount: 0, apeCoinListingOrderHash: apeCoinListingOrderHash }); orderHash = getMatchedOrderHash(matchedOrder); diff --git a/contracts/interfaces/IApeStakingP2P.sol b/contracts/interfaces/IApeStakingP2P.sol index 93a11dc7d..35c75c598 100644 --- a/contracts/interfaces/IApeStakingP2P.sol +++ b/contracts/interfaces/IApeStakingP2P.sol @@ -36,6 +36,8 @@ interface IApeStakingP2P { uint32 bakcShare; address apeCoinOfferer; uint32 apeCoinShare; + //used for placeholder when P2P migration to ParaApeStaking, will always be 0 in ParaApeStaking + uint256 apePrincipleAmount; bytes32 apeCoinListingOrderHash; } diff --git a/scripts/deployments/steps/23_renounceOwnership.ts b/scripts/deployments/steps/23_renounceOwnership.ts index 3221fca4c..316f6c1a5 100644 --- a/scripts/deployments/steps/23_renounceOwnership.ts +++ b/scripts/deployments/steps/23_renounceOwnership.ts @@ -7,6 +7,7 @@ import { getHelperContract, getInitializableAdminUpgradeabilityProxy, getNFTFloorOracle, + getP2PPairStaking, getParaApeStaking, getPausableZoneController, getPoolAddressesProvider, @@ -371,6 +372,47 @@ export const step_23 = async ( console.log(); } + //////////////////////////////////////////////////////////////////////////////// + // P2PPairStaking + //////////////////////////////////////////////////////////////////////////////// + if (await getContractAddressInDb(eContractid.P2PPairStaking)) { + console.time("transferring P2PPairStaking ownership..."); + const p2pPairStaking = await getP2PPairStaking(); + const p2pPairStakingProxy = + await getInitializableAdminUpgradeabilityProxy(p2pPairStaking.address); + if (DRY_RUN) { + const encodedData1 = p2pPairStakingProxy.interface.encodeFunctionData( + "changeAdmin", + [paraSpaceAdminAddress] + ); + await dryRunEncodedData(p2pPairStakingProxy.address, encodedData1); + if (gatewayAdminAddress !== paraSpaceAdminAddress) { + const encodedData2 = p2pPairStaking.interface.encodeFunctionData( + "transferOwnership", + [gatewayAdminAddress] + ); + await dryRunEncodedData(p2pPairStaking.address, encodedData2); + } + } else { + await waitForTx( + await p2pPairStakingProxy.changeAdmin( + paraSpaceAdminAddress, + GLOBAL_OVERRIDES + ) + ); + if (gatewayAdminAddress !== paraSpaceAdminAddress) { + await waitForTx( + await p2pPairStaking.transferOwnership( + gatewayAdminAddress, + GLOBAL_OVERRIDES + ) + ); + } + } + console.timeEnd("transferring P2PPairStaking ownership..."); + console.log(); + } + //////////////////////////////////////////////////////////////////////////////// // ParaApeStaking //////////////////////////////////////////////////////////////////////////////// diff --git a/test/_ape_staking_p2p_migration.spec.ts b/test/_ape_staking_p2p_migration.spec.ts new file mode 100644 index 000000000..eab7cc289 --- /dev/null +++ b/test/_ape_staking_p2p_migration.spec.ts @@ -0,0 +1,418 @@ +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect} from "chai"; +import {AutoCompoundApe, P2PPairStaking, ParaApeStaking} from "../types"; +import {TestEnv} from "./helpers/make-suite"; +import {testEnvFixture} from "./helpers/setup-env"; +import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; +import { + getAutoCompoundApe, + getFirstSigner, + getInitializableAdminUpgradeabilityProxy, + getP2PPairStaking, + getParaApeStaking, +} from "../helpers/contracts-getters"; +import {MAX_UINT_AMOUNT} from "../helpers/constants"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; +import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; +import {parseEther} from "ethers/lib/utils"; +import {almostEqual} from "./helpers/uniswapv3-helper"; +import {deployParaApeStakingImpl} from "../helpers/contracts-deployments"; +import {GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; +import {getEthersSigners, getProxyAdmin} from "../helpers/contracts-helpers"; + +describe("P2P Pair Staking Migration Test", () => { + let testEnv: TestEnv; + let p2pPairStaking: P2PPairStaking; + let paraApeStaking: ParaApeStaking; + let cApe: AutoCompoundApe; + let MINIMUM_LIQUIDITY; + + const fixture = async () => { + testEnv = await loadFixture(testEnvFixture); + const { + ape, + users: [user1, user2, , , , user6], + apeCoinStaking, + } = testEnv; + + p2pPairStaking = await getP2PPairStaking(); + + cApe = await getAutoCompoundApe(); + MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + + // send extra tokens to the apestaking contract for rewards + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"]( + apeCoinStaking.address, + parseEther("100000000000") + ) + ); + + // user4 deposit MINIMUM_LIQUIDITY to make test case easy + await mintAndValidate(ape, "1", user6); + await waitForTx( + await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) + ); + + //user2 deposit free sApe + await mintAndValidate(ape, "10000000", user2); + await waitForTx( + await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user2.signer) + .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) + ); + + return testEnv; + }; + + it("test BAYC pair with BAKC and ApeCoin Staking", async () => { + const { + users: [user1, user2, user3], + bayc, + mayc, + bakc, + nBAYC, + nMAYC, + nBAKC, + poolAdmin, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(bakc, "2", user3, true); + + //bayc staking + const baycApeAmount = await p2pPairStaking.getApeCoinStakingCap(0); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, baycApeAmount) + ); + + let user1SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + let user2SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 0, + cApe.address, + 0, + 8000, + user2 + ); + + let txReceipt = await waitForTx( + await p2pPairStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + let logLength = txReceipt.logs.length; + const tx0OrderHash = txReceipt.logs[logLength - 1].data; + + //mayc staking + const maycApeAmount = await p2pPairStaking.getApeCoinStakingCap(1); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, maycApeAmount) + ); + + user1SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 1, + mayc.address, + 0, + 2000, + user1 + ); + user2SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 1, + cApe.address, + 0, + 8000, + user2 + ); + + txReceipt = await waitForTx( + await p2pPairStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + logLength = txReceipt.logs.length; + const tx1OrderHash = txReceipt.logs[logLength - 1].data; + + //bayc + bakc pair staking + const pairApeAmount = await p2pPairStaking.getApeCoinStakingCap(2); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, pairApeAmount) + ); + + user1SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 2, + bayc.address, + 0, + 2000, + user1 + ); + let user3SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 2, + bakc.address, + 0, + 2000, + user3 + ); + user2SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 2, + cApe.address, + 0, + 6000, + user2 + ); + + txReceipt = await waitForTx( + await p2pPairStaking + .connect(user1.signer) + .matchBAKCPairStakingList( + user1SignedOrder, + user3SignedOrder, + user2SignedOrder + ) + ); + + logLength = txReceipt.logs.length; + const tx2OrderHash = txReceipt.logs[logLength - 1].data; + + //mayc + bakc pair staking + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, pairApeAmount) + ); + + user1SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 2, + mayc.address, + 0, + 2000, + user1 + ); + user3SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 2, + bakc.address, + 1, + 2000, + user3 + ); + user2SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 2, + cApe.address, + 1, + 6000, + user2 + ); + + txReceipt = await waitForTx( + await p2pPairStaking + .connect(user1.signer) + .matchBAKCPairStakingList( + user1SignedOrder, + user3SignedOrder, + user2SignedOrder + ) + ); + + logLength = txReceipt.logs.length; + const tx3OrderHash = txReceipt.logs[logLength - 1].data; + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await p2pPairStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([ + tx0OrderHash, + tx1OrderHash, + tx2OrderHash, + tx3OrderHash, + ]) + ); + + //check status + expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(0); + expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(0); + expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(0); + + let matchedOrder0 = await p2pPairStaking.matchedOrders(tx0OrderHash); + expect(matchedOrder0.apePrincipleAmount).to.be.equal(baycApeAmount); + let matchedOrder1 = await p2pPairStaking.matchedOrders(tx1OrderHash); + expect(matchedOrder1.apePrincipleAmount).to.be.equal(maycApeAmount); + let matchedOrder2 = await p2pPairStaking.matchedOrders(tx2OrderHash); + expect(matchedOrder2.apePrincipleAmount).to.be.equal(pairApeAmount); + let matchedOrder3 = await p2pPairStaking.matchedOrders(tx3OrderHash); + expect(matchedOrder3.apePrincipleAmount).to.be.equal(pairApeAmount); + + //720 * 3 + almostEqual( + await p2pPairStaking.pendingCApeReward(user1.address), + parseEther("2160") + ); + //2880*2 + 2160 = 7920 + almostEqual( + await p2pPairStaking.pendingCApeReward(user2.address), + parseEther("7920") + ); + //720 + almostEqual( + await p2pPairStaking.pendingCApeReward(user3.address), + parseEther("720") + ); + + //upgrade to ParaApeStaking + const paraApeStakingImpl = await deployParaApeStakingImpl(false); + const paraApeStakingProxy = await getInitializableAdminUpgradeabilityProxy( + p2pPairStaking.address + ); + await waitForTx( + await paraApeStakingProxy + .connect(poolAdmin.signer) + .upgradeTo(paraApeStakingImpl.address, GLOBAL_OVERRIDES) + ); + const signers = await getEthersSigners(); + const adminAddress = await signers[5].getAddress(); + await waitForTx( + await paraApeStakingProxy + .connect(poolAdmin.signer) + .changeAdmin(adminAddress, GLOBAL_OVERRIDES) + ); + paraApeStaking = await getParaApeStaking(p2pPairStaking.address); + + //check new status + expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(0); + expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(0); + expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(0); + matchedOrder0 = await paraApeStaking.matchedOrders(tx0OrderHash); + expect(matchedOrder0.apePrincipleAmount).to.be.equal(baycApeAmount); + matchedOrder1 = await paraApeStaking.matchedOrders(tx1OrderHash); + expect(matchedOrder1.apePrincipleAmount).to.be.equal(maycApeAmount); + matchedOrder2 = await paraApeStaking.matchedOrders(tx2OrderHash); + expect(matchedOrder2.apePrincipleAmount).to.be.equal(pairApeAmount); + matchedOrder3 = await paraApeStaking.matchedOrders(tx3OrderHash); + expect(matchedOrder3.apePrincipleAmount).to.be.equal(pairApeAmount); + + expect(await paraApeStaking.paused()).to.be.equal(true); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + 0 + ); + + await expect( + paraApeStaking.connect(user1.signer).initialize() + ).to.be.revertedWith("Initializable: contract is already initialized"); + + await waitForTx( + await paraApeStaking.connect(poolAdmin.signer).reset_initialize() + ); + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .updateP2PSApeBalance([ + tx0OrderHash, + tx1OrderHash, + tx2OrderHash, + tx3OrderHash, + ]) + ); + await waitForTx(await paraApeStaking.connect(user1.signer).initialize()); + + expect(await paraApeStaking.paused()).to.be.equal(false); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + parseEther("400000") + ); + + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("2160") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("7920") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user3.address), + parseEther("720") + ); + + //breakup + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .breakUpMatchedOrder(tx0OrderHash) + ); + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .breakUpMatchedOrder(tx1OrderHash) + ); + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .breakUpMatchedOrder(tx2OrderHash) + ); + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .breakUpMatchedOrder(tx3OrderHash) + ); + + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + 0 + ); + + //check status + expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); + expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); + expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(2); + + matchedOrder0 = await paraApeStaking.matchedOrders(tx0OrderHash); + expect(matchedOrder0.apePrincipleAmount).to.be.equal(0); + matchedOrder1 = await paraApeStaking.matchedOrders(tx1OrderHash); + expect(matchedOrder1.apePrincipleAmount).to.be.equal(0); + matchedOrder2 = await paraApeStaking.matchedOrders(tx2OrderHash); + expect(matchedOrder2.apePrincipleAmount).to.be.equal(0); + matchedOrder3 = await paraApeStaking.matchedOrders(tx3OrderHash); + expect(matchedOrder3.apePrincipleAmount).to.be.equal(0); + + //claim cApe reward + await waitForTx( + await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) + ); + await waitForTx( + await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) + ); + await waitForTx( + await p2pPairStaking.connect(user3.signer).claimCApeReward(user3.address) + ); + expect(await paraApeStaking.pendingCApeReward(user1.address)).to.be.equal( + 0 + ); + expect(await paraApeStaking.pendingCApeReward(user2.address)).to.be.equal( + 0 + ); + expect(await paraApeStaking.pendingCApeReward(user3.address)).to.be.equal( + 0 + ); + }); +}); diff --git a/test/helpers/p2ppairstaking-helper.ts b/test/helpers/p2ppairstaking-helper.ts index 1784378d0..aa9d65b17 100644 --- a/test/helpers/p2ppairstaking-helper.ts +++ b/test/helpers/p2ppairstaking-helper.ts @@ -1,5 +1,5 @@ import {DRE} from "../../helpers/misc-utils"; -import {ParaApeStaking} from "../../types"; +import {P2PPairStaking, ParaApeStaking} from "../../types"; import {SignerWithAddress} from "./make-suite"; import {convertSignatureToEIP2098} from "../../helpers/seaport-helpers/encoding"; import {BigNumberish, BytesLike} from "ethers"; @@ -18,7 +18,7 @@ export type ListingOrder = { }; export async function getSignedListingOrder( - p2pPairStaking: ParaApeStaking, + p2pPairStaking: ParaApeStaking | P2PPairStaking, stakingType: number, listingToken: string, tokenId: number, diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts index 748ee7484..6cedac7df 100644 --- a/test/p2p_pair_staking.spec.ts +++ b/test/p2p_pair_staking.spec.ts @@ -1,28 +1,22 @@ import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; import {expect} from "chai"; -import {AutoCompoundApe, ParaApeStaking} from "../types"; +import {AutoCompoundApe, P2PPairStaking} from "../types"; import {TestEnv} from "./helpers/make-suite"; import {testEnvFixture} from "./helpers/setup-env"; -import { - changePriceAndValidate, - changeSApePriceAndValidate, - mintAndValidate, - supplyAndValidate, -} from "./helpers/validated-steps"; +import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; import { getAutoCompoundApe, - getParaApeStaking, + getP2PPairStaking, } from "../helpers/contracts-getters"; -import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; +import {MAX_UINT_AMOUNT} from "../helpers/constants"; import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; import {parseEther} from "ethers/lib/utils"; import {almostEqual} from "./helpers/uniswapv3-helper"; -import {ProtocolErrors} from "../helpers/types"; describe("P2P Pair Staking Test", () => { let testEnv: TestEnv; - let paraApeStaking: ParaApeStaking; + let p2pPairStaking: P2PPairStaking; let cApe: AutoCompoundApe; let MINIMUM_LIQUIDITY; @@ -30,18 +24,11 @@ describe("P2P Pair Staking Test", () => { testEnv = await loadFixture(testEnvFixture); const { ape, - users: [user1, user2, , user4, , user6], + users: [user1, user2, , user4], apeCoinStaking, - poolAdmin, } = testEnv; - paraApeStaking = await getParaApeStaking(); - - await waitForTx( - await paraApeStaking - .connect(poolAdmin.signer) - .setApeStakingBot(user4.address) - ); + p2pPairStaking = await getP2PPairStaking(); cApe = await getAutoCompoundApe(); MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); @@ -57,21 +44,22 @@ describe("P2P Pair Staking Test", () => { ); // user4 deposit MINIMUM_LIQUIDITY to make test case easy - await mintAndValidate(ape, "1", user6); + await mintAndValidate(ape, "1", user4); await waitForTx( - await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) + await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) ); await waitForTx( - await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) + await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) ); - //user2 deposit free sApe await waitForTx( - await ape + await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe .connect(user2.signer) - .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) ); - await mintAndValidate(ape, "1000000", user2); return testEnv; }; @@ -85,16 +73,15 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "1", user1, true); + await mintAndValidate(ape, "1000000", user2); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, bayc.address, 0, @@ -102,51 +89,43 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, - ONE_ADDRESS, + cApe.address, 0, 8000, user2 ); const txReceipt = await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ); const logLength = txReceipt.logs.length; const orderHash = txReceipt.logs[logLength - 1].data; - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - "0", - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - apeAmount - ); - await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); almostEqual( - await paraApeStaking.pendingCApeReward(user1.address), + await p2pPairStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user2.address), + await p2pPairStaking.pendingCApeReward(user2.address), parseEther("2880") ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) + await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) ); almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); @@ -160,24 +139,17 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) ); expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - apeAmount, - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - "0" - ); - + almostEqual(await cApe.balanceOf(user2.address), apeAmount); almostEqual( - await paraApeStaking.pendingCApeReward(user1.address), + await p2pPairStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user2.address), + await p2pPairStaking.pendingCApeReward(user2.address), parseEther("2880") ); }); @@ -191,16 +163,21 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(mayc, "1", user1, true); + await mintAndValidate(ape, "1000000", user2); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(1); await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + await mayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(1); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 1, mayc.address, 0, @@ -208,51 +185,43 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 1, - ONE_ADDRESS, + cApe.address, 0, 8000, user2 ); const txReceipt = await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ); const logLength = txReceipt.logs.length; const orderHash = txReceipt.logs[logLength - 1].data; - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - "0", - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - apeAmount - ); - await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); almostEqual( - await paraApeStaking.pendingCApeReward(user1.address), + await p2pPairStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user2.address), + await p2pPairStaking.pendingCApeReward(user2.address), parseEther("2880") ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) + await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) ); almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); @@ -266,24 +235,17 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) ); expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - apeAmount, - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - "0" - ); - + almostEqual(await cApe.balanceOf(user2.address), apeAmount); almostEqual( - await paraApeStaking.pendingCApeReward(user1.address), + await p2pPairStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user2.address), + await p2pPairStaking.pendingCApeReward(user2.address), parseEther("2880") ); }); @@ -300,16 +262,26 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); + await mintAndValidate(ape, "1000000", user2); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await bakc + .connect(user3.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bayc.address, 0, @@ -317,7 +289,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bakc.address, 0, @@ -325,16 +297,16 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, - ONE_ADDRESS, + cApe.address, 0, 6000, user2 ); const txReceipt = await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, @@ -346,42 +318,34 @@ describe("P2P Pair Staking Test", () => { const logLength = txReceipt.logs.length; const orderHash = txReceipt.logs[logLength - 1].data; - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - "0", - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - apeAmount - ); - await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); almostEqual( - await paraApeStaking.pendingCApeReward(user1.address), + await p2pPairStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user3.address), + await p2pPairStaking.pendingCApeReward(user3.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user2.address), + await p2pPairStaking.pendingCApeReward(user2.address), parseEther("2160") ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) + await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) ); await waitForTx( - await paraApeStaking.connect(user3.signer).claimCApeReward(user3.address) + await p2pPairStaking.connect(user3.signer).claimCApeReward(user3.address) ); almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); @@ -396,29 +360,23 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) ); expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(1); - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - apeAmount, - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - "0" - ); + almostEqual(await cApe.balanceOf(user2.address), apeAmount); almostEqual( - await paraApeStaking.pendingCApeReward(user1.address), + await p2pPairStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user3.address), + await p2pPairStaking.pendingCApeReward(user3.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user2.address), + await p2pPairStaking.pendingCApeReward(user2.address), parseEther("2160") ); }); @@ -435,16 +393,25 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(mayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); + await mintAndValidate(ape, "1000000", user2); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + await mayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await bakc + .connect(user3.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, mayc.address, 0, @@ -452,7 +419,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bakc.address, 0, @@ -460,16 +427,16 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, - ONE_ADDRESS, + cApe.address, 0, 6000, user2 ); const txReceipt = await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, @@ -481,42 +448,34 @@ describe("P2P Pair Staking Test", () => { const logLength = txReceipt.logs.length; const orderHash = txReceipt.logs[logLength - 1].data; - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - "0", - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - apeAmount - ); - await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); almostEqual( - await paraApeStaking.pendingCApeReward(user1.address), + await p2pPairStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user3.address), + await p2pPairStaking.pendingCApeReward(user3.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user2.address), + await p2pPairStaking.pendingCApeReward(user2.address), parseEther("2160") ); await waitForTx( - await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) ); await waitForTx( - await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) + await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) ); await waitForTx( - await paraApeStaking.connect(user3.signer).claimCApeReward(user3.address) + await p2pPairStaking.connect(user3.signer).claimCApeReward(user3.address) ); almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); @@ -531,29 +490,22 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) ); expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(1); - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - apeAmount, - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - "0" - ); - + almostEqual(await cApe.balanceOf(user2.address), apeAmount); almostEqual( - await paraApeStaking.pendingCApeReward(user1.address), + await p2pPairStaking.pendingCApeReward(user1.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user3.address), + await p2pPairStaking.pendingCApeReward(user3.address), parseEther("720") ); almostEqual( - await paraApeStaking.pendingCApeReward(user2.address), + await p2pPairStaking.pendingCApeReward(user2.address), parseEther("2160") ); }); @@ -568,16 +520,28 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "10", user1, true); await supplyAndValidate(bakc, "10", user3, true); + await mintAndValidate(ape, "10000000", user2); + await waitForTx( - await paraApeStaking + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await bakc + .connect(user3.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await cApe .connect(user2.signer) - .depositFreeSApe(ape.address, parseEther("1000000")) + .deposit(user2.address, parseEther("1000000")) ); const txArray: string[] = []; for (let i = 0; i < 10; i++) { const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bayc.address, i, @@ -585,7 +549,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bakc.address, i, @@ -593,16 +557,16 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, - ONE_ADDRESS, + cApe.address, 0, 6000, user2 ); const txReceipt = await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, @@ -620,7 +584,7 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .claimForMatchedOrderAndCompound(txArray) ); @@ -635,15 +599,21 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "1", user1, true); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await mintAndValidate(ape, "1000000", user2); + + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); await waitForTx( - await paraApeStaking + await ape .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, bayc.address, 0, @@ -651,23 +621,23 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, - ONE_ADDRESS, + ape.address, 0, 8000, user2 ); await waitForTx( - await paraApeStaking.connect(user2.signer).cancelListing(user2SignedOrder) + await p2pPairStaking.connect(user2.signer).cancelListing(user2SignedOrder) ); await expect( - paraApeStaking + p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith(ProtocolErrors.INVALID_ORDER_STATUS); + ).to.be.revertedWith("order already cancelled"); }); it("match failed when order was canceled 1", async () => { @@ -680,15 +650,26 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user2, true); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); + await mintAndValidate(ape, "1000000", user3); + + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); await waitForTx( - await paraApeStaking + await bakc .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await ape + .connect(user3.signer) + .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bayc.address, 0, @@ -696,7 +677,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bakc.address, 0, @@ -704,27 +685,27 @@ describe("P2P Pair Staking Test", () => { user2 ); const user3SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, - ONE_ADDRESS, + ape.address, 0, 6000, user3 ); await waitForTx( - await paraApeStaking.connect(user3.signer).cancelListing(user3SignedOrder) + await p2pPairStaking.connect(user3.signer).cancelListing(user3SignedOrder) ); await expect( - paraApeStaking + p2pPairStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, user2SignedOrder, user3SignedOrder ) - ).to.be.revertedWith(ProtocolErrors.INVALID_ORDER_STATUS); + ).to.be.revertedWith("order already cancelled"); }); it("match failed when orders type match failed 0", async () => { @@ -735,15 +716,20 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "1", user1, true); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await mintAndValidate(ape, "1000000", user2); + await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, bayc.address, 0, @@ -751,19 +737,19 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 1, - ONE_ADDRESS, + cApe.address, 0, 8000, user2 ); await expect( - paraApeStaking + p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith(ProtocolErrors.ORDER_TYPE_MATCH_FAILED); + ).to.be.revertedWith("orders type match failed"); }); it("match failed when orders type match failed 1", async () => { @@ -776,15 +762,26 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); + await mintAndValidate(ape, "1000000", user2); + await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await bakc + .connect(user3.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bayc.address, 0, @@ -792,7 +789,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 1, bakc.address, 0, @@ -800,23 +797,23 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, - ONE_ADDRESS, + cApe.address, 0, 6000, user2 ); await expect( - paraApeStaking + p2pPairStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, user3SignedOrder, user2SignedOrder ) - ).to.be.revertedWith(ProtocolErrors.ORDER_TYPE_MATCH_FAILED); + ).to.be.revertedWith("orders type match failed"); }); it("match failed when share match failed 0", async () => { @@ -827,15 +824,20 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "1", user1, true); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await mintAndValidate(ape, "1000000", user2); + await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, bayc.address, 0, @@ -843,19 +845,19 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, - ONE_ADDRESS, + cApe.address, 0, 7000, user2 ); await expect( - paraApeStaking + p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith(ProtocolErrors.ORDER_SHARE_MATCH_FAILED); + ).to.be.revertedWith("share match failed"); }); it("match failed when share match failed 1", async () => { @@ -868,15 +870,26 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); + await mintAndValidate(ape, "1000000", user2); + await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await bakc + .connect(user3.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bayc.address, 0, @@ -884,7 +897,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bakc.address, 0, @@ -892,23 +905,23 @@ describe("P2P Pair Staking Test", () => { user3 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, - ONE_ADDRESS, + cApe.address, 0, 7000, user2 ); await expect( - paraApeStaking + p2pPairStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, user3SignedOrder, user2SignedOrder ) - ).to.be.revertedWith(ProtocolErrors.ORDER_SHARE_MATCH_FAILED); + ).to.be.revertedWith("share match failed"); }); it("listing order can only be canceled by offerer", async () => { @@ -918,7 +931,7 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, bayc.address, 0, @@ -927,53 +940,155 @@ describe("P2P Pair Staking Test", () => { ); await expect( - paraApeStaking.connect(user2.signer).cancelListing(user1SignedOrder) - ).to.be.revertedWith(ProtocolErrors.NOT_ORDER_OFFERER); + p2pPairStaking.connect(user2.signer).cancelListing(user1SignedOrder) + ).to.be.revertedWith("not order offerer"); + + await waitForTx( + await p2pPairStaking.connect(user1.signer).cancelListing(user1SignedOrder) + ); + }); + + it("matching operator work as expected", async () => { + const { + users: [user1, user2, user3, , user5], + gatewayAdmin, + bayc, + nBAYC, + ape, + } = await loadFixture(fixture); + + await expect( + p2pPairStaking.connect(user2.signer).setMatchingOperator(user5.address) + ).to.be.revertedWith("Ownable: caller is not the owner"); + + await waitForTx( + await p2pPairStaking + .connect(gatewayAdmin.signer) + .setMatchingOperator(user5.address) + ); + + await supplyAndValidate(bayc, "1", user3, true); + await mintAndValidate(ape, "1000000", user2); + + await waitForTx( + await bayc + .connect(user3.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 0, + bayc.address, + 0, + 2000, + user3 + ); + user1SignedOrder.v = 0; + const user2SignedOrder = await getSignedListingOrder( + p2pPairStaking, + 0, + cApe.address, + 0, + 8000, + user2 + ); + user2SignedOrder.v = 0; + + await expect( + p2pPairStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ).to.be.reverted; + + const txReceipt = await waitForTx( + await p2pPairStaking + .connect(user5.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + await expect( + p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + ).to.be.revertedWith("no permission to break up"); + + await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking.connect(user1.signer).cancelListing(user1SignedOrder) + await p2pPairStaking.connect(user5.signer).breakUpMatchedOrder(orderHash) + ); + + expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); + almostEqual(await cApe.balanceOf(user2.address), apeAmount); + almostEqual( + await p2pPairStaking.pendingCApeReward(user3.address), + parseEther("720") + ); + almostEqual( + await p2pPairStaking.pendingCApeReward(user2.address), + parseEther("2880") ); }); it("compound fee work as expected", async () => { const { - users: [user1, user2], + users: [user1, user2, user3], + gatewayAdmin, bayc, ape, - poolAdmin, } = await loadFixture(fixture); await waitForTx( - await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(50) + await p2pPairStaking.connect(gatewayAdmin.signer).setCompoundFee(50) ); - await supplyAndValidate(bayc, "1", user1, true); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await supplyAndValidate(bayc, "1", user3, true); + await mintAndValidate(ape, "1000000", user2); + + //deposit cApe for user3 to let exchangeRate > 1 + await waitForTx( + await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); await waitForTx( - await paraApeStaking + await cApe .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + .deposit(user2.address, parseEther("1000")) + ); + + await waitForTx( + await bayc + .connect(user3.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); + await waitForTx( + await cApe.connect(user2.signer).deposit(user2.address, apeAmount) ); const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, bayc.address, 0, 2000, - user1 + user3 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, - ONE_ADDRESS, + cApe.address, 0, 8000, user2 ); const txReceipt = await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ); @@ -983,24 +1098,31 @@ describe("P2P Pair Staking Test", () => { await advanceTimeAndBlock(parseInt("3600")); await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .claimForMatchedOrderAndCompound([orderHash]) ); almostEqual( - await paraApeStaking.pendingCApeReward(user1.address), + await p2pPairStaking.pendingCApeReward(p2pPairStaking.address), + parseEther("18") + ); + almostEqual( + await p2pPairStaking.pendingCApeReward(user3.address), parseEther("716.4") ); almostEqual( - await paraApeStaking.pendingCApeReward(user2.address), + await p2pPairStaking.pendingCApeReward(user2.address), parseEther("2865.6") ); - almostEqual( - await paraApeStaking.pendingCApeReward(paraApeStaking.address), - parseEther("18") + await waitForTx( + await p2pPairStaking + .connect(gatewayAdmin.signer) + .claimCompoundFee(gatewayAdmin.address) ); + + almostEqual(await cApe.balanceOf(gatewayAdmin.address), parseEther("18")); }); it("check ape token can be matched twice", async () => { @@ -1013,16 +1135,27 @@ describe("P2P Pair Staking Test", () => { await supplyAndValidate(bayc, "1", user1, true); await supplyAndValidate(bakc, "1", user3, true); + await mintAndValidate(ape, "1000000", user2); await waitForTx( - await paraApeStaking + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await bakc + .connect(user3.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await cApe .connect(user2.signer) - .depositFreeSApe(ape.address, parseEther("1000000")) + .deposit(user2.address, parseEther("500000")) ); //match bayc + ApeCoin let user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, bayc.address, 0, @@ -1030,16 +1163,16 @@ describe("P2P Pair Staking Test", () => { user1 ); let user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, - ONE_ADDRESS, + cApe.address, 0, 8000, user2 ); let txReceipt = await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder, user2SignedOrder) ); @@ -1048,7 +1181,7 @@ describe("P2P Pair Staking Test", () => { //match bayc + bakc + ApeCoin user1SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bayc.address, 0, @@ -1056,7 +1189,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user3SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, bakc.address, 0, @@ -1064,16 +1197,16 @@ describe("P2P Pair Staking Test", () => { user3 ); user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 2, - ONE_ADDRESS, + cApe.address, 0, 6000, user2 ); txReceipt = await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchBAKCPairStakingList( user1SignedOrder, @@ -1085,11 +1218,11 @@ describe("P2P Pair Staking Test", () => { const orderHash1 = txReceipt.logs[logLength - 1].data; await waitForTx( - await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) + await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) ); await waitForTx( - await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash1) + await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash1) ); }); @@ -1101,15 +1234,21 @@ describe("P2P Pair Staking Test", () => { } = await loadFixture(fixture); await supplyAndValidate(bayc, "2", user1, true); - const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await mintAndValidate(ape, "1000000", user2); + await waitForTx( - await paraApeStaking + await bayc + .connect(user1.signer) + .setApprovalForAll(p2pPairStaking.address, true) + ); + await waitForTx( + await cApe .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) + .deposit(user2.address, parseEther("500000")) ); const user1SignedOrder0 = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, bayc.address, 0, @@ -1117,7 +1256,7 @@ describe("P2P Pair Staking Test", () => { user1 ); const user1SignedOrder1 = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, bayc.address, 1, @@ -1125,16 +1264,16 @@ describe("P2P Pair Staking Test", () => { user1 ); const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, + p2pPairStaking, 0, - ONE_ADDRESS, + cApe.address, 0, 8000, user2 ); const txReceipt = await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder0, user2SignedOrder) ); @@ -1142,266 +1281,19 @@ describe("P2P Pair Staking Test", () => { const orderHash0 = txReceipt.logs[logLength - 1].data; await expect( - paraApeStaking + p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder1, user2SignedOrder) - ).to.be.revertedWith(ProtocolErrors.INVALID_ORDER_STATUS); + ).to.be.revertedWith("ape coin order already matched"); await waitForTx( - await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) + await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) ); await waitForTx( - await paraApeStaking + await p2pPairStaking .connect(user1.signer) .matchPairStakingList(user1SignedOrder1, user2SignedOrder) ); }); - - it("pause work as expected", async () => { - const { - users: [user1, user2], - ape, - mayc, - poolAdmin, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const apeAmount = await paraApeStaking.getApeCoinStakingCap(1); - await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, - 1, - mayc.address, - 0, - 2000, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, - 1, - ONE_ADDRESS, - 0, - 8000, - user2 - ); - - await expect( - paraApeStaking.connect(user1.signer).pause() - ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN); - - await waitForTx(await paraApeStaking.connect(poolAdmin.signer).pause()); - - await expect( - paraApeStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith("paused"); - - await waitForTx(await paraApeStaking.connect(poolAdmin.signer).unpause()); - - const txReceipt = await waitForTx( - await paraApeStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ); - const logLength = txReceipt.logs.length; - const orderHash = txReceipt.logs[logLength - 1].data; - - await advanceTimeAndBlock(parseInt("3600")); - - await waitForTx(await paraApeStaking.connect(poolAdmin.signer).pause()); - - await expect( - paraApeStaking - .connect(user1.signer) - .claimForMatchedOrderAndCompound([orderHash]) - ).to.be.revertedWith("paused"); - - await waitForTx(await paraApeStaking.connect(poolAdmin.signer).unpause()); - - await waitForTx( - await paraApeStaking - .connect(user1.signer) - .claimForMatchedOrderAndCompound([orderHash]) - ); - - await waitForTx(await paraApeStaking.connect(poolAdmin.signer).pause()); - - await expect( - paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) - ).to.be.revertedWith("paused"); - - await waitForTx(await paraApeStaking.connect(poolAdmin.signer).unpause()); - - await waitForTx( - await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) - ); - }); - - it("ApeCoin order staked sApe can be liquidate", async () => { - const { - users: [user1, user2, liquidator], - ape, - weth, - bayc, - pool, - } = await loadFixture(fixture); - const sApeAddress = ONE_ADDRESS; - - await supplyAndValidate(bayc, "1", user1, true); - - const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); - await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, - 0, - bayc.address, - 0, - 2000, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, - 0, - ONE_ADDRESS, - 0, - 8000, - user2 - ); - - const txReceipt = await waitForTx( - await paraApeStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ); - const logLength = txReceipt.logs.length; - const orderHash = txReceipt.logs[logLength - 1].data; - - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - "0", - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - apeAmount - ); - - await waitForTx( - await pool - .connect(user2.signer) - .setUserUseERC20AsCollateral(sApeAddress, true) - ); - - await changePriceAndValidate(ape, "0.001"); - await changeSApePriceAndValidate(sApeAddress, "0.001"); - await supplyAndValidate(weth, "100", liquidator, true); - - //collateral value: 200000 * 0.001 = 200 eth - //borrow value: 30 eth - await waitForTx( - await pool - .connect(user2.signer) - .borrow(weth.address, parseEther("30"), 0, user2.address) - ); - - await expect( - paraApeStaking.connect(liquidator.signer).breakUpMatchedOrder(orderHash) - ).to.be.revertedWith(ProtocolErrors.NO_BREAK_UP_PERMISSION); - - await changePriceAndValidate(ape, "0.0001"); - await changeSApePriceAndValidate(sApeAddress, "0.0001"); - - await waitForTx( - await paraApeStaking - .connect(liquidator.signer) - .breakUpMatchedOrder(orderHash) - ); - - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - apeAmount, - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - "0" - ); - await waitForTx( - await pool - .connect(liquidator.signer) - .liquidateERC20( - sApeAddress, - weth.address, - user2.address, - MAX_UINT_AMOUNT, - true, - { - value: parseEther("100"), - gasLimit: 5000000, - } - ) - ); - - expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( - "0", - parseEther("1") - ); - expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( - "0" - ); - expect( - await paraApeStaking.freeSApeBalance(liquidator.address) - ).to.be.closeTo(apeAmount, parseEther("1")); - }); - - it("test invalid order", async () => { - const { - users: [user1, user2], - ape, - bayc, - mayc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await supplyAndValidate(mayc, "1", user1, true); - - const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); - await waitForTx( - await paraApeStaking - .connect(user2.signer) - .depositFreeSApe(ape.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - paraApeStaking, - 0, - mayc.address, - 0, - 2000, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - paraApeStaking, - 0, - ONE_ADDRESS, - 0, - 8000, - user2 - ); - - await expect( - paraApeStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith(ProtocolErrors.INVALID_STAKING_TYPE); - }); }); diff --git a/test/para_p2p_ape_staking.spec.ts b/test/para_p2p_ape_staking.spec.ts new file mode 100644 index 000000000..48e6933c8 --- /dev/null +++ b/test/para_p2p_ape_staking.spec.ts @@ -0,0 +1,1407 @@ +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect} from "chai"; +import {AutoCompoundApe, ParaApeStaking} from "../types"; +import {TestEnv} from "./helpers/make-suite"; +import {testEnvFixture} from "./helpers/setup-env"; +import { + changePriceAndValidate, + changeSApePriceAndValidate, + mintAndValidate, + supplyAndValidate, +} from "./helpers/validated-steps"; +import { + getAutoCompoundApe, + getParaApeStaking, +} from "../helpers/contracts-getters"; +import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; +import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; +import {parseEther} from "ethers/lib/utils"; +import {almostEqual} from "./helpers/uniswapv3-helper"; +import {ProtocolErrors} from "../helpers/types"; + +describe("ParaApeStaking P2P Pair Staking Test", () => { + let testEnv: TestEnv; + let paraApeStaking: ParaApeStaking; + let cApe: AutoCompoundApe; + let MINIMUM_LIQUIDITY; + + const fixture = async () => { + testEnv = await loadFixture(testEnvFixture); + const { + ape, + users: [user1, user2, , user4, , user6], + apeCoinStaking, + poolAdmin, + } = testEnv; + + paraApeStaking = await getParaApeStaking(); + + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .setApeStakingBot(user4.address) + ); + + cApe = await getAutoCompoundApe(); + MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + + // send extra tokens to the apestaking contract for rewards + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"]( + apeCoinStaking.address, + parseEther("100000000000") + ) + ); + + // user4 deposit MINIMUM_LIQUIDITY to make test case easy + await mintAndValidate(ape, "1", user6); + await waitForTx( + await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) + ); + + //user2 deposit free sApe + await waitForTx( + await ape + .connect(user2.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + await mintAndValidate(ape, "1000000", user2); + + return testEnv; + }; + + it("test BAYC pair with ApeCoin Staking", async () => { + const { + users: [user1, user2], + ape, + bayc, + nBAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([orderHash]) + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("2880") + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) + ); + + almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); + almostEqual(await cApe.balanceOf(user2.address), parseEther("2880")); + await waitForTx( + await cApe + .connect(user2.signer) + .transfer(user1.address, await cApe.balanceOf(user2.address)) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + ); + + expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("2880") + ); + }); + + it("test MAYC pair with ApeCoin Staking", async () => { + const { + users: [user1, user2], + ape, + mayc, + nMAYC, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const apeAmount = await paraApeStaking.getApeCoinStakingCap(1); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 1, + mayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 1, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([orderHash]) + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("2880") + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) + ); + + almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); + almostEqual(await cApe.balanceOf(user2.address), parseEther("2880")); + await waitForTx( + await cApe + .connect(user2.signer) + .transfer(user1.address, await cApe.balanceOf(user2.address)) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + ); + + expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("2880") + ); + }); + + it("test BAYC pair with BAKC and ApeCoin Staking", async () => { + const { + users: [user1, user2, user3], + ape, + bayc, + bakc, + nBAYC, + nBAKC, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user3, true); + + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bayc.address, + 0, + 2000, + user1 + ); + const user3SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bakc.address, + 0, + 2000, + user3 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + ONE_ADDRESS, + 0, + 6000, + user2 + ); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchBAKCPairStakingList( + user1SignedOrder, + user3SignedOrder, + user2SignedOrder + ) + ); + + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([orderHash]) + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user3.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("2160") + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) + ); + await waitForTx( + await paraApeStaking.connect(user3.signer).claimCApeReward(user3.address) + ); + + almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); + almostEqual(await cApe.balanceOf(user3.address), parseEther("720")); + almostEqual(await cApe.balanceOf(user2.address), parseEther("2160")); + await waitForTx( + await cApe + .connect(user2.signer) + .transfer(user1.address, await cApe.balanceOf(user2.address)) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + ); + + expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); + expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(1); + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user3.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("2160") + ); + }); + + it("test MAYC pair with BAKC and ApeCoin Staking", async () => { + const { + users: [user1, user2, user3], + ape, + mayc, + bakc, + nMAYC, + nBAKC, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user3, true); + + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + mayc.address, + 0, + 2000, + user1 + ); + const user3SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bakc.address, + 0, + 2000, + user3 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + ONE_ADDRESS, + 0, + 6000, + user2 + ); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchBAKCPairStakingList( + user1SignedOrder, + user3SignedOrder, + user2SignedOrder + ) + ); + + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([orderHash]) + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user3.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("2160") + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + ); + await waitForTx( + await paraApeStaking.connect(user2.signer).claimCApeReward(user2.address) + ); + await waitForTx( + await paraApeStaking.connect(user3.signer).claimCApeReward(user3.address) + ); + + almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); + almostEqual(await cApe.balanceOf(user3.address), parseEther("720")); + almostEqual(await cApe.balanceOf(user2.address), parseEther("2160")); + await waitForTx( + await cApe + .connect(user2.signer) + .transfer(user1.address, await cApe.balanceOf(user2.address)) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) + ); + + expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); + expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(1); + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user3.address), + parseEther("720") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("2160") + ); + }); + + it("claimForMatchedOrderAndCompound for multi user work as expected", async () => { + const { + users: [user1, user2, user3], + ape, + bayc, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "10", user1, true); + await supplyAndValidate(bakc, "10", user3, true); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, parseEther("1000000")) + ); + + const txArray: string[] = []; + for (let i = 0; i < 10; i++) { + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bayc.address, + i, + 2000, + user1 + ); + const user3SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bakc.address, + i, + 2000, + user3 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + ONE_ADDRESS, + 0, + 6000, + user2 + ); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchBAKCPairStakingList( + user1SignedOrder, + user3SignedOrder, + user2SignedOrder + ) + ); + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + txArray.push(orderHash); + } + + for (let i = 0; i < 2; i++) { + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound(txArray) + ); + } + }); + + it("match failed when order was canceled 0", async () => { + const { + users: [user1, user2], + ape, + bayc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + await waitForTx( + await paraApeStaking.connect(user2.signer).cancelListing(user2SignedOrder) + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ).to.be.revertedWith(ProtocolErrors.INVALID_ORDER_STATUS); + }); + + it("match failed when order was canceled 1", async () => { + const { + users: [user1, user2, user3], + ape, + bayc, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user2, true); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bakc.address, + 0, + 2000, + user2 + ); + const user3SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + ONE_ADDRESS, + 0, + 6000, + user3 + ); + + await waitForTx( + await paraApeStaking.connect(user3.signer).cancelListing(user3SignedOrder) + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .matchBAKCPairStakingList( + user1SignedOrder, + user2SignedOrder, + user3SignedOrder + ) + ).to.be.revertedWith(ProtocolErrors.INVALID_ORDER_STATUS); + }); + + it("match failed when orders type match failed 0", async () => { + const { + users: [user1, user2], + ape, + bayc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 1, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ).to.be.revertedWith(ProtocolErrors.ORDER_TYPE_MATCH_FAILED); + }); + + it("match failed when orders type match failed 1", async () => { + const { + users: [user1, user2, user3], + ape, + bayc, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user3, true); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bayc.address, + 0, + 2000, + user1 + ); + const user3SignedOrder = await getSignedListingOrder( + paraApeStaking, + 1, + bakc.address, + 0, + 2000, + user3 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + ONE_ADDRESS, + 0, + 6000, + user2 + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .matchBAKCPairStakingList( + user1SignedOrder, + user3SignedOrder, + user2SignedOrder + ) + ).to.be.revertedWith(ProtocolErrors.ORDER_TYPE_MATCH_FAILED); + }); + + it("match failed when share match failed 0", async () => { + const { + users: [user1, user2], + ape, + bayc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 7000, + user2 + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ).to.be.revertedWith(ProtocolErrors.ORDER_SHARE_MATCH_FAILED); + }); + + it("match failed when share match failed 1", async () => { + const { + users: [user1, user2, user3], + ape, + bayc, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user3, true); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(2); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bayc.address, + 0, + 2000, + user1 + ); + const user3SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bakc.address, + 0, + 2000, + user3 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + ONE_ADDRESS, + 0, + 7000, + user2 + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .matchBAKCPairStakingList( + user1SignedOrder, + user3SignedOrder, + user2SignedOrder + ) + ).to.be.revertedWith(ProtocolErrors.ORDER_SHARE_MATCH_FAILED); + }); + + it("listing order can only be canceled by offerer", async () => { + const { + users: [user1, user2], + bayc, + } = await loadFixture(fixture); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + + await expect( + paraApeStaking.connect(user2.signer).cancelListing(user1SignedOrder) + ).to.be.revertedWith(ProtocolErrors.NOT_ORDER_OFFERER); + + await waitForTx( + await paraApeStaking.connect(user1.signer).cancelListing(user1SignedOrder) + ); + }); + + it("compound fee work as expected", async () => { + const { + users: [user1, user2], + bayc, + ape, + poolAdmin, + } = await loadFixture(fixture); + + await waitForTx( + await paraApeStaking.connect(poolAdmin.signer).setCompoundFee(50) + ); + + await supplyAndValidate(bayc, "1", user1, true); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([orderHash]) + ); + + almostEqual( + await paraApeStaking.pendingCApeReward(user1.address), + parseEther("716.4") + ); + almostEqual( + await paraApeStaking.pendingCApeReward(user2.address), + parseEther("2865.6") + ); + + almostEqual( + await paraApeStaking.pendingCApeReward(paraApeStaking.address), + parseEther("18") + ); + }); + + it("check ape token can be matched twice", async () => { + const { + users: [user1, user2, user3], + bayc, + ape, + bakc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user3, true); + + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, parseEther("1000000")) + ); + + //match bayc + ApeCoin + let user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + let user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + let txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + let logLength = txReceipt.logs.length; + const orderHash0 = txReceipt.logs[logLength - 1].data; + + //match bayc + bakc + ApeCoin + user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bayc.address, + 0, + 2000, + user1 + ); + const user3SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + bakc.address, + 0, + 2000, + user3 + ); + user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 2, + ONE_ADDRESS, + 0, + 6000, + user2 + ); + + txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchBAKCPairStakingList( + user1SignedOrder, + user3SignedOrder, + user2SignedOrder + ) + ); + logLength = txReceipt.logs.length; + const orderHash1 = txReceipt.logs[logLength - 1].data; + + await waitForTx( + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) + ); + + await waitForTx( + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash1) + ); + }); + + it("check ape coin listing order can not be matched twice", async () => { + const { + users: [user1, user2], + bayc, + ape, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "2", user1, true); + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder0 = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + const user1SignedOrder1 = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 1, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder0, user2SignedOrder) + ); + const logLength = txReceipt.logs.length; + const orderHash0 = txReceipt.logs[logLength - 1].data; + + await expect( + paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder1, user2SignedOrder) + ).to.be.revertedWith(ProtocolErrors.INVALID_ORDER_STATUS); + + await waitForTx( + await paraApeStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder1, user2SignedOrder) + ); + }); + + it("pause work as expected", async () => { + const { + users: [user1, user2], + ape, + mayc, + poolAdmin, + } = await loadFixture(fixture); + + await supplyAndValidate(mayc, "1", user1, true); + + const apeAmount = await paraApeStaking.getApeCoinStakingCap(1); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 1, + mayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 1, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + await expect( + paraApeStaking.connect(user1.signer).pause() + ).to.be.revertedWith(ProtocolErrors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN); + + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).pause()); + + await expect( + paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ).to.be.revertedWith("paused"); + + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).unpause()); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).pause()); + + await expect( + paraApeStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([orderHash]) + ).to.be.revertedWith("paused"); + + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).unpause()); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .claimForMatchedOrderAndCompound([orderHash]) + ); + + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).pause()); + + await expect( + paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + ).to.be.revertedWith("paused"); + + await waitForTx(await paraApeStaking.connect(poolAdmin.signer).unpause()); + + await waitForTx( + await paraApeStaking.connect(user1.signer).claimCApeReward(user1.address) + ); + }); + + it("ApeCoin order staked sApe can be liquidate", async () => { + const { + users: [user1, user2, liquidator], + ape, + weth, + bayc, + pool, + } = await loadFixture(fixture); + const sApeAddress = ONE_ADDRESS; + + await supplyAndValidate(bayc, "1", user1, true); + + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + bayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + const txReceipt = await waitForTx( + await paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ); + const logLength = txReceipt.logs.length; + const orderHash = txReceipt.logs[logLength - 1].data; + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + apeAmount + ); + + await waitForTx( + await pool + .connect(user2.signer) + .setUserUseERC20AsCollateral(sApeAddress, true) + ); + + await changePriceAndValidate(ape, "0.001"); + await changeSApePriceAndValidate(sApeAddress, "0.001"); + await supplyAndValidate(weth, "100", liquidator, true); + + //collateral value: 200000 * 0.001 = 200 eth + //borrow value: 30 eth + await waitForTx( + await pool + .connect(user2.signer) + .borrow(weth.address, parseEther("30"), 0, user2.address) + ); + + await expect( + paraApeStaking.connect(liquidator.signer).breakUpMatchedOrder(orderHash) + ).to.be.revertedWith(ProtocolErrors.NO_BREAK_UP_PERMISSION); + + await changePriceAndValidate(ape, "0.0001"); + await changeSApePriceAndValidate(sApeAddress, "0.0001"); + + await waitForTx( + await paraApeStaking + .connect(liquidator.signer) + .breakUpMatchedOrder(orderHash) + ); + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + apeAmount, + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + await waitForTx( + await pool + .connect(liquidator.signer) + .liquidateERC20( + sApeAddress, + weth.address, + user2.address, + MAX_UINT_AMOUNT, + true, + { + value: parseEther("100"), + gasLimit: 5000000, + } + ) + ); + + expect(await paraApeStaking.freeSApeBalance(user2.address)).to.be.closeTo( + "0", + parseEther("1") + ); + expect(await paraApeStaking.stakedSApeBalance(user2.address)).to.be.equal( + "0" + ); + expect( + await paraApeStaking.freeSApeBalance(liquidator.address) + ).to.be.closeTo(apeAmount, parseEther("1")); + }); + + it("test invalid order", async () => { + const { + users: [user1, user2], + ape, + bayc, + mayc, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(mayc, "1", user1, true); + + const apeAmount = await paraApeStaking.getApeCoinStakingCap(0); + await waitForTx( + await paraApeStaking + .connect(user2.signer) + .depositFreeSApe(ape.address, apeAmount) + ); + + const user1SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + mayc.address, + 0, + 2000, + user1 + ); + const user2SignedOrder = await getSignedListingOrder( + paraApeStaking, + 0, + ONE_ADDRESS, + 0, + 8000, + user2 + ); + + await expect( + paraApeStaking + .connect(user1.signer) + .matchPairStakingList(user1SignedOrder, user2SignedOrder) + ).to.be.revertedWith(ProtocolErrors.INVALID_STAKING_TYPE); + }); +}); From 02e0ee28c4582068fc128d647daccd23d33e21e4 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Wed, 13 Sep 2023 18:28:45 +0800 Subject: [PATCH 96/99] chore: small optimization --- contracts/interfaces/IPoolApeStaking.sol | 2 +- contracts/protocol/pool/PoolApeStaking.sol | 131 ++++++++------------- test/_ape_staking_migration.spec.ts | 40 +++---- 3 files changed, 69 insertions(+), 104 deletions(-) diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol index 7fc78b07f..3c26ec3b2 100644 --- a/contracts/interfaces/IPoolApeStaking.sol +++ b/contracts/interfaces/IPoolApeStaking.sol @@ -65,7 +65,7 @@ interface IPoolApeStaking { struct ApeCoinInfo { address asset; - uint256 cashAmount; + uint256 totalAmount; uint256 borrowAmount; bool openSApeCollateralFlag; } diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index a4466ddae..8fe1d8d14 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -272,7 +272,7 @@ contract PoolApeStaking is if (nBakc == address(0)) { nBakc = ps._reserves[BAKC].xTokenAddress; } - //transfer bakc from nBakc to ape + //transfer bakc from nBakc to nApe for (uint256 j = 0; j < pairLength; j++) { IERC721(BAKC).safeTransferFrom( nBakc, @@ -304,11 +304,13 @@ contract PoolApeStaking is Errors.INVALID_ASSET_TYPE ); // 1, prepare cash part. - if (apeCoinInfo.cashAmount > 0) { + uint256 cashAmount = apeCoinInfo.totalAmount - + apeCoinInfo.borrowAmount; + if (cashAmount > 0) { IERC20(apeCoinInfo.asset).transferFrom( onBehalf, address(this), - apeCoinInfo.cashAmount + cashAmount ); } @@ -326,132 +328,99 @@ contract PoolApeStaking is ); } - uint256 totalAmount = apeCoinInfo.cashAmount + - apeCoinInfo.borrowAmount; - if (apeCoinInfo.asset == address(APE_COMPOUND) && totalAmount > 0) { - APE_COMPOUND.withdraw(totalAmount); + if ( + apeCoinInfo.asset == address(APE_COMPOUND) && + apeCoinInfo.totalAmount > 0 + ) { + APE_COMPOUND.withdraw(apeCoinInfo.totalAmount); } } //staking in paraApeStaking { uint256 stakingLength = stakingInfos.length; + uint256 bakcPairCap; for (uint256 index = 0; index < stakingLength; index++) { ParaStakingInfo calldata stakingInfo = stakingInfos[index]; if ( stakingInfo.PoolId == - ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID - ) { - IParaApeStaking(PARA_APE_STAKING).depositPairNFT( - onBehalf, - true, - stakingInfo.apeTokenIds, - stakingInfo.bakcTokenIds - ); - } else if ( + ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID || stakingInfo.PoolId == ApeStakingCommonLogic.MAYC_BAKC_PAIR_POOL_ID ) { + bool isBayc = (stakingInfo.PoolId == + ApeStakingCommonLogic.BAYC_BAKC_PAIR_POOL_ID); IParaApeStaking(PARA_APE_STAKING).depositPairNFT( onBehalf, - false, + isBayc, stakingInfo.apeTokenIds, stakingInfo.bakcTokenIds ); } else if ( stakingInfo.PoolId == - ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID - ) { - IParaApeStaking(PARA_APE_STAKING).depositNFT( - onBehalf, - BAYC, - stakingInfo.apeTokenIds - ); - } else if ( + ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID || stakingInfo.PoolId == - ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID - ) { - IParaApeStaking(PARA_APE_STAKING).depositNFT( - onBehalf, - MAYC, - stakingInfo.apeTokenIds - ); - } else if ( + ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID || stakingInfo.PoolId == ApeStakingCommonLogic.BAKC_SINGLE_POOL_ID ) { + address nft = (stakingInfo.PoolId == + ApeStakingCommonLogic.BAYC_SINGLE_POOL_ID) + ? BAYC + : (stakingInfo.PoolId == + ApeStakingCommonLogic.MAYC_SINGLE_POOL_ID) + ? MAYC + : BAKC; IParaApeStaking(PARA_APE_STAKING).depositNFT( onBehalf, - BAKC, - stakingInfo.bakcTokenIds + nft, + stakingInfo.apeTokenIds ); } else if ( stakingInfo.PoolId == - ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID - ) { - uint256 cap = IParaApeStaking(PARA_APE_STAKING) - .getApeCoinStakingCap( - IApeStakingP2P.StakingType.BAYCStaking - ); - IParaApeStaking(PARA_APE_STAKING).depositApeCoinPool( - IApeCoinPool.ApeCoinDepositInfo({ - onBehalf: onBehalf, - cashToken: address(APE_COIN), - cashAmount: cap * stakingInfo.apeTokenIds.length, - isBAYC: true, - tokenIds: stakingInfo.apeTokenIds - }) - ); - } else if ( + ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID || stakingInfo.PoolId == ApeStakingCommonLogic.MAYC_APECOIN_POOL_ID ) { + bool isBayc = (stakingInfo.PoolId == + ApeStakingCommonLogic.BAYC_APECOIN_POOL_ID); + IApeStakingP2P.StakingType stakingType = isBayc + ? IApeStakingP2P.StakingType.BAYCStaking + : IApeStakingP2P.StakingType.MAYCStaking; uint256 cap = IParaApeStaking(PARA_APE_STAKING) - .getApeCoinStakingCap( - IApeStakingP2P.StakingType.MAYCStaking - ); + .getApeCoinStakingCap(stakingType); IParaApeStaking(PARA_APE_STAKING).depositApeCoinPool( IApeCoinPool.ApeCoinDepositInfo({ onBehalf: onBehalf, cashToken: address(APE_COIN), cashAmount: cap * stakingInfo.apeTokenIds.length, - isBAYC: false, + isBAYC: isBayc, tokenIds: stakingInfo.apeTokenIds }) ); } else if ( stakingInfo.PoolId == - ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID - ) { - uint256 cap = IParaApeStaking(PARA_APE_STAKING) - .getApeCoinStakingCap( - IApeStakingP2P.StakingType.BAKCPairStaking - ); - IParaApeStaking(PARA_APE_STAKING).depositApeCoinPairPool( - IApeCoinPool.ApeCoinPairDepositInfo({ - onBehalf: onBehalf, - cashToken: address(APE_COIN), - cashAmount: cap * stakingInfo.apeTokenIds.length, - isBAYC: true, - apeTokenIds: stakingInfo.apeTokenIds, - bakcTokenIds: stakingInfo.bakcTokenIds - }) - ); - } else if ( + ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID || stakingInfo.PoolId == ApeStakingCommonLogic.MAYC_BAKC_APECOIN_POOL_ID ) { - uint256 cap = IParaApeStaking(PARA_APE_STAKING) - .getApeCoinStakingCap( - IApeStakingP2P.StakingType.BAKCPairStaking - ); + if (bakcPairCap == 0) { + bakcPairCap = IParaApeStaking(PARA_APE_STAKING) + .getApeCoinStakingCap( + IApeStakingP2P.StakingType.BAKCPairStaking + ); + } + + bool isBayc = (stakingInfo.PoolId == + ApeStakingCommonLogic.BAYC_BAKC_APECOIN_POOL_ID); IParaApeStaking(PARA_APE_STAKING).depositApeCoinPairPool( IApeCoinPool.ApeCoinPairDepositInfo({ onBehalf: onBehalf, cashToken: address(APE_COIN), - cashAmount: cap * stakingInfo.apeTokenIds.length, - isBAYC: false, + cashAmount: bakcPairCap * + stakingInfo.apeTokenIds.length, + isBAYC: isBayc, apeTokenIds: stakingInfo.apeTokenIds, bakcTokenIds: stakingInfo.bakcTokenIds }) @@ -463,11 +432,7 @@ contract PoolApeStaking is // repay and supply remaining apecoin uint256 diffBalance = APE_COIN.balanceOf(address(this)) - beforeBalance; if (diffBalance > 0) { - //wrong cashAmount or borrowAmount - require( - apeCoinInfo.cashAmount + apeCoinInfo.borrowAmount == 0, - Errors.INVALID_PARAMETER - ); + require(apeCoinInfo.totalAmount == 0, Errors.INVALID_PARAMETER); APE_COMPOUND.deposit(address(this), diffBalance); _repayAndSupplyForUser( ps, diff --git a/test/_ape_staking_migration.spec.ts b/test/_ape_staking_migration.spec.ts index 5b217e6fa..7d8c50ed2 100644 --- a/test/_ape_staking_migration.spec.ts +++ b/test/_ape_staking_migration.spec.ts @@ -202,7 +202,7 @@ describe("Para Ape Staking Test", () => { ], { asset: ape.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } @@ -284,7 +284,7 @@ describe("Para Ape Staking Test", () => { ], { asset: ape.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } @@ -365,7 +365,7 @@ describe("Para Ape Staking Test", () => { ], { asset: cApe.address, - cashAmount: 0, + totalAmount: parseEther("85600"), borrowAmount: parseEther("85600"), openSApeCollateralFlag: true, } @@ -448,7 +448,7 @@ describe("Para Ape Staking Test", () => { ], { asset: cApe.address, - cashAmount: 0, + totalAmount: parseEther("85600"), borrowAmount: parseEther("85600"), openSApeCollateralFlag: true, } @@ -523,13 +523,13 @@ describe("Para Ape Staking Test", () => { }, { PoolId: 5, - apeTokenIds: [], - bakcTokenIds: [0], + apeTokenIds: [0], + bakcTokenIds: [], }, ], { asset: ape.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } @@ -603,13 +603,13 @@ describe("Para Ape Staking Test", () => { }, { PoolId: 5, - apeTokenIds: [], - bakcTokenIds: [0], + apeTokenIds: [0], + bakcTokenIds: [], }, ], { asset: ape.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } @@ -682,13 +682,13 @@ describe("Para Ape Staking Test", () => { }, { PoolId: 5, - apeTokenIds: [], - bakcTokenIds: [0], + apeTokenIds: [0], + bakcTokenIds: [], }, ], { asset: cApe.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } @@ -763,13 +763,13 @@ describe("Para Ape Staking Test", () => { }, { PoolId: 5, - apeTokenIds: [], - bakcTokenIds: [0], + apeTokenIds: [0], + bakcTokenIds: [], }, ], { asset: cApe.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } @@ -843,7 +843,7 @@ describe("Para Ape Staking Test", () => { ], { asset: ape.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } @@ -918,7 +918,7 @@ describe("Para Ape Staking Test", () => { ], { asset: ape.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } @@ -992,7 +992,7 @@ describe("Para Ape Staking Test", () => { ], { asset: cApe.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } @@ -1068,7 +1068,7 @@ describe("Para Ape Staking Test", () => { ], { asset: cApe.address, - cashAmount: 0, + totalAmount: 0, borrowAmount: 0, openSApeCollateralFlag: true, } From 9aaaab7c393dbaf0a57b9ca3e024306003f768ca Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 13 Oct 2023 15:45:49 +0800 Subject: [PATCH 97/99] chore: check ntoken owner when migration --- contracts/protocol/pool/PoolApeStaking.sol | 35 +++++++++++++++++----- test/_ape_staking_migration.spec.ts | 34 ++++++++++++++++++++- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol index 8fe1d8d14..9216cc617 100644 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ b/contracts/protocol/pool/PoolApeStaking.sol @@ -261,11 +261,21 @@ contract PoolApeStaking is DataTypes.ReserveData storage nftReserve = ps._reserves[ unstakingInfo.nftAsset ]; - INTokenApeStaking nToken = INTokenApeStaking( - nftReserve.xTokenAddress - ); - if (unstakingInfo._nfts.length > 0) { - nToken.withdrawApeCoin(unstakingInfo._nfts, address(this)); + address nToken = nftReserve.xTokenAddress; + uint256 singleLength = unstakingInfo._nfts.length; + if (singleLength > 0) { + for (uint256 j = 0; j < singleLength; j++) { + require( + IERC721(nToken).ownerOf( + unstakingInfo._nfts[j].tokenId + ) == onBehalf, + Errors.NOT_THE_OWNER + ); + } + INTokenApeStaking(nToken).withdrawApeCoin( + unstakingInfo._nfts, + address(this) + ); } uint256 pairLength = unstakingInfo._nftPairs.length; if (pairLength > 0) { @@ -274,20 +284,29 @@ contract PoolApeStaking is } //transfer bakc from nBakc to nApe for (uint256 j = 0; j < pairLength; j++) { + require( + IERC721(nBakc).ownerOf( + unstakingInfo._nftPairs[j].bakcTokenId + ) == onBehalf, + Errors.NOT_THE_BAKC_OWNER + ); IERC721(BAKC).safeTransferFrom( nBakc, - address(nToken), + nToken, unstakingInfo._nftPairs[j].bakcTokenId ); } //unstake - nToken.withdrawBAKC(unstakingInfo._nftPairs, address(this)); + INTokenApeStaking(nToken).withdrawBAKC( + unstakingInfo._nftPairs, + address(this) + ); //transfer bakc back to nBakc for (uint256 j = 0; j < pairLength; j++) { IERC721(BAKC).safeTransferFrom( - address(nToken), + nToken, nBakc, unstakingInfo._nftPairs[j].bakcTokenId ); diff --git a/test/_ape_staking_migration.spec.ts b/test/_ape_staking_migration.spec.ts index 7d8c50ed2..955f834a0 100644 --- a/test/_ape_staking_migration.spec.ts +++ b/test/_ape_staking_migration.spec.ts @@ -25,8 +25,9 @@ import { import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {parseEther} from "ethers/lib/utils"; +import {ProtocolErrors} from "../helpers/types"; -describe("Para Ape Staking Test", () => { +describe("Para Ape Staking Migration Test", () => { let testEnv: TestEnv; let variableDebtCApeCoin: VariableDebtToken; let paraApeStaking: ParaApeStaking; @@ -1082,6 +1083,37 @@ describe("Para Ape Staking Test", () => { expect(await pSApeCoin.balanceOf(user1.address)).to.be.eq(0); }); + it("should revert when msgsender is not ntoken owner", async () => { + const { + users: [user1, user2], + bayc, + bakc, + ape, + pool, + } = await loadFixture(fixture); + + await supplyAndValidate(bayc, "1", user1, true); + await supplyAndValidate(bakc, "1", user1, true); + await mintAndValidate(ape, "250000", user2); + await waitForTx( + await ape.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + + await expect( + pool.connect(user2.signer).borrowApeAndStakeV2( + { + nftAsset: bayc.address, + borrowAsset: ape.address, + borrowAmount: 0, + cashAsset: ape.address, + cashAmount: parseEther("250000"), + }, + [{tokenId: 0, amount: parseEther("200000")}], + [{mainTokenId: 0, bakcTokenId: 0, amount: parseEther("50000")}] + ) + ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); + }); + /* it("gas test: test 1 pair of BAYC with BAKC position migration", async () => { const { From 7272557d96e96861d8fa17f5540126ebc9652557 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 13 Oct 2023 18:43:54 +0800 Subject: [PATCH 98/99] chore: fix test case --- test/_ape_staking_migration.spec.ts | 2 +- test/_ape_staking_p2p_migration.spec.ts | 3 +-- test/para_pool_ape_staking.spec.ts | 20 +++++++++++++++----- test/xtoken_ntoken_bakc.spec.ts | 18 ++++++++++++------ 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/test/_ape_staking_migration.spec.ts b/test/_ape_staking_migration.spec.ts index 955f834a0..b9ff3a839 100644 --- a/test/_ape_staking_migration.spec.ts +++ b/test/_ape_staking_migration.spec.ts @@ -1096,7 +1096,7 @@ describe("Para Ape Staking Migration Test", () => { await supplyAndValidate(bakc, "1", user1, true); await mintAndValidate(ape, "250000", user2); await waitForTx( - await ape.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) + await ape.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) ); await expect( diff --git a/test/_ape_staking_p2p_migration.spec.ts b/test/_ape_staking_p2p_migration.spec.ts index eab7cc289..3704ce391 100644 --- a/test/_ape_staking_p2p_migration.spec.ts +++ b/test/_ape_staking_p2p_migration.spec.ts @@ -6,7 +6,6 @@ import {testEnvFixture} from "./helpers/setup-env"; import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; import { getAutoCompoundApe, - getFirstSigner, getInitializableAdminUpgradeabilityProxy, getP2PPairStaking, getParaApeStaking, @@ -18,7 +17,7 @@ import {parseEther} from "ethers/lib/utils"; import {almostEqual} from "./helpers/uniswapv3-helper"; import {deployParaApeStakingImpl} from "../helpers/contracts-deployments"; import {GLOBAL_OVERRIDES} from "../helpers/hardhat-constants"; -import {getEthersSigners, getProxyAdmin} from "../helpers/contracts-helpers"; +import {getEthersSigners} from "../helpers/contracts-helpers"; describe("P2P Pair Staking Migration Test", () => { let testEnv: TestEnv; diff --git a/test/para_pool_ape_staking.spec.ts b/test/para_pool_ape_staking.spec.ts index a2524facc..d88350350 100644 --- a/test/para_pool_ape_staking.spec.ts +++ b/test/para_pool_ape_staking.spec.ts @@ -1883,19 +1883,29 @@ describe("Para Ape staking ape coin pool test", () => { await supplyAndValidate(bakc, "3", user1, true); await waitForTx( - await paraApeStaking.connect(user1.signer).depositPairNFT(true, [0], [0]) + await paraApeStaking + .connect(user1.signer) + .depositPairNFT(user1.address, true, [0], [0]) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositPairNFT(false, [0], [1]) + await paraApeStaking + .connect(user1.signer) + .depositPairNFT(user1.address, false, [0], [1]) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositNFT(bayc.address, [1]) + await paraApeStaking + .connect(user1.signer) + .depositNFT(user1.address, bayc.address, [1]) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositNFT(mayc.address, [1]) + await paraApeStaking + .connect(user1.signer) + .depositNFT(user1.address, mayc.address, [1]) ); await waitForTx( - await paraApeStaking.connect(user1.signer).depositNFT(bakc.address, [2]) + await paraApeStaking + .connect(user1.signer) + .depositNFT(user1.address, bakc.address, [2]) ); await waitForTx( diff --git a/test/xtoken_ntoken_bakc.spec.ts b/test/xtoken_ntoken_bakc.spec.ts index c38c4cf51..5e7bf1009 100644 --- a/test/xtoken_ntoken_bakc.spec.ts +++ b/test/xtoken_ntoken_bakc.spec.ts @@ -87,11 +87,12 @@ describe("APE Coin Staking Test", () => { const halfAmount = await convertToCurrencyDecimals(ape.address, "5000"); await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [], @@ -173,11 +174,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "10000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [], @@ -213,11 +215,12 @@ describe("APE Coin Staking Test", () => { const amount = await convertToCurrencyDecimals(ape.address, "10000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [], @@ -259,11 +262,12 @@ describe("APE Coin Staking Test", () => { const amount = parseEther("10000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [], @@ -326,11 +330,12 @@ describe("APE Coin Staking Test", () => { const amount = parseEther("10000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: 0, + cashAsset: ape.address, cashAmount: amount, }, [], @@ -376,11 +381,12 @@ describe("APE Coin Staking Test", () => { const amount = parseEther("10000"); expect( - await pool.connect(user1.signer).borrowApeAndStake( + await pool.connect(user1.signer).borrowApeAndStakeV2( { nftAsset: mayc.address, borrowAsset: ape.address, borrowAmount: amount, + cashAsset: ape.address, cashAmount: 0, }, [], From 0f9b99b060914083ceb2098ef9a02f35fc110938 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 9 Nov 2023 19:11:07 +0800 Subject: [PATCH 99/99] chore: add gas test case --- test/para_ape_staking_gas_test.ts | 575 ++++++++++++++++++++++++++++++ 1 file changed, 575 insertions(+) create mode 100644 test/para_ape_staking_gas_test.ts diff --git a/test/para_ape_staking_gas_test.ts b/test/para_ape_staking_gas_test.ts new file mode 100644 index 000000000..166d4f723 --- /dev/null +++ b/test/para_ape_staking_gas_test.ts @@ -0,0 +1,575 @@ +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect} from "chai"; +import {AutoCompoundApe, ParaApeStaking} from "../types"; +import {TestEnv} from "./helpers/make-suite"; +import {testEnvFixture} from "./helpers/setup-env"; +import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; +import { + getAutoCompoundApe, + getParaApeStaking, +} from "../helpers/contracts-getters"; +import {MAX_UINT_AMOUNT} from "../helpers/constants"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; +import {parseEther} from "ethers/lib/utils"; + +describe("Para Ape Staking Test", () => { + let testEnv: TestEnv; + let paraApeStaking: ParaApeStaking; + let cApe: AutoCompoundApe; + let MINIMUM_LIQUIDITY; + + before(async () => { + testEnv = await loadFixture(testEnvFixture); + const { + ape, + users: [user1, , , user4, , user6], + apeCoinStaking, + pool, + configurator, + poolAdmin, + } = testEnv; + + paraApeStaking = await getParaApeStaking(); + + await waitForTx( + await paraApeStaking + .connect(poolAdmin.signer) + .setApeStakingBot(user4.address) + ); + + cApe = await getAutoCompoundApe(); + MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + + // send extra tokens to the apestaking contract for rewards + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"]( + apeCoinStaking.address, + parseEther("100000000000") + ) + ); + + // user6 deposit MINIMUM_LIQUIDITY to make test case easy + await mintAndValidate(ape, "1", user6); + await waitForTx( + await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) + ); + + // user4 deposit and supply cApe to MM + expect( + await configurator + .connect(poolAdmin.signer) + .setSupplyCap(cApe.address, "20000000000") + ); + await mintAndValidate(ape, "10000000000", user4); + await waitForTx( + await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user4.signer) + .deposit(user4.address, parseEther("10000000000")) + ); + await waitForTx( + await cApe.connect(user4.signer).approve(pool.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await pool + .connect(user4.signer) + .supply(cApe.address, parseEther("10000000000"), user4.address, 0) + ); + + await mintAndValidate(ape, "200000000", user1); + await waitForTx( + await ape + .connect(user1.signer) + .approve(paraApeStaking.address, MAX_UINT_AMOUNT) + ); + }); + + it(" supply 100 + 4 bayc", async () => { + const { + bayc, + users: [user1], + } = testEnv; + + await supplyAndValidate(bayc, "104", user1, true); + }); + + it(" supply 100 + 4 mayc", async () => { + const { + mayc, + users: [user1], + } = testEnv; + + await supplyAndValidate(mayc, "104", user1, true); + }); + + it(" supply 125 + 8 bakc", async () => { + const { + bakc, + users: [user1], + } = testEnv; + + await supplyAndValidate(bakc, "133", user1, true); + }); + + it(" deposit", async () => { + const { + users: [user1], + bayc, + mayc, + bakc, + ape, + } = testEnv; + + const tx0 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ + user1.address, + true, + Array.from(Array(25).keys()), //bayc 0-25 + Array.from(Array(25).keys()), //bakc 0-25 + ]); + const tx1 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ + user1.address, + false, + Array.from(Array(25).keys()), //mayc 0-25 + Array.from(Array(50).keys()).slice(25), //bakc 25-50 + ]); + const tx2 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + user1.address, + bayc.address, + Array.from(Array(50).keys()).slice(25), //bayc 25-50 + ]); + const tx3 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + user1.address, + mayc.address, + Array.from(Array(50).keys()).slice(25), //mayc 25-50 + ]); + const tx4 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + user1.address, + bakc.address, + Array.from(Array(75).keys()).slice(50), //bakc 50-75 + ]); + + const tx5 = paraApeStaking.interface.encodeFunctionData( + "depositApeCoinPool", + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("5000000"), + isBAYC: true, + tokenIds: Array.from(Array(75).keys()).slice(50), //bayc 50-75 + }, + ] + ); + + const tx6 = paraApeStaking.interface.encodeFunctionData( + "depositApeCoinPool", + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("2500000"), + isBAYC: false, + tokenIds: Array.from(Array(75).keys()).slice(50), //mayc 50-75 + }, + ] + ); + + const tx7 = paraApeStaking.interface.encodeFunctionData( + "depositApeCoinPairPool", + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("1250000"), + isBAYC: true, + apeTokenIds: Array.from(Array(100).keys()).slice(75), //bayc 75-100 + bakcTokenIds: Array.from(Array(100).keys()).slice(75), //bakc 75-100 + }, + ] + ); + + const tx8 = paraApeStaking.interface.encodeFunctionData( + "depositApeCoinPairPool", + [ + { + onBehalf: user1.address, + cashToken: ape.address, + cashAmount: parseEther("1250000"), + isBAYC: false, + apeTokenIds: Array.from(Array(100).keys()).slice(75), //mayc 75-100 + bakcTokenIds: Array.from(Array(125).keys()).slice(100), //bakc 100-125 + }, + ] + ); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .multicall([tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7, tx8]) + ); + }); + + it(" staking", async () => { + const { + users: [user1], + } = testEnv; + + const tx0 = paraApeStaking.interface.encodeFunctionData("stakingPairNFT", [ + true, + Array.from(Array(25).keys()), //bayc 0-25 + Array.from(Array(25).keys()), //bakc 0-25 + ]); + const tx1 = paraApeStaking.interface.encodeFunctionData("stakingPairNFT", [ + false, + Array.from(Array(25).keys()), //mayc 0-25 + Array.from(Array(50).keys()).slice(25), //bakc 25-50 + ]); + const tx2 = paraApeStaking.interface.encodeFunctionData("stakingApe", [ + true, + Array.from(Array(50).keys()).slice(25), //bayc 25-50 + ]); + const tx3 = paraApeStaking.interface.encodeFunctionData("stakingApe", [ + false, + Array.from(Array(50).keys()).slice(25), //mayc 25-50 + ]); + const tx4 = paraApeStaking.interface.encodeFunctionData("stakingBAKC", [ + { + baycTokenIds: Array.from(Array(50).keys()).slice(25, 30), //bayc 25-30 + bakcPairBaycTokenIds: Array.from(Array(75).keys()).slice(50, 55), //bakc 50-55 + maycTokenIds: Array.from(Array(50).keys()).slice(30), //mayc 30-50 + bakcPairMaycTokenIds: Array.from(Array(75).keys()).slice(55), //bakc 55-75 + }, + ]); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .multicall([tx0, tx1, tx2, tx3, tx4]) + ); + }); + + it("first compound", async () => { + const { + users: [, , , user4], + } = testEnv; + + await advanceTimeAndBlock(parseInt("4000")); + + const tx0 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ + true, + Array.from(Array(25).keys()), //bayc 0-25, + Array.from(Array(25).keys()), //bakc 0-25 + ]); + + const tx1 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ + false, + Array.from(Array(25).keys()), //mayc 0-25 + Array.from(Array(50).keys()).slice(25), //bakc 25-50 + ]); + + const tx2 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ + true, + Array.from(Array(50).keys()).slice(25), //bayc 25-50 + ]); + const tx3 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ + false, + Array.from(Array(50).keys()).slice(25), //mayc 25-50 + ]); + const tx4 = paraApeStaking.interface.encodeFunctionData("compoundBAKC", [ + { + baycTokenIds: Array.from(Array(50).keys()).slice(25, 30), //bayc 25-30 + bakcPairBaycTokenIds: Array.from(Array(75).keys()).slice(50, 55), //bakc 50-55 + maycTokenIds: Array.from(Array(50).keys()).slice(30), //mayc 30-50 + bakcPairMaycTokenIds: Array.from(Array(75).keys()).slice(55), //bakc 55-75 + }, + ]); + + const tx5 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPairPool", + [ + true, + Array.from(Array(100).keys()).slice(75), //bayc 75-100 + Array.from(Array(100).keys()).slice(75), //bakc 75-100 + ] + ); + + const tx6 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPairPool", + [ + false, + Array.from(Array(100).keys()).slice(75), //mayc 75-100 + Array.from(Array(125).keys()).slice(100), //bakc 100-125 + ] + ); + + const tx7 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPool", + [ + true, + Array.from(Array(75).keys()).slice(50), //bayc 50-75 + ] + ); + + const tx8 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPool", + [ + false, + Array.from(Array(75).keys()).slice(50), //mayc 50-75 + ] + ); + + const receipt = await waitForTx( + await paraApeStaking + .connect(user4.signer) + .multicall([tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7, tx8]) + ); + console.log("gas: ", receipt.gasUsed); + }); + + it("second compound", async () => { + const { + users: [, , , user4], + } = testEnv; + + await advanceTimeAndBlock(parseInt("4000")); + + const tx0 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ + true, + Array.from(Array(25).keys()), //bayc 0-25, + Array.from(Array(25).keys()), //bakc 0-25 + ]); + + const tx1 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ + false, + Array.from(Array(25).keys()), //mayc 0-25 + Array.from(Array(50).keys()).slice(25), //bakc 25-50 + ]); + + const tx2 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ + true, + Array.from(Array(50).keys()).slice(25), //bayc 25-50 + ]); + const tx3 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ + false, + Array.from(Array(50).keys()).slice(25), //mayc 25-50 + ]); + const tx4 = paraApeStaking.interface.encodeFunctionData("compoundBAKC", [ + { + baycTokenIds: Array.from(Array(50).keys()).slice(25, 30), //bayc 25-30 + bakcPairBaycTokenIds: Array.from(Array(75).keys()).slice(50, 55), //bakc 50-55 + maycTokenIds: Array.from(Array(50).keys()).slice(30), //mayc 30-50 + bakcPairMaycTokenIds: Array.from(Array(75).keys()).slice(55), //bakc 55-75 + }, + ]); + + const tx5 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPairPool", + [ + true, + Array.from(Array(100).keys()).slice(75), //bayc 75-100 + Array.from(Array(100).keys()).slice(75), //bakc 75-100 + ] + ); + + const tx6 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPairPool", + [ + false, + Array.from(Array(100).keys()).slice(75), //mayc 75-100 + Array.from(Array(125).keys()).slice(100), //bakc 100-125 + ] + ); + + const tx7 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPool", + [ + true, + Array.from(Array(75).keys()).slice(50), //bayc 50-75 + ] + ); + + const tx8 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPool", + [ + false, + Array.from(Array(75).keys()).slice(50), //mayc 50-75 + ] + ); + + const receipt = await waitForTx( + await paraApeStaking + .connect(user4.signer) + .multicall([tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7, tx8]) + ); + console.log("gas: ", receipt.gasUsed); + }); + + it(" second deposit", async () => { + const { + users: [user1], + bayc, + mayc, + bakc, + } = testEnv; + const tx0 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ + user1.address, + true, + [100, 101], + [125, 126], + ]); + const tx1 = paraApeStaking.interface.encodeFunctionData("depositPairNFT", [ + user1.address, + false, + [100, 101], + [127, 128], + ]); + const tx2 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + user1.address, + bayc.address, + [102, 103], + ]); + const tx3 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + user1.address, + mayc.address, + [102, 103], + ]); + const tx4 = paraApeStaking.interface.encodeFunctionData("depositNFT", [ + user1.address, + bakc.address, + [129, 130, 131, 132], + ]); + + await waitForTx( + await paraApeStaking + .connect(user1.signer) + .multicall([tx0, tx1, tx2, tx3, tx4]) + ); + }); + + it("third compound with staking", async () => { + const { + users: [, , , user4], + } = testEnv; + + await advanceTimeAndBlock(parseInt("4000")); + + const tx0 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ + true, + Array.from(Array(25).keys()), //bayc 0-25, + Array.from(Array(25).keys()), //bakc 0-25 + ]); + + const tx1 = paraApeStaking.interface.encodeFunctionData("compoundPairNFT", [ + false, + Array.from(Array(25).keys()), //mayc 0-25 + Array.from(Array(50).keys()).slice(25), //bakc 25-50 + ]); + + const tx2 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ + true, + Array.from(Array(50).keys()).slice(25), //bayc 25-50 + ]); + const tx3 = paraApeStaking.interface.encodeFunctionData("compoundApe", [ + false, + Array.from(Array(50).keys()).slice(25), //mayc 25-50 + ]); + const tx4 = paraApeStaking.interface.encodeFunctionData("compoundBAKC", [ + { + baycTokenIds: Array.from(Array(50).keys()).slice(25, 30), //bayc 25-30 + bakcPairBaycTokenIds: Array.from(Array(75).keys()).slice(50, 55), //bakc 50-55 + maycTokenIds: Array.from(Array(50).keys()).slice(30), //mayc 30-50 + bakcPairMaycTokenIds: Array.from(Array(75).keys()).slice(55), //bakc 55-75 + }, + ]); + + const tx5 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPairPool", + [ + true, + Array.from(Array(100).keys()).slice(75), //bayc 75-100 + Array.from(Array(100).keys()).slice(75), //bakc 75-100 + ] + ); + + const tx6 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPairPool", + [ + false, + Array.from(Array(100).keys()).slice(75), //mayc 75-100 + Array.from(Array(125).keys()).slice(100), //bakc 100-125 + ] + ); + + const tx7 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPool", + [ + true, + Array.from(Array(75).keys()).slice(50), //bayc 50-75 + ] + ); + + const tx8 = paraApeStaking.interface.encodeFunctionData( + "compoundApeCoinPool", + [ + false, + Array.from(Array(75).keys()).slice(50), //mayc 50-75 + ] + ); + + const tx9 = paraApeStaking.interface.encodeFunctionData("stakingPairNFT", [ + true, + [100, 101], + [125, 126], + ]); + const tx10 = paraApeStaking.interface.encodeFunctionData("stakingPairNFT", [ + false, + [100, 101], + [127, 128], + ]); + const tx11 = paraApeStaking.interface.encodeFunctionData("stakingApe", [ + true, + [102, 103], + ]); + const tx12 = paraApeStaking.interface.encodeFunctionData("stakingApe", [ + false, + [102, 103], + ]); + const tx13 = paraApeStaking.interface.encodeFunctionData("stakingBAKC", [ + { + baycTokenIds: [102, 103], //bayc 25-30 + bakcPairBaycTokenIds: [129, 130], //bakc 50-55 + maycTokenIds: [102, 103], //mayc 30-50 + bakcPairMaycTokenIds: [131, 132], //bakc 55-75 + }, + ]); + + const receipt = await waitForTx( + await paraApeStaking + .connect(user4.signer) + .multicall([ + tx0, + tx1, + tx2, + tx3, + tx4, + tx5, + tx6, + tx7, + tx8, + tx9, + tx10, + tx11, + tx12, + tx13, + ]) + ); + console.log("gas: ", receipt.gasUsed); + }); +});