From e4765ecb69258031083d1caf00ce5e80b550ee05 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 6 Jul 2023 23:10:16 +0800 Subject: [PATCH] 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 () => {}); });