From 833bf75e18845dd9072cac06e63234e7062f0b57 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 13 Jun 2023 15:33:33 +0800 Subject: [PATCH 01/16] 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 02/16] 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 03/16] 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 04/16] 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 05/16] 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 06/16] 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 07/16] 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 08/16] 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 09/16] 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 10/16] 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 11/16] 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 12/16] 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 13/16] 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 14/16] 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 15/16] 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 16/16] 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}, + ]) + ); + }); });