From 955fe2bed4ecc567770f37b952fd1536c5584b46 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 11 Dec 2023 11:52:15 +0800 Subject: [PATCH 1/9] chore: cross chain struct implementation --- contracts/cross-chain/BridgeDefine.sol | 31 +++++++ .../cross-chain/L1/IParaxBridgeNFTVault.sol | 12 +++ .../cross-chain/L1/IParaxL1MessageHandler.sol | 10 +++ .../cross-chain/L1/ParaxBridgeNFTVault.sol | 86 +++++++++++++++++++ .../cross-chain/L1/ParaxL1MessageHandler.sol | 49 +++++++++++ contracts/cross-chain/L2/BridgeERC721.sol | 47 ++++++++++ .../cross-chain/L2/BridgeERC721Handler.sol | 34 ++++++++ contracts/cross-chain/L2/IBridgeERC721.sol | 8 ++ .../cross-chain/L2/IParaxL2MessageHandler.sol | 12 +++ .../cross-chain/L2/ParaxL2MessageHandler.sol | 38 ++++++++ contracts/interfaces/IPool.sol | 4 +- contracts/interfaces/IPoolCrossChain.sol | 20 +++++ contracts/interfaces/ITokenDelegation.sol | 6 -- contracts/mocks/upgradeability/MockNToken.sol | 2 +- .../protocol/libraries/helpers/Errors.sol | 6 ++ contracts/protocol/tokenization/NToken.sol | 6 +- .../tokenization/NTokenApeStaking.sol | 6 +- .../protocol/tokenization/NTokenBAKC.sol | 5 +- .../protocol/tokenization/NTokenBAYC.sol | 5 +- .../tokenization/NTokenChromieSquiggle.sol | 3 +- .../protocol/tokenization/NTokenMAYC.sol | 5 +- .../protocol/tokenization/NTokenMoonBirds.sol | 19 +--- .../protocol/tokenization/NTokenOtherdeed.sol | 44 ---------- .../protocol/tokenization/NTokenStakefish.sol | 5 +- .../protocol/tokenization/NTokenUniswapV3.sol | 5 +- .../base/MintableIncentivizedERC721.sol | 14 +-- .../libraries/MintableERC721Logic.sol | 36 +++----- contracts/ui/UiPoolDataProvider.sol | 32 ------- .../ui/interfaces/IUiPoolDataProvider.sol | 5 -- helpers/contracts-deployments.ts | 24 ------ helpers/contracts-getters.ts | 12 --- helpers/hardhat-constants.ts | 4 +- helpers/init-helpers.ts | 1 - helpers/types.ts | 1 - scripts/deployments/steps/11_allReserves.ts | 1 - scripts/upgrade/ntoken.ts | 6 +- 36 files changed, 388 insertions(+), 216 deletions(-) create mode 100644 contracts/cross-chain/BridgeDefine.sol create mode 100644 contracts/cross-chain/L1/IParaxBridgeNFTVault.sol create mode 100644 contracts/cross-chain/L1/IParaxL1MessageHandler.sol create mode 100644 contracts/cross-chain/L1/ParaxBridgeNFTVault.sol create mode 100644 contracts/cross-chain/L1/ParaxL1MessageHandler.sol create mode 100644 contracts/cross-chain/L2/BridgeERC721.sol create mode 100644 contracts/cross-chain/L2/BridgeERC721Handler.sol create mode 100644 contracts/cross-chain/L2/IBridgeERC721.sol create mode 100644 contracts/cross-chain/L2/IParaxL2MessageHandler.sol create mode 100644 contracts/cross-chain/L2/ParaxL2MessageHandler.sol create mode 100644 contracts/interfaces/IPoolCrossChain.sol delete mode 100644 contracts/protocol/tokenization/NTokenOtherdeed.sol diff --git a/contracts/cross-chain/BridgeDefine.sol b/contracts/cross-chain/BridgeDefine.sol new file mode 100644 index 000000000..cdf4ff6b5 --- /dev/null +++ b/contracts/cross-chain/BridgeDefine.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +enum MessageType { + AddNewCrossChainERC721, + BridgeERC721, + ERC721DELEGATION +} + +struct BridgeMessage { + MessageType msgType; + bytes data; +} + +struct BridgeERC721Message { + address asset; + uint256[] tokenIds; + address receiver; +} + +struct ERC721DelegationMessage { + address asset; + address delegateTo; + uint256[] tokenIds; + bool value; +} + +//library BridgeDefine { +// +// +//} diff --git a/contracts/cross-chain/L1/IParaxBridgeNFTVault.sol b/contracts/cross-chain/L1/IParaxBridgeNFTVault.sol new file mode 100644 index 000000000..e1bde7b26 --- /dev/null +++ b/contracts/cross-chain/L1/IParaxBridgeNFTVault.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; + +interface IParaxBridgeNFTVault { + function releaseNFT(BridgeERC721Message calldata message) external; + + function updateTokenDelegation( + ERC721DelegationMessage calldata delegationInfo + ) external; +} diff --git a/contracts/cross-chain/L1/IParaxL1MessageHandler.sol b/contracts/cross-chain/L1/IParaxL1MessageHandler.sol new file mode 100644 index 000000000..a1d2d70c0 --- /dev/null +++ b/contracts/cross-chain/L1/IParaxL1MessageHandler.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {MessageType, BridgeMessage, BridgeERC721Message} from "../BridgeDefine.sol"; + +interface IParaxL1MessageHandler { + function addBridgeAsset(address asset) external; + + function bridgeAsset(BridgeERC721Message calldata message) external; +} diff --git a/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol b/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol new file mode 100644 index 000000000..64dde6ad2 --- /dev/null +++ b/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; +import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; +import "../../dependencies/openzeppelin/upgradeability/Initializable.sol"; +import "../../dependencies/openzeppelin/upgradeability/OwnableUpgradeable.sol"; +import "./IParaxL1MessageHandler.sol"; +import {IDelegateRegistry} from "../../dependencies/delegation/IDelegateRegistry.sol"; + +contract ParaxBridgeNFTVault is Initializable, OwnableUpgradeable { + IParaxL1MessageHandler internal immutable l1MsgHander; + + IDelegateRegistry delegationRegistry; + + mapping(address => bool) supportAsset; + + constructor(IParaxL1MessageHandler msgHandler) { + l1MsgHander = msgHandler; + } + + modifier onlyMsgHandler() { + require(msg.sender == address(l1MsgHander), Errors.ONLY_MSG_HANDLER); + _; + } + + function addBridgeAsset(address asset) external { + require(supportAsset[asset] == false, "asset already added"); + supportAsset[asset] = true; + l1MsgHander.addBridgeAsset(asset); + } + + function bridgeAsset( + address asset, + uint256[] calldata tokenIds, + address receiver + ) external { + require(supportAsset[asset] == true, "asset already added"); + //lock asset + uint256 length = tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = tokenIds[index]; + IERC721(asset).safeTransferFrom(msg.sender, address(this), tokenId); + } + + //send cross chain msg + l1MsgHander.bridgeAsset( + BridgeERC721Message({ + asset: asset, + tokenIds: tokenIds, + receiver: receiver + }) + ); + } + + function releaseNFT( + BridgeERC721Message calldata message + ) external onlyMsgHandler { + uint256 length = message.tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = message.tokenIds[index]; + IERC721(message.asset).safeTransferFrom( + address(this), + message.receiver, + tokenId + ); + } + } + + function updateTokenDelegation( + ERC721DelegationMessage calldata delegationInfo + ) external onlyMsgHandler { + uint256 length = delegationInfo.tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = delegationInfo.tokenIds[index]; + delegationRegistry.delegateERC721( + delegationInfo.delegateTo, + delegationInfo.asset, + tokenId, + "", + delegationInfo.value + ); + } + } +} diff --git a/contracts/cross-chain/L1/ParaxL1MessageHandler.sol b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol new file mode 100644 index 000000000..15825d338 --- /dev/null +++ b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; +pragma abicoder v2; + +import {MessageType, BridgeMessage, BridgeERC721Message} from "../BridgeDefine.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; +import "./IParaxBridgeNFTVault.sol"; + +contract ParaxL1MessageHandler { + IParaxBridgeNFTVault internal immutable nftVault; + address immutable bridgeImpl; + + constructor(IParaxBridgeNFTVault vault, address bridge) { + nftVault = vault; + bridgeImpl = bridge; + } + + modifier onlyVault() { + require(msg.sender == address(nftVault), Errors.ONLY_VAULT); + _; + } + + modifier onlyBridge() { + require(msg.sender == address(bridgeImpl), Errors.ONLY_BRIDGE); + _; + } + + function addBridgeAsset(address asset) external onlyVault {} + + function bridgeAsset( + BridgeERC721Message calldata message + ) external onlyVault {} + + function bridgeReceive(BridgeMessage calldata message) external onlyBridge { + if (message.msgType == MessageType.BridgeERC721) { + BridgeERC721Message memory message = abi.decode( + message.data, + (BridgeERC721Message) + ); + nftVault.releaseNFT(message); + } else if (message.msgType == MessageType.ERC721DELEGATION) { + ERC721DelegationMessage memory message = abi.decode( + message.data, + (ERC721DelegationMessage) + ); + nftVault.updateTokenDelegation(message); + } + } +} diff --git a/contracts/cross-chain/L2/BridgeERC721.sol b/contracts/cross-chain/L2/BridgeERC721.sol new file mode 100644 index 000000000..f8db4385b --- /dev/null +++ b/contracts/cross-chain/L2/BridgeERC721.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ERC721} from "../../dependencies/openzeppelin/contracts/ERC721.sol"; +import {ERC721Enumerable} from "../../dependencies/openzeppelin/contracts/ERC721Enumerable.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; + +contract BridgeERC21 is ERC721Enumerable { + address internal immutable handler; + + modifier onlyHandler() { + require(msg.sender == handler, Errors.ONLY_VAULT); + _; + } + + constructor( + string memory name, + string memory symbol, + address _handler + ) ERC721(name, symbol) { + handler = _handler; + } + + function mint( + address to, + uint256[] calldata tokenIds + ) external onlyHandler { + uint256 length = tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = tokenIds[index]; + _mint(to, tokenId); + } + } + + function burn( + address from, + uint256[] calldata tokenIds + ) external onlyHandler { + uint256 length = tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = tokenIds[index]; + address owner = ownerOf(tokenId); + require(owner == from, "invalid"); + _burn(tokenId); + } + } +} diff --git a/contracts/cross-chain/L2/BridgeERC721Handler.sol b/contracts/cross-chain/L2/BridgeERC721Handler.sol new file mode 100644 index 000000000..81fdc81bf --- /dev/null +++ b/contracts/cross-chain/L2/BridgeERC721Handler.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {MessageType, BridgeMessage, BridgeERC721Message} from "../BridgeDefine.sol"; +import {ERC721} from "../../dependencies/openzeppelin/contracts/ERC721.sol"; +import "./IParaxL2MessageHandler.sol"; +import "./IBridgeERC721.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; + +contract BridgeERC21Handler { + IParaxL2MessageHandler internal immutable l2MsgHandler; + + //origin asset -> bridge asset + mapping(address => address) getBridgeAsset; + mapping(address => address) getOriginAsset; + + constructor(IParaxL2MessageHandler msgHandler) { + l2MsgHandler = msgHandler; + } + + modifier onlyMsgHandler() { + require(msg.sender == address(l2MsgHandler), Errors.ONLY_HANDLER); + _; + } + + function bridgeAsset( + BridgeERC721Message calldata message + ) external onlyMsgHandler { + address bridgeAsset = getBridgeAsset[message.asset]; + require(bridgeAsset != address(0), "invalid"); + + IBridgeERC721(bridgeAsset).mint(message.receiver, message.tokenIds); + } +} diff --git a/contracts/cross-chain/L2/IBridgeERC721.sol b/contracts/cross-chain/L2/IBridgeERC721.sol new file mode 100644 index 000000000..327eb7b51 --- /dev/null +++ b/contracts/cross-chain/L2/IBridgeERC721.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IBridgeERC721 { + function mint(address to, uint256[] calldata tokenId) external; + + function burn(address from, uint256[] calldata tokenId) external; +} diff --git a/contracts/cross-chain/L2/IParaxL2MessageHandler.sol b/contracts/cross-chain/L2/IParaxL2MessageHandler.sol new file mode 100644 index 000000000..d2f4a1904 --- /dev/null +++ b/contracts/cross-chain/L2/IParaxL2MessageHandler.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {MessageType, BridgeMessage, BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; + +interface IParaxL2MessageHandler { + //function bridgeAsset(BridgeERC721Message calldata message) external; + + function updateTokenDelegation( + ERC721DelegationMessage calldata delegationInfo + ) external; +} diff --git a/contracts/cross-chain/L2/ParaxL2MessageHandler.sol b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol new file mode 100644 index 000000000..216c74657 --- /dev/null +++ b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {MessageType, BridgeMessage} from "../BridgeDefine.sol"; +import "./BridgeERC721Handler.sol"; +import "./IParaxL2MessageHandler.sol"; + +contract ParaxL2MessageHandler is IParaxL2MessageHandler { + BridgeERC21Handler internal immutable erc712Handler; + address immutable bridgeImpl; + address immutable paraX; + + constructor(BridgeERC21Handler handler) { + erc712Handler = handler; + } + + function bridgeReceive(BridgeMessage calldata message) external { + require(msg.sender == bridgeImpl, ""); + if (message.msgType == MessageType.BridgeERC721) { + BridgeERC721Message memory message = abi.decode( + message.data, + (BridgeERC721Message) + ); + erc712Handler.bridgeAsset(message); + } else {} + } + + function updateTokenDelegation( + ERC721DelegationMessage calldata delegationInfo + ) external { + require(msg.sender == paraX, Errors.ONLY_PARAX); + + BridgeMessage memory message; + message.msgType = MessageType.ERC721DELEGATION; + message.data = abi.encode(delegationInfo); + //send msg + } +} diff --git a/contracts/interfaces/IPool.sol b/contracts/interfaces/IPool.sol index f099ff026..d326fc614 100644 --- a/contracts/interfaces/IPool.sol +++ b/contracts/interfaces/IPool.sol @@ -9,6 +9,7 @@ import {IPoolPositionMover} from "./IPoolPositionMover.sol"; import {IPoolAAPositionMover} from "./IPoolAAPositionMover.sol"; import "./IPoolApeStaking.sol"; import "./IPoolBorrowAndStake.sol"; +import "./IPoolCrossChain.sol"; /** * @title IPool @@ -23,7 +24,8 @@ interface IPool is IParaProxyInterfaces, IPoolPositionMover, IPoolBorrowAndStake, - IPoolAAPositionMover + IPoolAAPositionMover, + IPoolCrossChain { } diff --git a/contracts/interfaces/IPoolCrossChain.sol b/contracts/interfaces/IPoolCrossChain.sol new file mode 100644 index 000000000..d225b6b25 --- /dev/null +++ b/contracts/interfaces/IPoolCrossChain.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.0; + +import {DataTypes} from "../protocol/libraries/types/DataTypes.sol"; + +/** + * @title IPool + * + * @notice Defines the basic interface for an ParaSpace Pool. + **/ +interface IPoolCrossChain { + function updateTokenDelegation( + address delegateTo, + address underlyingAsset, + uint256[] calldata tokenIds, + bool value + ) external; + + function CROSS_CHAIN_MSG_HANDLER() external view returns (address); +} diff --git a/contracts/interfaces/ITokenDelegation.sol b/contracts/interfaces/ITokenDelegation.sol index c240023ec..2b8539ebe 100644 --- a/contracts/interfaces/ITokenDelegation.sol +++ b/contracts/interfaces/ITokenDelegation.sol @@ -13,10 +13,4 @@ interface ITokenDelegation { uint256[] calldata tokenIds, bool value ) external; - - /** - * @notice Returns the address of the delegation registry of this nToken - * @return The address of the delegation registry - **/ - function DELEGATE_REGISTRY() external view returns (address); } diff --git a/contracts/mocks/upgradeability/MockNToken.sol b/contracts/mocks/upgradeability/MockNToken.sol index e3477eeb2..707a7146e 100644 --- a/contracts/mocks/upgradeability/MockNToken.sol +++ b/contracts/mocks/upgradeability/MockNToken.sol @@ -6,7 +6,7 @@ import {IPool} from "../../interfaces/IPool.sol"; import {IRewardController} from "../../interfaces/IRewardController.sol"; contract MockNToken is NToken { - constructor(IPool pool, address delegateRegistry) NToken(pool, false, delegateRegistry) {} + constructor(IPool pool) NToken(pool, false) {} function getRevision() internal pure override returns (uint256) { return 999; diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index ece1ab752..55f86e966 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -136,4 +136,10 @@ library Errors { string public constant INVALID_PARAMETER = "170"; //invalid parameter string public constant INVALID_CALLER = "171"; //invalid callser + + string public constant ONLY_MSG_HANDLER = "200"; //only msg handler + string public constant ONLY_VAULT = "201"; //only vault + string public constant ONLY_HANDLER = "202"; //only handler + string public constant ONLY_PARAX = "203"; //only parax + string public constant ONLY_BRIDGE = "204"; //only cross-chain bridge } diff --git a/contracts/protocol/tokenization/NToken.sol b/contracts/protocol/tokenization/NToken.sol index ad2592278..98defb89e 100644 --- a/contracts/protocol/tokenization/NToken.sol +++ b/contracts/protocol/tokenization/NToken.sol @@ -42,15 +42,13 @@ contract NToken is VersionedInitializable, MintableIncentivizedERC721, INToken { */ constructor( IPool pool, - bool atomic_pricing, - address delegateRegistry + bool atomic_pricing ) MintableIncentivizedERC721( pool, "NTOKEN_IMPL", "NTOKEN_IMPL", - atomic_pricing, - delegateRegistry + atomic_pricing ) {} diff --git a/contracts/protocol/tokenization/NTokenApeStaking.sol b/contracts/protocol/tokenization/NTokenApeStaking.sol index 1fbe8a6ce..55dbaae97 100644 --- a/contracts/protocol/tokenization/NTokenApeStaking.sol +++ b/contracts/protocol/tokenization/NTokenApeStaking.sol @@ -35,11 +35,7 @@ abstract contract NTokenApeStaking is NToken, INTokenApeStaking { * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address apeCoinStaking, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { + constructor(IPool pool, address apeCoinStaking) NToken(pool, false) { _apeCoinStaking = ApeCoinStaking(apeCoinStaking); } diff --git a/contracts/protocol/tokenization/NTokenBAKC.sol b/contracts/protocol/tokenization/NTokenBAKC.sol index 31432b406..18a31cff0 100644 --- a/contracts/protocol/tokenization/NTokenBAKC.sol +++ b/contracts/protocol/tokenization/NTokenBAKC.sol @@ -32,9 +32,8 @@ contract NTokenBAKC is NToken { IPool pool, address apeCoinStaking, address _nBAYC, - address _nMAYC, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { + address _nMAYC + ) NToken(pool, false) { _apeCoinStaking = ApeCoinStaking(apeCoinStaking); nBAYC = _nBAYC; nMAYC = _nMAYC; diff --git a/contracts/protocol/tokenization/NTokenBAYC.sol b/contracts/protocol/tokenization/NTokenBAYC.sol index 55fd6cf69..c87ab35a0 100644 --- a/contracts/protocol/tokenization/NTokenBAYC.sol +++ b/contracts/protocol/tokenization/NTokenBAYC.sol @@ -15,9 +15,8 @@ import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; contract NTokenBAYC is NTokenApeStaking { constructor( IPool pool, - address apeCoinStaking, - address delegateRegistry - ) NTokenApeStaking(pool, apeCoinStaking, delegateRegistry) {} + address apeCoinStaking + ) NTokenApeStaking(pool, apeCoinStaking) {} /** * @notice Deposit ApeCoin to the BAYC Pool diff --git a/contracts/protocol/tokenization/NTokenChromieSquiggle.sol b/contracts/protocol/tokenization/NTokenChromieSquiggle.sol index f555e9558..2e6b1639f 100644 --- a/contracts/protocol/tokenization/NTokenChromieSquiggle.sol +++ b/contracts/protocol/tokenization/NTokenChromieSquiggle.sol @@ -32,10 +32,9 @@ contract NTokenChromieSquiggle is NToken { */ constructor( IPool pool, - address delegateRegistry, uint256 _startTokenId, uint256 _endTokenId - ) NToken(pool, false, delegateRegistry) { + ) NToken(pool, false) { startTokenId = _startTokenId; endTokenId = _endTokenId; } diff --git a/contracts/protocol/tokenization/NTokenMAYC.sol b/contracts/protocol/tokenization/NTokenMAYC.sol index 780a67c91..5c4be8f08 100644 --- a/contracts/protocol/tokenization/NTokenMAYC.sol +++ b/contracts/protocol/tokenization/NTokenMAYC.sol @@ -15,9 +15,8 @@ import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; contract NTokenMAYC is NTokenApeStaking { constructor( IPool pool, - address apeCoinStaking, - address delegateRegistry - ) NTokenApeStaking(pool, apeCoinStaking, delegateRegistry) {} + address apeCoinStaking + ) NTokenApeStaking(pool, apeCoinStaking) {} /** * @notice Deposit ApeCoin to the MAYC Pool diff --git a/contracts/protocol/tokenization/NTokenMoonBirds.sol b/contracts/protocol/tokenization/NTokenMoonBirds.sol index fc24a298e..c069fc9b8 100644 --- a/contracts/protocol/tokenization/NTokenMoonBirds.sol +++ b/contracts/protocol/tokenization/NTokenMoonBirds.sol @@ -26,19 +26,11 @@ import {ITimeLock} from "../../interfaces/ITimeLock.sol"; * @notice Implementation of the interest bearing token for the ParaSpace protocol */ contract NTokenMoonBirds is NToken, IMoonBirdBase { - address internal immutable timeLockV1; - /** * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address delegateRegistry, - address _timeLockV1 - ) NToken(pool, false, delegateRegistry) { - timeLockV1 = _timeLockV1; - } + constructor(IPool pool) NToken(pool, false) {} function getXTokenType() external pure override returns (XTokenType) { return XTokenType.NTokenMoonBirds; @@ -98,7 +90,7 @@ contract NTokenMoonBirds is NToken, IMoonBirdBase { ) external virtual override returns (bytes4) { // if the operator is the pool, this means that the pool is transferring the token to this contract // which can happen during a normal supplyERC721 pool tx - if (operator == address(POOL) || operator == timeLockV1) { + if (operator == address(POOL)) { return this.onERC721Received.selector; } @@ -154,11 +146,4 @@ contract NTokenMoonBirds is NToken, IMoonBirdBase { function nestingOpen() external view returns (bool) { return IMoonBird(_ERC721Data.underlyingAsset).nestingOpen(); } - - function claimUnderlying( - address timeLockV1, - uint256[] calldata agreementIds - ) external virtual override onlyPool { - ITimeLock(timeLockV1).claimMoonBirds(agreementIds); - } } diff --git a/contracts/protocol/tokenization/NTokenOtherdeed.sol b/contracts/protocol/tokenization/NTokenOtherdeed.sol deleted file mode 100644 index ab6933c40..000000000 --- a/contracts/protocol/tokenization/NTokenOtherdeed.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {IHotWalletProxy} from "../../interfaces/IHotWalletProxy.sol"; -import {NToken} from "./NToken.sol"; -import {IPool} from "../../interfaces/IPool.sol"; -import {XTokenType} from "../../interfaces/IXTokenType.sol"; - -/** - * @title Otherdeed NToken - * - * @notice Implementation of the interest bearing token for the ParaSpace protocol - */ -contract NTokenOtherdeed is NToken, IHotWalletProxy { - IHotWalletProxy private immutable WARM_WALLET; - - /** - * @dev Constructor. - * @param pool The address of the Pool contract - */ - constructor( - IPool pool, - IHotWalletProxy warmWallet, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { - WARM_WALLET = warmWallet; - } - - function setHotWallet( - address hotWalletAddress, - uint256 expirationTimestamp, - bool lockHotWalletAddress - ) external onlyPoolAdmin { - WARM_WALLET.setHotWallet( - hotWalletAddress, - expirationTimestamp, - lockHotWalletAddress - ); - } - - function getXTokenType() external pure override returns (XTokenType) { - return XTokenType.NTokenOtherdeed; - } -} diff --git a/contracts/protocol/tokenization/NTokenStakefish.sol b/contracts/protocol/tokenization/NTokenStakefish.sol index 92e302a24..73a2cb88f 100644 --- a/contracts/protocol/tokenization/NTokenStakefish.sol +++ b/contracts/protocol/tokenization/NTokenStakefish.sol @@ -26,10 +26,7 @@ contract NTokenStakefish is NToken, INTokenStakefish { * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address delegateRegistry - ) NToken(pool, false, delegateRegistry) { + constructor(IPool pool) NToken(pool, false) { WETH = IWETH(_addressesProvider.getWETH()); } diff --git a/contracts/protocol/tokenization/NTokenUniswapV3.sol b/contracts/protocol/tokenization/NTokenUniswapV3.sol index a7cc8cfc6..573dfccf6 100644 --- a/contracts/protocol/tokenization/NTokenUniswapV3.sol +++ b/contracts/protocol/tokenization/NTokenUniswapV3.sol @@ -31,10 +31,7 @@ contract NTokenUniswapV3 is NToken, INTokenUniswapV3 { * @dev Constructor. * @param pool The address of the Pool contract */ - constructor( - IPool pool, - address delegateRegistry - ) NToken(pool, true, delegateRegistry) { + constructor(IPool pool) NToken(pool, true) { _ERC721Data.balanceLimit = 30; } diff --git a/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol b/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol index 45abc5332..d47964476 100644 --- a/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol +++ b/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol @@ -91,7 +91,6 @@ abstract contract MintableIncentivizedERC721 is IPoolAddressesProvider internal immutable _addressesProvider; IPool internal immutable POOL; bool internal immutable ATOMIC_PRICING; - address internal immutable DELEGATE_REGISTRY_ADDRESS; /** * @dev Constructor. @@ -103,15 +102,13 @@ abstract contract MintableIncentivizedERC721 is IPool pool, string memory name_, string memory symbol_, - bool atomic_pricing, - address delegateRegistry + bool atomic_pricing ) { _addressesProvider = pool.ADDRESSES_PROVIDER(); _ERC721Data.name = name_; _ERC721Data.symbol = symbol_; POOL = pool; ATOMIC_PRICING = atomic_pricing; - DELEGATE_REGISTRY_ADDRESS = delegateRegistry; } function name() public view override returns (string memory) { @@ -395,7 +392,6 @@ abstract contract MintableIncentivizedERC721 is _ERC721Data, POOL, ATOMIC_PRICING, - DELEGATE_REGISTRY_ADDRESS, user, tokenIds ); @@ -424,7 +420,7 @@ abstract contract MintableIncentivizedERC721 is MintableERC721Logic.executeUpdateTokenDelegation( _ERC721Data, - DELEGATE_REGISTRY_ADDRESS, + POOL, delegate, tokenIds[index], value @@ -432,10 +428,6 @@ abstract contract MintableIncentivizedERC721 is } } - function DELEGATE_REGISTRY() external view returns (address) { - return DELEGATE_REGISTRY_ADDRESS; - } - /** * @dev Transfers `tokenId` from `from` to `to`. * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. @@ -456,7 +448,6 @@ abstract contract MintableIncentivizedERC721 is _ERC721Data, POOL, ATOMIC_PRICING, - DELEGATE_REGISTRY_ADDRESS, from, to, tokenId @@ -476,7 +467,6 @@ abstract contract MintableIncentivizedERC721 is _ERC721Data, POOL, ATOMIC_PRICING, - DELEGATE_REGISTRY_ADDRESS, from, to, tokenId diff --git a/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol b/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol index 6819c26d2..d4de32c4c 100644 --- a/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol +++ b/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol @@ -172,7 +172,6 @@ library MintableERC721Logic { MintableERC721Data storage erc721Data, IPool POOL, bool ATOMIC_PRICING, - address DELEGATION_REGISTRY, address from, address to, uint256 tokenId @@ -208,7 +207,7 @@ library MintableERC721Logic { if (from != to && tokenDelegationAddress != address(0)) { _updateTokenDelegation( erc721Data, - DELEGATION_REGISTRY, + POOL, tokenDelegationAddress, tokenId, false @@ -239,7 +238,6 @@ library MintableERC721Logic { MintableERC721Data storage erc721Data, IPool POOL, bool ATOMIC_PRICING, - address DELEGATION_REGISTRY, address from, address to, uint256 tokenId @@ -260,15 +258,7 @@ library MintableERC721Logic { delete erc721Data.isUsedAsCollateral[tokenId]; } - executeTransfer( - erc721Data, - POOL, - ATOMIC_PRICING, - DELEGATION_REGISTRY, - from, - to, - tokenId - ); + executeTransfer(erc721Data, POOL, ATOMIC_PRICING, from, to, tokenId); } function executeSetIsUsedAsCollateral( @@ -441,7 +431,6 @@ library MintableERC721Logic { MintableERC721Data storage erc721Data, IPool POOL, bool ATOMIC_PRICING, - address DELEGATION_REGISTRY, address user, uint256[] calldata tokenIds ) external returns (uint64, uint64) { @@ -496,7 +485,7 @@ library MintableERC721Logic { if (tokenDelegationAddress != address(0)) { _updateTokenDelegation( erc721Data, - DELEGATION_REGISTRY, + POOL, tokenDelegationAddress, tokenIds[index], false @@ -542,23 +531,17 @@ library MintableERC721Logic { function executeUpdateTokenDelegation( MintableERC721Data storage erc721Data, - address delegationRegistry, + IPool POOL, address delegate, uint256 tokenId, bool value ) external { - _updateTokenDelegation( - erc721Data, - delegationRegistry, - delegate, - tokenId, - value - ); + _updateTokenDelegation(erc721Data, POOL, delegate, tokenId, value); } function _updateTokenDelegation( MintableERC721Data storage erc721Data, - address delegationRegistry, + IPool POOL, address delegate, uint256 tokenId, bool value @@ -569,11 +552,12 @@ library MintableERC721Logic { delete erc721Data.tokenDelegations[tokenId]; } - IDelegateRegistry(delegationRegistry).delegateERC721( + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = tokenId; + POOL.updateTokenDelegation( delegate, erc721Data.underlyingAsset, - tokenId, - "", + tokenIds, value ); } diff --git a/contracts/ui/UiPoolDataProvider.sol b/contracts/ui/UiPoolDataProvider.sol index 540c23e0f..73a86b476 100644 --- a/contracts/ui/UiPoolDataProvider.sol +++ b/contracts/ui/UiPoolDataProvider.sol @@ -550,36 +550,4 @@ contract UiPoolDataProvider is IUiPoolDataProvider { } return (userData, tokensData); } - - function getDelegatesForTokens( - address vault, - uint256[] calldata tokenIds - ) external view returns (IDelegateRegistry.Delegation[] memory) { - address contract_ = INToken(vault).UNDERLYING_ASSET_ADDRESS(); - address delegationRegistry = ITokenDelegation(vault) - .DELEGATE_REGISTRY(); - - IDelegateRegistry.Delegation[] memory delegations = IDelegateRegistry( - delegationRegistry - ).getOutgoingDelegations(vault); - - uint256 tokenLength = tokenIds.length; - IDelegateRegistry.Delegation[] - memory ret = new IDelegateRegistry.Delegation[](tokenLength); - uint256 delegationsLength = delegations.length; - for (uint256 index = 0; index < tokenLength; index++) { - for (uint256 j = 0; j < delegationsLength; j++) { - IDelegateRegistry.Delegation memory delegation = delegations[j]; - if ( - delegation.contract_ == contract_ && - delegation.tokenId == tokenIds[index] - ) { - ret[index] = delegation; - break; - } - } - } - - return ret; - } } diff --git a/contracts/ui/interfaces/IUiPoolDataProvider.sol b/contracts/ui/interfaces/IUiPoolDataProvider.sol index ee36af3c3..194c0c45c 100644 --- a/contracts/ui/interfaces/IUiPoolDataProvider.sol +++ b/contracts/ui/interfaces/IUiPoolDataProvider.sol @@ -155,9 +155,4 @@ interface IUiPoolDataProvider { external view returns (UserGlobalData memory, TokenInLiquidationData[][] memory); - - function getDelegatesForTokens( - address vault, - uint256[] calldata tokenIds - ) external view returns (IDelegateRegistry.Delegation[] memory); } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index b825dbb2f..5019ae3f1 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -3096,30 +3096,6 @@ export const deployReserveTimeLockStrategy = async ( verify ) as Promise; -export const deployOtherdeedNTokenImpl = async ( - poolAddress: tEthereumAddress, - warmWallet: tEthereumAddress, - delegationRegistryAddress: tEthereumAddress, - verify?: boolean -) => { - const mintableERC721Logic = - (await getContractAddressInDb(eContractid.MintableERC721Logic)) || - (await deployMintableERC721Logic(verify)).address; - - const libraries = { - ["contracts/protocol/tokenization/libraries/MintableERC721Logic.sol:MintableERC721Logic"]: - mintableERC721Logic, - }; - return withSaveAndVerify( - await getContractFactory("NTokenOtherdeed", libraries), - eContractid.NTokenOtherdeedImpl, - [poolAddress, warmWallet, delegationRegistryAddress], - verify, - false, - libraries - ) as Promise; -}; - export const deployChromieSquiggleNTokenImpl = async ( poolAddress: tEthereumAddress, delegationRegistryAddress: tEthereumAddress, diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index 693b99166..86f055b71 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -88,7 +88,6 @@ import { MockCToken__factory, TimeLock__factory, HotWalletProxy__factory, - NTokenOtherdeed__factory, DelegateRegistry__factory, DepositContract__factory, StakefishNFTManager__factory, @@ -1218,17 +1217,6 @@ export const getTimeLockProxy = async (address?: tEthereumAddress) => await getFirstSigner() ); -export const getNTokenOtherdeed = async (address?: tEthereumAddress) => - await NTokenOtherdeed__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.NTokenOtherdeedImpl}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - export const getNTokenChromieSquiggle = async (address?: tEthereumAddress) => await NTokenChromieSquiggle__factory.connect( address || diff --git a/helpers/hardhat-constants.ts b/helpers/hardhat-constants.ts index ef66a0b05..058e6d513 100644 --- a/helpers/hardhat-constants.ts +++ b/helpers/hardhat-constants.ts @@ -469,7 +469,6 @@ export const eContractidToContractName = { TimeLockProxy: "InitializableAdminUpgradeabilityProxy", TimeLockImpl: "TimeLock", DefaultTimeLockStrategy: "DefaultTimeLockStrategy", - NTokenOtherdeedImpl: "NTokenOtherdeed", NTokenChromieSquiggleImpl: "NTokenChromieSquiggle", NTokenStakefishImpl: "NTokenStakefish", HotWalletProxy: "HotWalletProxy", @@ -498,5 +497,4 @@ export const XTOKEN_TYPE_UPGRADE_WHITELIST = .split(/\s?,\s?/) .map((x) => +x); export const XTOKEN_SYMBOL_UPGRADE_WHITELIST = - process.env.XTOKEN_SYMBOL_UPGRADE_WHITELIST?.trim() - .split(/\s?,\s?/); + process.env.XTOKEN_SYMBOL_UPGRADE_WHITELIST?.trim().split(/\s?,\s?/); diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index a8cb6c807..a77fc556e 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -329,7 +329,6 @@ export const initReservesByHelper = async ( eContractid.NTokenBAKCImpl, eContractid.NTokenStakefishImpl, eContractid.NTokenChromieSquiggleImpl, - eContractid.NTokenOtherdeedImpl, ].includes(xTokenImpl) ) { xTokenType[symbol] = "nft"; diff --git a/helpers/types.ts b/helpers/types.ts index 5e6c1842e..b32ff0909 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -270,7 +270,6 @@ export enum eContractid { TimeLockProxy = "TimeLockProxy", TimeLockImpl = "TimeLockImpl", DefaultTimeLockStrategy = "DefaultTimeLockStrategy", - NTokenOtherdeedImpl = "NTokenOtherdeedImpl", NTokenChromieSquiggleImpl = "NTokenChromieSquiggleImpl", NTokenStakefishImpl = "NTokenStakefishImpl", HotWalletProxy = "HotWalletProxy", diff --git a/scripts/deployments/steps/11_allReserves.ts b/scripts/deployments/steps/11_allReserves.ts index a8043e0f9..7330fe550 100644 --- a/scripts/deployments/steps/11_allReserves.ts +++ b/scripts/deployments/steps/11_allReserves.ts @@ -97,7 +97,6 @@ export const step_11 = async (verify = false) => { xTokenImpl === eContractid.PYieldTokenImpl || xTokenImpl === eContractid.NTokenBAKCImpl || xTokenImpl === eContractid.NTokenStakefishImpl || - xTokenImpl === eContractid.NTokenOtherdeedImpl || xTokenImpl === eContractid.NTokenChromieSquiggleImpl ) as [string, IReserveParams][]; const chunkedReserves = chunk(reserves, 20); diff --git a/scripts/upgrade/ntoken.ts b/scripts/upgrade/ntoken.ts index ba3ca36f6..21713a50b 100644 --- a/scripts/upgrade/ntoken.ts +++ b/scripts/upgrade/ntoken.ts @@ -76,12 +76,14 @@ export const upgradeNToken = async (verify = false) => { continue; } - if (XTOKEN_SYMBOL_UPGRADE_WHITELIST && !XTOKEN_SYMBOL_UPGRADE_WHITELIST.includes(symbol)) { + if ( + XTOKEN_SYMBOL_UPGRADE_WHITELIST && + !XTOKEN_SYMBOL_UPGRADE_WHITELIST.includes(symbol) + ) { console.log(symbol + "not in XTOKEN_SYMBOL_UPGRADE_WHITELIST, skip..."); continue; } - if (xTokenType == XTokenType.NTokenBAYC) { if (!nTokenBAYCImplementationAddress) { console.log("deploy NTokenBAYC implementation"); From 7ee99f1fb19079b8defc2f0f338a7b1bf532ba46 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 19 Dec 2023 09:25:39 +0800 Subject: [PATCH 2/9] chore: L1 apestaking & remove ntoken for BAYC/MAYC/BAKC --- contracts/apestaking/AutoCompoundApe.sol | 51 +- contracts/cross-chain/L1/IVaultApeStaking.sol | 150 ++++ .../cross-chain/L1/ParaxBridgeNFTVault.sol | 8 +- .../cross-chain/L1/ParaxL1MessageHandler.sol | 8 +- contracts/cross-chain/L1/VaultApeStaking.sol | 736 ++++++++++++++++++ .../cross-chain/L2/BridgeERC721Handler.sol | 6 +- .../cross-chain/L2/ParaxL2MessageHandler.sol | 4 +- .../openzeppelin/contracts/SafeCast.sol | 17 + contracts/interfaces/ICApe.sol | 61 ++ .../protocol/libraries/helpers/Errors.sol | 15 +- .../tokenization/NTokenApeStaking.sol | 230 ------ .../protocol/tokenization/NTokenBAKC.sol | 145 ---- .../protocol/tokenization/NTokenBAYC.sol | 113 --- .../protocol/tokenization/NTokenMAYC.sol | 113 --- helpers/contracts-deployments.ts | 123 +-- helpers/init-helpers.ts | 87 --- 16 files changed, 1037 insertions(+), 830 deletions(-) create mode 100644 contracts/cross-chain/L1/IVaultApeStaking.sol create mode 100644 contracts/cross-chain/L1/VaultApeStaking.sol delete mode 100644 contracts/protocol/tokenization/NTokenApeStaking.sol delete mode 100644 contracts/protocol/tokenization/NTokenBAKC.sol delete mode 100644 contracts/protocol/tokenization/NTokenBAYC.sol delete mode 100644 contracts/protocol/tokenization/NTokenMAYC.sol diff --git a/contracts/apestaking/AutoCompoundApe.sol b/contracts/apestaking/AutoCompoundApe.sol index ad0357cc6..33fdaefcc 100644 --- a/contracts/apestaking/AutoCompoundApe.sol +++ b/contracts/apestaking/AutoCompoundApe.sol @@ -6,8 +6,8 @@ import "../dependencies/openzeppelin/upgradeability/OwnableUpgradeable.sol"; import {IERC20} from "../dependencies/openzeppelin/contracts/IERC20.sol"; import {SafeERC20} from "../dependencies/openzeppelin/contracts/SafeERC20.sol"; import {ApeCoinStaking} from "../dependencies/yoga-labs/ApeCoinStaking.sol"; -import {IAutoCompoundApe} from "../interfaces/IAutoCompoundApe.sol"; import {CApe} from "./base/CApe.sol"; +import {ICApe} from "../interfaces/ICApe.sol"; import {IVoteDelegator} from "../interfaces/IVoteDelegator.sol"; import {IDelegation} from "../interfaces/IDelegation.sol"; import {IACLManager} from "../interfaces/IACLManager.sol"; @@ -17,8 +17,7 @@ contract AutoCompoundApe is Initializable, OwnableUpgradeable, CApe, - IVoteDelegator, - IAutoCompoundApe + IVoteDelegator { using SafeERC20 for IERC20; @@ -34,11 +33,19 @@ contract AutoCompoundApe is uint256 public bufferBalance; uint256 public stakingBalance; IACLManager private immutable aclManager; - - constructor(address _apeCoin, address _apeStaking, address _aclManager) { + address private immutable bridgeVault; + uint256 public nftStakingBalance; + + constructor( + address _apeCoin, + address _apeStaking, + address _aclManager, + address _bridgeVault + ) { apeStaking = ApeCoinStaking(_apeStaking); apeCoin = IERC20(_apeCoin); aclManager = IACLManager(_aclManager); + bridgeVault = _bridgeVault; } function initialize() public initializer { @@ -47,7 +54,7 @@ contract AutoCompoundApe is apeCoin.safeApprove(address(apeStaking), type(uint256).max); } - /// @inheritdoc IAutoCompoundApe + /// @inheritdoc ICApe function deposit(address onBehalf, uint256 amount) external override { require(amount > 0, "zero amount"); uint256 amountShare = getShareByPooledApe(amount); @@ -67,7 +74,7 @@ contract AutoCompoundApe is emit Deposit(msg.sender, onBehalf, amount, amountShare); } - /// @inheritdoc IAutoCompoundApe + /// @inheritdoc ICApe function withdraw(uint256 amount) external override { require(amount > 0, "zero amount"); @@ -87,12 +94,32 @@ contract AutoCompoundApe is emit Redeem(msg.sender, amount, amountShare); } - /// @inheritdoc IAutoCompoundApe + /// @inheritdoc ICApe function harvestAndCompound() external { _harvest(); _compound(); } + function borrowApeCoin(uint256 amount) external onlyBridgeVault { + _harvest(); + uint256 _bufferBalance = bufferBalance; + if (amount > _bufferBalance) { + _withdrawFromApeCoinStaking(amount - _bufferBalance); + } + _transferTokenOut(msg.sender, amount); + + nftStakingBalance += amount; + } + + function repayApeCoin(uint256 amount) external onlyBridgeVault { + _transferTokenIn(msg.sender, amount); + nftStakingBalance -= amount; + } + + function notifyReward(uint256 amount) external onlyBridgeVault { + _transferTokenIn(msg.sender, amount); + } + function _getTotalPooledApeBalance() internal view @@ -104,7 +131,8 @@ contract AutoCompoundApe is address(this), 0 ); - return stakingBalance + rewardAmount + bufferBalance; + return + stakingBalance + rewardAmount + bufferBalance + nftStakingBalance; } function _withdrawFromApeCoinStaking(uint256 amount) internal { @@ -207,6 +235,11 @@ contract AutoCompoundApe is _; } + modifier onlyBridgeVault() { + require(msg.sender == bridgeVault, Errors.ONLY_VAULT); + _; + } + /** * @dev Only emergency or pool admin can call functions marked by this modifier. **/ diff --git a/contracts/cross-chain/L1/IVaultApeStaking.sol b/contracts/cross-chain/L1/IVaultApeStaking.sol new file mode 100644 index 000000000..83a211284 --- /dev/null +++ b/contracts/cross-chain/L1/IVaultApeStaking.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; + +interface IVaultApeStaking { + struct TokenStatus { + //record tokenId reward debt position + uint128 rewardsDebt; + // income beneficiary + address beneficiary; + // is staking + bool isStaking; + // is paired with bakc, only for bayc/mayc + bool isPairedStaking; + // is paired with bayc, only for bakc + bool isPairedWithBayc; + // paired staking token id, bakc tokenId for bayc/mayc, ape tokenId for bakc + uint32 pairTokenId; + } + + struct PoolState { + // accumulated cApe reward for per NFT position + uint128 accumulatedRewardsPerNft; + // total NFT position count, max value for uint32 is 4294967296 + uint32 totalPosition; + // total NFT staking position + uint32 stakingPosition; + // total NFT pair staking position, only for bayc/mayc + uint32 pairStakingPosition; + // cApe income ratio + uint32 cApeIncomeRatio; + //tokenId => reward debt position + mapping(uint256 => TokenStatus) tokenStatus; + } + + struct BAKCPairActionInfo { + uint32[] baycTokenIds; + uint32[] bakcPairBaycTokenIds; + uint32[] maycTokenIds; + uint32[] bakcPairMaycTokenIds; + } + + /** + * @dev Emitted during setApeStakingBot() + * @param oldBot The address of the old compound bot + * @param newBot The address of the new compound bot + **/ + event ApeStakingBotUpdated(address oldBot, address newBot); + + /** + * @dev Emitted during setCompoundFeeRate() + * @param oldValue The old value of compound fee rate + * @param newValue The new value of compound fee rate + **/ + event CompoundFeeRateUpdated(uint32 oldValue, uint32 newValue); + + /** + * @dev Emitted during claimCompoundFee() + * @param amount The amount of fee claimed + **/ + event CompoundFeeClaimed(uint256 amount); + + /** + * @dev Emitted during claimPendingReward() + * @param nft identify which pool user claimed from + * @param tokenId identify position token id + * @param rewardAmount Reward amount claimed + **/ + event PoolRewardClaimed(address nft, uint256 tokenId, uint256 rewardAmount); + + /** + * @dev Emitted during updateBeneficiary() + * @param nft identify which pool user claimed from + * @param tokenId identify position token id + * @param newBenificiary new benificiary for the token id + **/ + event BeneficiaryUpdated( + address nft, + uint256 tokenId, + address newBenificiary + ); + + event ApeStaked(bool isBAYC, uint256 tokenId); + event BakcStaked(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + event ApeCompounded(bool isBAYC, uint256 tokenId); + event BakcCompounded(bool isBAYC, uint256 apeTokenId, uint256 bakcTokenId); + + /** + * @notice Query token status for the specified pool and nft + * @param nft Identify pool + * @param tokenId The tokenId of the nft + */ + function getTokenStatus( + address nft, + uint256 tokenId + ) external view returns (TokenStatus memory); + + /** + * @notice Query position pending reward in the pool, will revert if token id is not in the pool + * @param nft Identify pool + * @param tokenIds The tokenIds of the nft + */ + function getPendingReward( + address nft, + uint32[] calldata tokenIds + ) external view returns (uint256); + + /** + * @notice Claim position pending reward in the pool, will revert if token id is not in the pool + * @param nft Identify pool + * @param tokenIds The tokenIds of the nft + */ + function claimPendingReward( + address nft, + uint32[] calldata tokenIds + ) external; + + /** + * @notice stake pool's Ape into ApeCoinStaking + * @param isBAYC if Ape is BAYC + * @param tokenIds Ape token ids + */ + function stakingApe(bool isBAYC, uint32[] calldata tokenIds) external; + + /** + * @notice stake pool's Ape and BAKC into ApeCoinStaking pair staking pool + * @param actionInfo detail staking info + */ + function stakingBAKC(BAKCPairActionInfo calldata actionInfo) external; + + /** + * @notice claim Ape staking reward from ApeCoinStaking and compound as cApe for user + * only ape staking bot can call this function + * @param isBAYC if Ape is BAYC + * @param tokenIds Ape token ids + */ + function compoundApe(bool isBAYC, uint32[] calldata tokenIds) external; + + /** + * @notice claim single pool's Ape and BAKC pair staking reward from ApeCoinStaking and compound as cApe for user + * only ape staking bot can call this function + * @param actionInfo detail staking info + */ + function compoundBAKC(BAKCPairActionInfo calldata actionInfo) external; + + function checkApeStakingPosition(address nft, uint32 tokenId) external; + + function unstakeApe(bool isBAYC, uint32[] calldata tokenIds) external; +} diff --git a/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol b/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol index 64dde6ad2..e58284776 100644 --- a/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol +++ b/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol @@ -12,12 +12,16 @@ import {IDelegateRegistry} from "../../dependencies/delegation/IDelegateRegistry contract ParaxBridgeNFTVault is Initializable, OwnableUpgradeable { IParaxL1MessageHandler internal immutable l1MsgHander; - IDelegateRegistry delegationRegistry; + IDelegateRegistry internal immutable delegationRegistry; mapping(address => bool) supportAsset; - constructor(IParaxL1MessageHandler msgHandler) { + constructor( + IParaxL1MessageHandler msgHandler, + address _delegationRegistry + ) { l1MsgHander = msgHandler; + delegationRegistry = IDelegateRegistry(_delegationRegistry); } modifier onlyMsgHandler() { diff --git a/contracts/cross-chain/L1/ParaxL1MessageHandler.sol b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol index 15825d338..f7b622134 100644 --- a/contracts/cross-chain/L1/ParaxL1MessageHandler.sol +++ b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol @@ -33,17 +33,17 @@ contract ParaxL1MessageHandler { function bridgeReceive(BridgeMessage calldata message) external onlyBridge { if (message.msgType == MessageType.BridgeERC721) { - BridgeERC721Message memory message = abi.decode( + BridgeERC721Message memory erc721Message = abi.decode( message.data, (BridgeERC721Message) ); - nftVault.releaseNFT(message); + nftVault.releaseNFT(erc721Message); } else if (message.msgType == MessageType.ERC721DELEGATION) { - ERC721DelegationMessage memory message = abi.decode( + ERC721DelegationMessage memory delegationMsg = abi.decode( message.data, (ERC721DelegationMessage) ); - nftVault.updateTokenDelegation(message); + nftVault.updateTokenDelegation(delegationMsg); } } } diff --git a/contracts/cross-chain/L1/VaultApeStaking.sol b/contracts/cross-chain/L1/VaultApeStaking.sol new file mode 100644 index 000000000..ddefe804e --- /dev/null +++ b/contracts/cross-chain/L1/VaultApeStaking.sol @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; +import "../../dependencies/openzeppelin/contracts/ReentrancyGuard.sol"; +import "../../dependencies/openzeppelin/contracts//Pausable.sol"; +import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; +import {WadRayMath} from "../../protocol/libraries/math/WadRayMath.sol"; +import {PercentageMath} from "../../protocol/libraries/math/PercentageMath.sol"; +import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; +import "../../interfaces/IACLManager.sol"; +import "../../interfaces/ICApe.sol"; +import "./IParaxL1MessageHandler.sol"; +import "./IVaultApeStaking.sol"; + +contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { + using WadRayMath for uint256; + using SafeCast for uint256; + using PercentageMath for uint256; + + bytes32 constant APE_STAKING_STORAGE_POSITION = + bytes32( + uint256(keccak256("vault.apestaking.implementation.storage")) - 1 + ); + + struct ApeStakingStorage { + mapping(address => PoolState) poolStates; + uint128 accuCompoundFee; + uint32 compoundFeeRate; + address apeStakingBot; + } + + address internal immutable bayc; + address internal immutable mayc; + address internal immutable bakc; + address internal immutable apeCoin; + ICApe internal immutable cApe; + ApeCoinStaking internal immutable apeCoinStaking; + uint256 private immutable baycMatchedCap; + uint256 private immutable maycMatchedCap; + uint256 private immutable bakcMatchedCap; + IACLManager private immutable aclManager; + IParaxL1MessageHandler internal immutable l1MsgHander; + + constructor( + address _bayc, + address _mayc, + address _bakc, + address _apeCoin, + address _cApe, + address _apeCoinStaking, + address _aclManager, + IParaxL1MessageHandler _msgHandler + ) { + bayc = _bayc; + mayc = _mayc; + bakc = _bakc; + apeCoin = _apeCoin; + cApe = ICApe(_cApe); + apeCoinStaking = ApeCoinStaking(_apeCoinStaking); + aclManager = IACLManager(_aclManager); + l1MsgHander = _msgHandler; + + ( + , + 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; + } + + /// @inheritdoc IVaultApeStaking + function stakingApe( + bool isBAYC, + uint32[] calldata tokenIds + ) external override whenNotPaused nonReentrant { + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, Errors.INVALID_PARAMETER); + + address nft = isBAYC ? bayc : mayc; + uint256 positionCap = isBAYC ? baycMatchedCap : maycMatchedCap; + PoolState storage poolState = apeStakingStorage().poolStates[nft]; + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; + require( + tokenStatus.beneficiary != address(0), + Errors.NFT_NOT_IN_POOL + ); + require(!tokenStatus.isStaking, Errors.ALREADY_STAKING); + + // construct staking data + _nfts[index] = ApeCoinStaking.SingleNft({ + tokenId: tokenId, + amount: positionCap.toUint224() + }); + + tokenStatus.isStaking = true; + poolState.tokenStatus[tokenId] = tokenStatus; + + //emit event + emit ApeStaked(isBAYC, tokenId); + } + + // prepare Ape coin + uint256 totalBorrow = positionCap * arrayLength; + cApe.borrowApeCoin(totalBorrow); + + //stake in ApeCoinStaking + if (isBAYC) { + apeCoinStaking.depositBAYC(_nfts); + } else { + apeCoinStaking.depositMAYC(_nfts); + } + + poolState.stakingPosition += arrayLength.toUint24(); + } + + /// @inheritdoc IVaultApeStaking + function stakingBAKC( + BAKCPairActionInfo calldata actionInfo + ) external override whenNotPaused nonReentrant { + ( + uint256 baycArrayLength, + uint256 maycArrayLength + ) = _validateBAKCPairActionInfo(actionInfo); + + ApeCoinStaking.PairNftDepositWithAmount[] + memory _baycPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + baycArrayLength + ); + ApeCoinStaking.PairNftDepositWithAmount[] + memory _maycPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( + maycArrayLength + ); + + PoolState storage bakcPoolState = apeStakingStorage().poolStates[bakc]; + PoolState storage baycPoolState = apeStakingStorage().poolStates[bayc]; + for (uint256 index = 0; index < baycArrayLength; index++) { + uint32 apeTokenId = actionInfo.baycTokenIds[index]; + uint32 bakcTokenId = actionInfo.bakcPairBaycTokenIds[index]; + + TokenStatus memory baycTokenStatus = baycPoolState.tokenStatus[ + apeTokenId + ]; + TokenStatus memory bakcTokenStatus = bakcPoolState.tokenStatus[ + bakcTokenId + ]; + require( + baycTokenStatus.beneficiary != address(0), + Errors.NFT_NOT_IN_POOL + ); + require(!baycTokenStatus.isPairedStaking, Errors.ALREADY_STAKING); + require( + bakcTokenStatus.beneficiary != address(0), + Errors.NFT_NOT_IN_POOL + ); + require(!bakcTokenStatus.isStaking, Errors.ALREADY_STAKING); + + baycTokenStatus.isPairedStaking = true; + baycTokenStatus.pairTokenId = bakcTokenId; + bakcTokenStatus.isStaking = true; + bakcTokenStatus.pairTokenId = apeTokenId; + bakcTokenStatus.isPairedWithBayc = true; + baycPoolState.tokenStatus[apeTokenId] = baycTokenStatus; + bakcPoolState.tokenStatus[bakcTokenId] = bakcTokenStatus; + + // construct staking data + _baycPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: bakcMatchedCap.toUint184() + }); + + //emit event + emit BakcStaked(true, apeTokenId, bakcTokenId); + } + + PoolState storage maycPoolState = apeStakingStorage().poolStates[mayc]; + for (uint256 index = 0; index < maycArrayLength; index++) { + uint32 apeTokenId = actionInfo.maycTokenIds[index]; + uint32 bakcTokenId = actionInfo.bakcPairMaycTokenIds[index]; + + TokenStatus memory maycTokenStatus = maycPoolState.tokenStatus[ + apeTokenId + ]; + TokenStatus memory bakcTokenStatus = bakcPoolState.tokenStatus[ + bakcTokenId + ]; + require( + maycTokenStatus.beneficiary != address(0), + Errors.NFT_NOT_IN_POOL + ); + require(!maycTokenStatus.isPairedStaking, Errors.ALREADY_STAKING); + require( + bakcTokenStatus.beneficiary != address(0), + Errors.NFT_NOT_IN_POOL + ); + require(!bakcTokenStatus.isStaking, Errors.ALREADY_STAKING); + + maycTokenStatus.isPairedStaking = true; + maycTokenStatus.pairTokenId = bakcTokenId; + bakcTokenStatus.isStaking = true; + bakcTokenStatus.pairTokenId = apeTokenId; + bakcTokenStatus.isPairedWithBayc = false; + maycPoolState.tokenStatus[apeTokenId] = maycTokenStatus; + bakcPoolState.tokenStatus[bakcTokenId] = bakcTokenStatus; + + // construct staking data + _maycPairs[index] = ApeCoinStaking.PairNftDepositWithAmount({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId, + amount: bakcMatchedCap.toUint184() + }); + + //emit event + emit BakcStaked(false, apeTokenId, bakcTokenId); + } + + // prepare Ape coin + uint256 totalBorrow = bakcMatchedCap * + (baycArrayLength + maycArrayLength); + cApe.borrowApeCoin(totalBorrow); + + //stake in ApeCoinStaking + apeCoinStaking.depositBAKC(_baycPairs, _maycPairs); + + //update bakc pool state + bakcPoolState.stakingPosition += (baycArrayLength + maycArrayLength) + .toUint24(); + } + + /// @inheritdoc IVaultApeStaking + function compoundApe( + bool isBAYC, + uint32[] calldata tokenIds + ) external override { + ApeStakingStorage storage ds = apeStakingStorage(); + require(ds.apeStakingBot == msg.sender, Errors.NOT_APE_STAKING_BOT); + + uint256 arrayLength = tokenIds.length; + require(arrayLength > 0, Errors.INVALID_PARAMETER); + + uint256[] memory _nfts = new uint256[](arrayLength); + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + // construct staking data + _nfts[index] = tokenId; + + //emit event + emit ApeCompounded(isBAYC, tokenId); + } + + uint256 balanceBefore = IERC20(apeCoin).balanceOf(address(this)); + if (isBAYC) { + apeCoinStaking.claimSelfBAYC(_nfts); + } else { + apeCoinStaking.claimSelfMAYC(_nfts); + } + uint256 balanceAfter = IERC20(apeCoin).balanceOf(address(this)); + uint256 totalClaimedApe = balanceAfter - balanceBefore; + + address nft = isBAYC ? bayc : mayc; + _distributeIncome(nft, totalClaimedApe); + } + + /// @inheritdoc IVaultApeStaking + function compoundBAKC( + BAKCPairActionInfo calldata actionInfo + ) external override { + ApeStakingStorage storage ds = apeStakingStorage(); + require(ds.apeStakingBot == msg.sender, Errors.NOT_APE_STAKING_BOT); + + ( + uint256 baycArrayLength, + uint256 maycArrayLength + ) = _validateBAKCPairActionInfo(actionInfo); + + ApeCoinStaking.PairNft[] + memory _baycPairs = new ApeCoinStaking.PairNft[](baycArrayLength); + ApeCoinStaking.PairNft[] + memory _maycPairs = new ApeCoinStaking.PairNft[](maycArrayLength); + for (uint256 index = 0; index < baycArrayLength; index++) { + uint32 apeTokenId = actionInfo.baycTokenIds[index]; + uint32 bakcTokenId = actionInfo.bakcPairBaycTokenIds[index]; + + // construct staking data + _baycPairs[index] = ApeCoinStaking.PairNft({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId + }); + + //emit event + emit BakcCompounded(true, apeTokenId, bakcTokenId); + } + + for (uint256 index = 0; index < maycArrayLength; index++) { + uint32 apeTokenId = actionInfo.maycTokenIds[index]; + uint32 bakcTokenId = actionInfo.bakcPairMaycTokenIds[index]; + + // construct staking data + _maycPairs[index] = ApeCoinStaking.PairNft({ + mainTokenId: apeTokenId, + bakcTokenId: bakcTokenId + }); + + //emit event + emit BakcCompounded(false, apeTokenId, bakcTokenId); + } + + uint256 balanceBefore = IERC20(apeCoin).balanceOf(address(this)); + apeCoinStaking.claimSelfBAKC(_baycPairs, _maycPairs); + uint256 balanceAfter = IERC20(apeCoin).balanceOf(address(this)); + uint256 totalClaimedApe = balanceAfter - balanceBefore; + + _distributeIncome(bakc, totalClaimedApe); + } + + /// @inheritdoc IVaultApeStaking + function checkApeStakingPosition( + address nft, + uint32 tokenId + ) external override { + require(msg.sender == address(this), Errors.INVALID_CALLER); + + PoolState storage poolState = apeStakingStorage().poolStates[nft]; + if (poolState.totalPosition > 0) { + if (poolState.stakingPosition > 0) { + TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; + if (nft == bakc) { + if (tokenStatus.isStaking) { + uint32[] memory tokenIds = new uint32[](1); + tokenIds[0] = tokenStatus.pairTokenId; + _unstakeApe(tokenStatus.isPairedWithBayc, tokenIds); + } + } else { + if (tokenStatus.isStaking || tokenStatus.isPairedStaking) { + bool isBAYC = (nft == bayc); + uint32[] memory tokenIds = new uint32[](1); + tokenIds[0] = tokenId; + _unstakeApe(isBAYC, tokenIds); + } + } + } + + //claim pending reward + uint256 cApeExchangeRate = cApe.getPooledApeByShares( + WadRayMath.RAY + ); + uint256 rewardShare = _claimPendingReward( + poolState.tokenStatus[tokenId], + poolState.accumulatedRewardsPerNft, + nft, + tokenId, + cApeExchangeRate + ); + if (rewardShare > 0) { + uint256 pendingReward = rewardShare.rayMul(cApeExchangeRate); + cApe.transfer( + poolState.tokenStatus[tokenId].beneficiary, + pendingReward + ); + } + + poolState.totalPosition -= 1; + delete poolState.tokenStatus[tokenId]; + } + } + + /// @inheritdoc IVaultApeStaking + function unstakeApe(bool isBAYC, uint32[] calldata tokenIds) external { + require( + apeStakingStorage().apeStakingBot == msg.sender, + Errors.NOT_APE_STAKING_BOT + ); + + require(tokenIds.length > 0, Errors.INVALID_PARAMETER); + _unstakeApe(isBAYC, tokenIds); + } + + function _unstakeApe(bool isBAYC, uint32[] memory tokenIds) internal { + address nft = isBAYC ? bayc : mayc; + uint256 positionCap = isBAYC ? baycMatchedCap : maycMatchedCap; + PoolState storage poolState = apeStakingStorage().poolStates[nft]; + + uint256 arrayLength = tokenIds.length; + ApeCoinStaking.SingleNft[] + memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + arrayLength + ); + uint24 singleStakingCount; + uint24 pairStakingCount; + for (uint256 index = 0; index < tokenIds.length; index++) { + uint32 tokenId = tokenIds[index]; + + TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; + if (tokenStatus.isStaking) { + _nfts[singleStakingCount] = ApeCoinStaking.SingleNft({ + tokenId: tokenId, + amount: positionCap.toUint224() + }); + singleStakingCount++; + } + + if (tokenStatus.isPairedStaking) { + _nftPairs[pairStakingCount] = ApeCoinStaking + .PairNftWithdrawWithAmount({ + mainTokenId: tokenId, + bakcTokenId: tokenStatus.pairTokenId, + amount: bakcMatchedCap.toUint184(), + isUncommit: true + }); + pairStakingCount++; + } + } + + if (singleStakingCount > 0) { + assembly { + mstore(_nfts, singleStakingCount) + } + uint256 balanceBefore = IERC20(apeCoin).balanceOf(address(this)); + if (isBAYC) { + apeCoinStaking.withdrawBAYC(_nfts, address(this)); + } else { + apeCoinStaking.withdrawMAYC(_nfts, address(this)); + } + uint256 balanceAfter = IERC20(apeCoin).balanceOf(address(this)); + uint256 totalClaimedApe = balanceAfter - balanceBefore; + + uint256 principle = positionCap * singleStakingCount; + cApe.repayApeCoin(principle); + uint256 income = totalClaimedApe - principle; + _distributeIncome(nft, income); + + poolState.stakingPosition -= singleStakingCount; + } + + if (pairStakingCount > 0) { + assembly { + mstore(_nftPairs, pairStakingCount) + } + uint256 balanceBefore = IERC20(apeCoin).balanceOf(address(this)); + ApeCoinStaking.PairNftWithdrawWithAmount[] + memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( + 0 + ); + if (isBAYC) { + apeCoinStaking.withdrawBAKC(_nftPairs, _otherPairs); + } else { + apeCoinStaking.withdrawBAKC(_otherPairs, _nftPairs); + } + uint256 balanceAfter = IERC20(apeCoin).balanceOf(address(this)); + uint256 totalClaimedApe = balanceAfter - balanceBefore; + + uint256 principle = bakcMatchedCap * pairStakingCount; + cApe.repayApeCoin(principle); + uint256 income = totalClaimedApe - principle; + _distributeIncome(bakc, income); + + PoolState storage bakcPoolState = apeStakingStorage().poolStates[ + bakc + ]; + bakcPoolState.stakingPosition -= pairStakingCount; + } + } + + function _distributeIncome(address nft, uint256 totalClaimedApe) internal { + ApeStakingStorage storage ds = apeStakingStorage(); + PoolState storage poolState = ds.poolStates[nft]; + //first part compound fee + uint256 compoundFee = totalClaimedApe.percentMul(ds.compoundFeeRate); + //second part repay cape + uint256 cApeIncome = (totalClaimedApe - compoundFee).percentMul( + poolState.cApeIncomeRatio + ); + //third ape pool income + uint256 poolIncome = totalClaimedApe - compoundFee - cApeIncome; + + cApe.notifyReward(cApeIncome); + cApe.deposit(address(this), compoundFee + poolIncome); + uint256 cApeExchangeRate = cApe.getPooledApeByShares(WadRayMath.RAY); + poolState.accumulatedRewardsPerNft += (poolIncome.rayDiv( + cApeExchangeRate + ) / poolState.totalPosition).toUint128(); + ds.accuCompoundFee += (compoundFee.rayDiv(cApeExchangeRate)) + .toUint128(); + } + + function _validateBAKCPairActionInfo( + BAKCPairActionInfo calldata actionInfo + ) internal pure returns (uint256 baycArrayLength, uint256 maycArrayLength) { + baycArrayLength = actionInfo.baycTokenIds.length; + maycArrayLength = actionInfo.maycTokenIds.length; + require( + baycArrayLength == actionInfo.bakcPairBaycTokenIds.length, + Errors.INVALID_PARAMETER + ); + require( + maycArrayLength == actionInfo.bakcPairMaycTokenIds.length, + Errors.INVALID_PARAMETER + ); + require( + baycArrayLength > 0 || maycArrayLength > 0, + Errors.INVALID_PARAMETER + ); + } + + /// @inheritdoc IVaultApeStaking + function getTokenStatus( + address nft, + uint256 tokenId + ) external view returns (TokenStatus memory) { + ApeStakingStorage storage ds = apeStakingStorage(); + return ds.poolStates[nft].tokenStatus[tokenId]; + } + + /// @inheritdoc IVaultApeStaking + function getPendingReward( + address nft, + uint32[] calldata tokenIds + ) external view returns (uint256) { + uint256 rewardShares; + uint256 arrayLength = tokenIds.length; + + PoolState storage poolState = apeStakingStorage().poolStates[nft]; + uint256 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; + require( + tokenStatus.beneficiary != address(0), + Errors.NFT_NOT_IN_POOL + ); + + rewardShares += (accumulatedRewardsPerNft - + tokenStatus.rewardsDebt); + } + return ICApe(cApe).getPooledApeByShares(rewardShares); + } + + /// @inheritdoc IVaultApeStaking + function claimPendingReward( + address nft, + uint32[] calldata tokenIds + ) external whenNotPaused nonReentrant { + uint256 totalRewardShares; + uint256 cApeExchangeRate = cApe.getPooledApeByShares(WadRayMath.RAY); + + PoolState storage poolState = apeStakingStorage().poolStates[nft]; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + uint256 arrayLength = tokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; + require( + msg.sender == tokenStatus.beneficiary, + Errors.INVALID_CALLER + ); + totalRewardShares += _claimPendingReward( + poolState.tokenStatus[tokenId], + accumulatedRewardsPerNft, + nft, + tokenId, + cApeExchangeRate + ); + } + + if (totalRewardShares > 0) { + uint256 pendingReward = totalRewardShares.rayMul(cApeExchangeRate); + cApe.transfer(msg.sender, pendingReward); + } + } + + function _claimPendingReward( + TokenStatus storage tokenStatus, + uint256 accumulatedRewardsPerNft, + address nft, + uint256 tokenId, + uint256 cApeExchangeRate + ) internal returns (uint256 rewardShare) { + rewardShare = accumulatedRewardsPerNft - tokenStatus.rewardsDebt; + tokenStatus.rewardsDebt = accumulatedRewardsPerNft.toUint128(); + + //emit event + emit PoolRewardClaimed( + nft, + tokenId, + rewardShare.rayMul(cApeExchangeRate) + ); + } + + function setApeStakingBot(address _apeStakingBot) external onlyPoolAdmin { + ApeStakingStorage storage ds = apeStakingStorage(); + address oldValue = ds.apeStakingBot; + if (oldValue != _apeStakingBot) { + ds.apeStakingBot = _apeStakingBot; + emit ApeStakingBotUpdated(oldValue, _apeStakingBot); + } + } + + function setCompoundFeeRate( + uint32 _compoundFeeRate + ) external onlyPoolAdmin { + //0.1e4 means 10% + require(_compoundFeeRate <= 0.1e4, Errors.INVALID_PARAMETER); + ApeStakingStorage storage ds = apeStakingStorage(); + uint32 oldValue = ds.compoundFeeRate; + if (oldValue != _compoundFeeRate) { + ds.compoundFeeRate = _compoundFeeRate; + emit CompoundFeeRateUpdated(oldValue, _compoundFeeRate); + } + } + + function claimCompoundFee(address receiver) external { + ApeStakingStorage storage ds = apeStakingStorage(); + require(ds.apeStakingBot == msg.sender, Errors.NOT_APE_STAKING_BOT); + uint256 fee = ds.accuCompoundFee; + if (fee > 0) { + uint256 amount = cApe.getPooledApeByShares(fee); + cApe.transfer(receiver, amount); + ds.accuCompoundFee = 0; + + emit CompoundFeeClaimed(amount); + } + } + + function updateBeneficiary( + address nft, + uint32[] calldata tokenIds, + address newBenificiary + ) external onlyMsgHandler { + uint256 cApeExchangeRate = cApe.getPooledApeByShares(WadRayMath.RAY); + + PoolState storage poolState = apeStakingStorage().poolStates[nft]; + uint128 accumulatedRewardsPerNft = poolState.accumulatedRewardsPerNft; + uint256 arrayLength = tokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; + uint256 rewardShare = accumulatedRewardsPerNft - + tokenStatus.rewardsDebt; + if (rewardShare > 0) { + tokenStatus.rewardsDebt = accumulatedRewardsPerNft; + uint256 pendingReward = rewardShare.rayMul(cApeExchangeRate); + cApe.transfer(tokenStatus.beneficiary, pendingReward); + + //emit event + emit PoolRewardClaimed( + nft, + tokenId, + rewardShare.rayMul(cApeExchangeRate) + ); + } + tokenStatus.beneficiary = newBenificiary; + poolState.tokenStatus[tokenId] = tokenStatus; + + emit BeneficiaryUpdated(nft, tokenId, newBenificiary); + } + } + + /** + * @notice Pauses the contract. Only pool admin or emergency admin can call this function + **/ + function pause() external onlyEmergencyOrPoolAdmin { + _pause(); + } + + /** + * @notice Unpause the contract. Only pool admin can call this function + **/ + function unpause() external onlyPoolAdmin { + _unpause(); + } + + modifier onlyMsgHandler() { + require(msg.sender == address(l1MsgHander), Errors.ONLY_MSG_HANDLER); + _; + } + + /** + * @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 + ); + } + + function apeStakingStorage() + internal + pure + returns (ApeStakingStorage storage ds) + { + bytes32 position = APE_STAKING_STORAGE_POSITION; + assembly { + ds.slot := position + } + } +} diff --git a/contracts/cross-chain/L2/BridgeERC721Handler.sol b/contracts/cross-chain/L2/BridgeERC721Handler.sol index 81fdc81bf..3f2627b64 100644 --- a/contracts/cross-chain/L2/BridgeERC721Handler.sol +++ b/contracts/cross-chain/L2/BridgeERC721Handler.sol @@ -26,9 +26,9 @@ contract BridgeERC21Handler { function bridgeAsset( BridgeERC721Message calldata message ) external onlyMsgHandler { - address bridgeAsset = getBridgeAsset[message.asset]; - require(bridgeAsset != address(0), "invalid"); + address asset = getBridgeAsset[message.asset]; + require(asset != address(0), "invalid"); - IBridgeERC721(bridgeAsset).mint(message.receiver, message.tokenIds); + IBridgeERC721(asset).mint(message.receiver, message.tokenIds); } } diff --git a/contracts/cross-chain/L2/ParaxL2MessageHandler.sol b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol index 216c74657..bd41aa215 100644 --- a/contracts/cross-chain/L2/ParaxL2MessageHandler.sol +++ b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol @@ -17,11 +17,11 @@ contract ParaxL2MessageHandler is IParaxL2MessageHandler { function bridgeReceive(BridgeMessage calldata message) external { require(msg.sender == bridgeImpl, ""); if (message.msgType == MessageType.BridgeERC721) { - BridgeERC721Message memory message = abi.decode( + BridgeERC721Message memory erc721Message = abi.decode( message.data, (BridgeERC721Message) ); - erc712Handler.bridgeAsset(message); + erc712Handler.bridgeAsset(erc721Message); } else {} } diff --git a/contracts/dependencies/openzeppelin/contracts/SafeCast.sol b/contracts/dependencies/openzeppelin/contracts/SafeCast.sol index 41cd26986..a8c6acdc7 100644 --- a/contracts/dependencies/openzeppelin/contracts/SafeCast.sol +++ b/contracts/dependencies/openzeppelin/contracts/SafeCast.sol @@ -142,6 +142,23 @@ library SafeCast { return uint32(value); } + /** + * @dev Returns the downcasted uint24 from uint256, reverting on + * overflow (when the input is greater than largest uint24). + * + * Counterpart to Solidity's `uint24` operator. + * + * Requirements: + * + * - input must fit into 24 bits + * + * _Available since v4.7._ + */ + function toUint24(uint256 value) internal pure returns (uint24) { + require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits"); + return uint24(value); + } + /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). diff --git a/contracts/interfaces/ICApe.sol b/contracts/interfaces/ICApe.sol index e33174dc3..40c9b6ddd 100644 --- a/contracts/interfaces/ICApe.sol +++ b/contracts/interfaces/ICApe.sol @@ -22,4 +22,65 @@ interface ICApe is IERC20 { * @return the amount of shares belongs to _account. */ function sharesOf(address _account) external view returns (uint256); + + function borrowApeCoin(uint256 amount) external; + + function repayApeCoin(uint256 amount) external; + + function notifyReward(uint256 amount) external; + + /** + * @dev Emitted during deposit() + * @param user The address of the user deposit for + * @param amountDeposited The amount being deposit + * @param amountShare The share being deposit + **/ + event Deposit( + address indexed caller, + address indexed user, + uint256 amountDeposited, + uint256 amountShare + ); + + /** + * @dev Emitted during withdraw() + * @param user The address of the user + * @param amountWithdraw The amount being withdraw + * @param amountShare The share being withdraw + **/ + event Redeem( + address indexed user, + uint256 amountWithdraw, + uint256 amountShare + ); + + /** + * @dev Emitted during rescueERC20() + * @param token The address of the token + * @param to The address of the recipient + * @param amount The amount being rescued + **/ + event RescueERC20( + address indexed token, + address indexed to, + uint256 amount + ); + + /** + * @notice deposit an `amount` of ape into compound pool. + * @param onBehalf The address of user will receive the pool share + * @param amount The amount of ape to be deposit + **/ + function deposit(address onBehalf, uint256 amount) external; + + /** + * @notice withdraw an `amount` of ape from compound pool. + * @param amount The amount of ape to be withdraw + **/ + function withdraw(uint256 amount) external; + + /** + * @notice collect ape reward in ApeCoinStaking and deposit to earn compound interest. + **/ + function harvestAndCompound() external; } diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index 55f86e966..97cde563f 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -135,11 +135,14 @@ library Errors { string public constant TOKEN_NOT_ALLOW_RESCUE = "140"; // token is not allow rescue string public constant INVALID_PARAMETER = "170"; //invalid parameter - string public constant INVALID_CALLER = "171"; //invalid callser - string public constant ONLY_MSG_HANDLER = "200"; //only msg handler - string public constant ONLY_VAULT = "201"; //only vault - string public constant ONLY_HANDLER = "202"; //only handler - string public constant ONLY_PARAX = "203"; //only parax - string public constant ONLY_BRIDGE = "204"; //only cross-chain bridge + string public constant INVALID_CALLER = "200"; //invalid caller + string public constant ONLY_MSG_HANDLER = "201"; //only msg handler + string public constant ONLY_VAULT = "202"; //only vault + string public constant ONLY_HANDLER = "203"; //only handler + string public constant ONLY_PARAX = "204"; //only parax + string public constant ONLY_BRIDGE = "205"; //only cross-chain bridge + string public constant NOT_APE_STAKING_BOT = "206"; //only ape staking bot + string public constant NFT_NOT_IN_POOL = "207"; //nft not in the pool + string public constant ALREADY_STAKING = "208"; //already staking } diff --git a/contracts/protocol/tokenization/NTokenApeStaking.sol b/contracts/protocol/tokenization/NTokenApeStaking.sol deleted file mode 100644 index 55dbaae97..000000000 --- a/contracts/protocol/tokenization/NTokenApeStaking.sol +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {NToken} from "./NToken.sol"; -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; -import {IPool} from "../../interfaces/IPool.sol"; -import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; -import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; -import {IRewardController} from "../../interfaces/IRewardController.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; -import "../../interfaces/INTokenApeStaking.sol"; -import {DataTypes} from "../libraries/types/DataTypes.sol"; - -/** - * @title ApeCoinStaking NToken - * - * @notice Implementation of the NToken for the ParaSpace protocol - */ -abstract contract NTokenApeStaking is NToken, INTokenApeStaking { - ApeCoinStaking immutable _apeCoinStaking; - - bytes32 constant APE_STAKING_DATA_STORAGE_POSITION = - bytes32( - uint256(keccak256("paraspace.proxy.ntoken.apestaking.storage")) - 1 - ); - - /** - * @dev Default percentage of borrower's ape position to be repaid as incentive in a unstaking transaction. - * @dev Percentage applied when the users ape position got unstaked by others. - * Expressed in bps, a value of 30 results in 0.3% - */ - uint256 internal constant DEFAULT_UNSTAKE_INCENTIVE_PERCENTAGE = 30; - - /** - * @dev Constructor. - * @param pool The address of the Pool contract - */ - constructor(IPool pool, address apeCoinStaking) NToken(pool, false) { - _apeCoinStaking = ApeCoinStaking(apeCoinStaking); - } - - function initialize( - IPool initializingPool, - address underlyingAsset, - IRewardController incentivesController, - string calldata nTokenName, - string calldata nTokenSymbol, - bytes calldata params - ) public virtual override initializer { - IERC20 _apeCoin = _apeCoinStaking.apeCoin(); - //approve for apeCoinStaking - uint256 allowance = IERC20(_apeCoin).allowance( - address(this), - address(_apeCoinStaking) - ); - if (allowance == 0) { - IERC20(_apeCoin).approve( - address(_apeCoinStaking), - type(uint256).max - ); - } - //approve for Pool contract - allowance = IERC20(_apeCoin).allowance(address(this), address(POOL)); - if (allowance == 0) { - IERC20(_apeCoin).approve(address(POOL), type(uint256).max); - } - getBAKC().setApprovalForAll(address(POOL), true); - - super.initialize( - initializingPool, - underlyingAsset, - incentivesController, - nTokenName, - nTokenSymbol, - params - ); - - initializeStakingData(); - } - - /** - * @notice Returns the address of BAKC contract address. - **/ - function getBAKC() public view returns (IERC721) { - return _apeCoinStaking.nftContracts(ApeStakingLogic.BAKC_POOL_ID); - } - - /** - * @notice Returns the address of ApeCoinStaking contract address. - **/ - function getApeStaking() external view returns (ApeCoinStaking) { - return _apeCoinStaking; - } - - /** - * @notice Overrides the _transfer from NToken to withdraw all staked and pending rewards before transfer the asset - */ - function _transfer( - address from, - address to, - uint256 tokenId, - bool validate - ) internal override { - ApeStakingLogic.executeUnstakePositionAndRepay( - _ERC721Data.owners, - apeStakingDataStorage(), - ApeStakingLogic.UnstakeAndRepayParams({ - POOL: POOL, - _apeCoinStaking: _apeCoinStaking, - _underlyingAsset: _ERC721Data.underlyingAsset, - poolId: POOL_ID(), - tokenId: tokenId, - incentiveReceiver: address(0), - bakcNToken: getBAKCNTokenAddress() - }) - ); - super._transfer(from, to, tokenId, validate); - } - - /** - * @notice Overrides the burn from NToken to withdraw all staked and pending rewards before burning the NToken on liquidation/withdraw - */ - function burn( - address from, - address receiverOfUnderlying, - uint256[] calldata tokenIds, - DataTypes.TimeLockParams calldata timeLockParams - ) external virtual override onlyPool nonReentrant returns (uint64, uint64) { - for (uint256 index = 0; index < tokenIds.length; index++) { - ApeStakingLogic.executeUnstakePositionAndRepay( - _ERC721Data.owners, - apeStakingDataStorage(), - ApeStakingLogic.UnstakeAndRepayParams({ - POOL: POOL, - _apeCoinStaking: _apeCoinStaking, - _underlyingAsset: _ERC721Data.underlyingAsset, - poolId: POOL_ID(), - tokenId: tokenIds[index], - incentiveReceiver: address(0), - bakcNToken: getBAKCNTokenAddress() - }) - ); - } - - return _burn(from, receiverOfUnderlying, tokenIds, timeLockParams); - } - - function POOL_ID() internal pure virtual returns (uint256) { - // should be overridden - return 0; - } - - function initializeStakingData() internal { - ApeStakingLogic.APEStakingParameter - storage dataStorage = apeStakingDataStorage(); - ApeStakingLogic.executeSetUnstakeApeIncentive( - dataStorage, - DEFAULT_UNSTAKE_INCENTIVE_PERCENTAGE - ); - } - - function setUnstakeApeIncentive(uint256 incentive) external onlyPoolAdmin { - ApeStakingLogic.executeSetUnstakeApeIncentive( - apeStakingDataStorage(), - incentive - ); - } - - function apeStakingDataStorage() - internal - pure - returns (ApeStakingLogic.APEStakingParameter storage rgs) - { - bytes32 position = APE_STAKING_DATA_STORAGE_POSITION; - assembly { - rgs.slot := position - } - } - - /** - * @notice Unstake Ape coin staking position and repay user debt - * @param tokenId Token id of the ape staking position on - * @param incentiveReceiver address to receive incentive - */ - function unstakePositionAndRepay( - uint256 tokenId, - address incentiveReceiver - ) external nonReentrant { - address bakcNToken = getBAKCNTokenAddress(); - require( - msg.sender == address(POOL) || msg.sender == bakcNToken, - "Invalid Caller" - ); - ApeStakingLogic.executeUnstakePositionAndRepay( - _ERC721Data.owners, - apeStakingDataStorage(), - ApeStakingLogic.UnstakeAndRepayParams({ - POOL: POOL, - _apeCoinStaking: _apeCoinStaking, - _underlyingAsset: _ERC721Data.underlyingAsset, - poolId: POOL_ID(), - tokenId: tokenId, - incentiveReceiver: incentiveReceiver, - bakcNToken: bakcNToken - }) - ); - } - - /** - * @notice get user total ape staking position - * @param user user address - */ - function getUserApeStakingAmount( - address user - ) external view returns (uint256) { - return - ApeStakingLogic.getUserTotalStakingAmount( - _ERC721Data.userState, - _ERC721Data.ownedTokens, - _ERC721Data.underlyingAsset, - user, - POOL_ID(), - _apeCoinStaking - ); - } - - function getBAKCNTokenAddress() internal view returns (address) { - return POOL.getReserveData(address(getBAKC())).xTokenAddress; - } -} diff --git a/contracts/protocol/tokenization/NTokenBAKC.sol b/contracts/protocol/tokenization/NTokenBAKC.sol deleted file mode 100644 index 18a31cff0..000000000 --- a/contracts/protocol/tokenization/NTokenBAKC.sol +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {NToken} from "./NToken.sol"; -import {IPool} from "../../interfaces/IPool.sol"; -import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; -import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; -import {Errors} from "../libraries/helpers/Errors.sol"; -import {XTokenType} from "../../interfaces/IXTokenType.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; -import {INTokenApeStaking} from "../../interfaces/INTokenApeStaking.sol"; -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; -import {INToken} from "../../interfaces/INToken.sol"; -import {IRewardController} from "../../interfaces/IRewardController.sol"; -import {DataTypes} from "../libraries/types/DataTypes.sol"; - -/** - * @title NTokenBAKC - * - * @notice Implementation of the NTokenBAKC for the ParaSpace protocol - */ -contract NTokenBAKC is NToken { - ApeCoinStaking immutable _apeCoinStaking; - address private immutable nBAYC; - address private immutable nMAYC; - - /** - * @dev Constructor. - * @param pool The address of the Pool contract - */ - constructor( - IPool pool, - address apeCoinStaking, - address _nBAYC, - address _nMAYC - ) NToken(pool, false) { - _apeCoinStaking = ApeCoinStaking(apeCoinStaking); - nBAYC = _nBAYC; - nMAYC = _nMAYC; - } - - function initialize( - IPool initializingPool, - address underlyingAsset, - IRewardController incentivesController, - string calldata nTokenName, - string calldata nTokenSymbol, - bytes calldata params - ) public virtual override initializer { - super.initialize( - initializingPool, - underlyingAsset, - incentivesController, - nTokenName, - nTokenSymbol, - params - ); - - IERC20 ape = _apeCoinStaking.apeCoin(); - //approve for nBAYC - uint256 allowance = ape.allowance(address(this), nBAYC); - if (allowance == 0) { - ape.approve(nBAYC, type(uint256).max); - } - //approve for Pool nMAYC - allowance = ape.allowance(address(this), nMAYC); - if (allowance == 0) { - ape.approve(nMAYC, type(uint256).max); - } - IERC721(underlyingAsset).setApprovalForAll(address(POOL), true); - } - - function _transfer( - address from, - address to, - uint256 tokenId, - bool validate - ) internal override { - _unStakePairedApePosition(tokenId); - super._transfer(from, to, tokenId, validate); - } - - /** - * @notice Overrides the burn from NToken to withdraw all staked and pending rewards before burning the NToken on liquidation/withdraw - */ - function burn( - address from, - address receiverOfUnderlying, - uint256[] calldata tokenIds, - DataTypes.TimeLockParams calldata timeLockParams - ) external virtual override onlyPool nonReentrant returns (uint64, uint64) { - if (from != receiverOfUnderlying) { - for (uint256 index = 0; index < tokenIds.length; index++) { - _unStakePairedApePosition(tokenIds[index]); - } - } - return _burn(from, receiverOfUnderlying, tokenIds, timeLockParams); - } - - function _unStakePairedApePosition(uint256 tokenId) internal { - //check if have ape pair position - (uint256 bakcStakedAmount, ) = _apeCoinStaking.nftPosition( - ApeStakingLogic.BAKC_POOL_ID, - tokenId - ); - if (bakcStakedAmount > 0) { - bool positionExisted = _tryUnstakeMainTokenPosition( - ApeStakingLogic.BAYC_POOL_ID, - nBAYC, - tokenId - ); - if (!positionExisted) { - _tryUnstakeMainTokenPosition( - ApeStakingLogic.MAYC_POOL_ID, - nMAYC, - tokenId - ); - } - } - } - - function _tryUnstakeMainTokenPosition( - uint256 poolId, - address nToken, - uint256 tokenId - ) internal returns (bool) { - (uint256 mainTokenId, bool positionExisted) = _apeCoinStaking - .bakcToMain(tokenId, poolId); - if (positionExisted) { - bool sameOwner = INToken(nToken).ownerOf(mainTokenId) == - ownerOf(tokenId); - if (sameOwner) { - INTokenApeStaking(nToken).unstakePositionAndRepay( - mainTokenId, - address(0) - ); - } - } - return positionExisted; - } - - function getXTokenType() external pure override returns (XTokenType) { - return XTokenType.NTokenBAKC; - } -} diff --git a/contracts/protocol/tokenization/NTokenBAYC.sol b/contracts/protocol/tokenization/NTokenBAYC.sol deleted file mode 100644 index c87ab35a0..000000000 --- a/contracts/protocol/tokenization/NTokenBAYC.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; -import {NTokenApeStaking} from "./NTokenApeStaking.sol"; -import {IPool} from "../../interfaces/IPool.sol"; -import {XTokenType} from "../../interfaces/IXTokenType.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; - -/** - * @title BAYC NToken - * - * @notice Implementation of the NToken for the ParaSpace protocol - */ -contract NTokenBAYC is NTokenApeStaking { - constructor( - IPool pool, - address apeCoinStaking - ) NTokenApeStaking(pool, apeCoinStaking) {} - - /** - * @notice Deposit ApeCoin to the BAYC Pool - * @param _nfts Array of SingleNft structs - * @dev Commits 1 or more BAYC NFTs, each with an ApeCoin amount to the BAYC pool.\ - * Each BAYC committed must attach an ApeCoin amount >= 1 ApeCoin and <= the BAYC pool cap amount. - */ - function depositApeCoin( - ApeCoinStaking.SingleNft[] calldata _nfts - ) external onlyPool nonReentrant { - _apeCoinStaking.depositBAYC(_nfts); - } - - /** - * @notice Claim rewards for array of BAYC NFTs and send to recipient - * @param _nfts Array of NFTs owned and committed by the msg.sender - * @param _recipient Address to send claim reward to - */ - function claimApeCoin( - uint256[] calldata _nfts, - address _recipient - ) external onlyPool nonReentrant { - _apeCoinStaking.claimBAYC(_nfts, _recipient); - } - - /** - * @notice Withdraw staked ApeCoin from the BAYC pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nfts Array of BAYC NFT's with staked amounts - * @param _recipient Address to send withdraw amount and claim to - */ - function withdrawApeCoin( - ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient - ) external onlyPool nonReentrant { - _apeCoinStaking.withdrawBAYC(_nfts, _recipient); - } - - /** - * @notice Deposit ApeCoin to the Pair Pool, where Pair = (BAYC + BAKC) - * @param _nftPairs Array of PairNftWithAmount structs - * @dev Commits 1 or more Pairs, each with an ApeCoin amount to the Pair pool.\ - * Each BAKC committed must attach an ApeCoin amount >= 1 ApeCoin and <= the Pair pool cap amount.\ - * Example: BAYC + BAKC + 1 ApeCoin: [[0, 0, "1000000000000000000"]]\ - */ - function depositBAKC( - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external onlyPool nonReentrant { - _apeCoinStaking.depositBAKC( - _nftPairs, - new ApeCoinStaking.PairNftDepositWithAmount[](0) - ); - } - - /** - * @notice Claim rewards for array of Paired NFTs and send to recipient - * @param _nftPairs Array of Paired BAYC/MAYC NFTs owned and committed by the msg.sender - * @param _recipient Address to send claim reward to - */ - function claimBAKC( - ApeCoinStaking.PairNft[] calldata _nftPairs, - address _recipient - ) external onlyPool nonReentrant { - _apeCoinStaking.claimBAKC( - _nftPairs, - new ApeCoinStaking.PairNft[](0), - _recipient - ); - } - - /** - * @notice Withdraw staked ApeCoin from the Pair pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nftPairs Array of Paired BAYC NFT's with staked amounts - * @dev if pairs have split ownership and BAKC is attempting a withdraw, the withdraw must be for the total staked amount - */ - function withdrawBAKC( - ApeCoinStaking.PairNftWithdrawWithAmount[] calldata _nftPairs, - address _apeRecipient - ) external onlyPool nonReentrant { - ApeStakingLogic.withdrawBAKC( - _apeCoinStaking, - POOL_ID(), - _nftPairs, - _apeRecipient - ); - } - - function POOL_ID() internal pure virtual override returns (uint256) { - return ApeStakingLogic.BAYC_POOL_ID; - } - - function getXTokenType() external pure override returns (XTokenType) { - return XTokenType.NTokenBAYC; - } -} diff --git a/contracts/protocol/tokenization/NTokenMAYC.sol b/contracts/protocol/tokenization/NTokenMAYC.sol deleted file mode 100644 index 5c4be8f08..000000000 --- a/contracts/protocol/tokenization/NTokenMAYC.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; -import {NTokenApeStaking} from "./NTokenApeStaking.sol"; -import {IPool} from "../../interfaces/IPool.sol"; -import {XTokenType} from "../../interfaces/IXTokenType.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; - -/** - * @title MAYC NToken - * - * @notice Implementation of the NToken for the ParaSpace protocol - */ -contract NTokenMAYC is NTokenApeStaking { - constructor( - IPool pool, - address apeCoinStaking - ) NTokenApeStaking(pool, apeCoinStaking) {} - - /** - * @notice Deposit ApeCoin to the MAYC Pool - * @param _nfts Array of SingleNft structs - * @dev Commits 1 or more MAYC NFTs, each with an ApeCoin amount to the MAYC pool.\ - * Each MAYC committed must attach an ApeCoin amount >= 1 ApeCoin and <= the MAYC pool cap amount. - */ - function depositApeCoin( - ApeCoinStaking.SingleNft[] calldata _nfts - ) external onlyPool nonReentrant { - _apeCoinStaking.depositMAYC(_nfts); - } - - /** - * @notice Claim rewards for array of MAYC NFTs and send to recipient - * @param _nfts Array of NFTs owned and committed by the msg.sender - * @param _recipient Address to send claim reward to - */ - function claimApeCoin( - uint256[] calldata _nfts, - address _recipient - ) external onlyPool nonReentrant { - _apeCoinStaking.claimMAYC(_nfts, _recipient); - } - - /** - * @notice Withdraw staked ApeCoin from the MAYC pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nfts Array of MAYC NFT's with staked amounts - * @param _recipient Address to send withdraw amount and claim to - */ - function withdrawApeCoin( - ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient - ) external onlyPool nonReentrant { - _apeCoinStaking.withdrawMAYC(_nfts, _recipient); - } - - /** - * @notice Deposit ApeCoin to the Pair Pool, where Pair = (MAYC + BAKC) - * @param _nftPairs Array of PairNftWithAmount structs - * @dev Commits 1 or more Pairs, each with an ApeCoin amount to the Pair pool.\ - * Each BAKC committed must attach an ApeCoin amount >= 1 ApeCoin and <= the Pair pool cap amount.\ - * Example: MAYC + BAKC + 1 ApeCoin: [[0, 0, "1000000000000000000"]]\ - */ - function depositBAKC( - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external onlyPool nonReentrant { - _apeCoinStaking.depositBAKC( - new ApeCoinStaking.PairNftDepositWithAmount[](0), - _nftPairs - ); - } - - /** - * @notice Claim rewards for array of Paired NFTs and send to recipient - * @param _nftPairs Array of Paired MAYC NFTs owned and committed by the msg.sender - * @param _recipient Address to send claim reward to - */ - function claimBAKC( - ApeCoinStaking.PairNft[] calldata _nftPairs, - address _recipient - ) external onlyPool nonReentrant { - _apeCoinStaking.claimBAKC( - new ApeCoinStaking.PairNft[](0), - _nftPairs, - _recipient - ); - } - - /** - * @notice Withdraw staked ApeCoin from the Pair pool. If withdraw is total staked amount, performs an automatic claim. - * @param _nftPairs Array of Paired MAYC NFT's with staked amounts - * @dev if pairs have split ownership and BAKC is attempting a withdraw, the withdraw must be for the total staked amount - */ - function withdrawBAKC( - ApeCoinStaking.PairNftWithdrawWithAmount[] calldata _nftPairs, - address _apeRecipient - ) external onlyPool nonReentrant { - ApeStakingLogic.withdrawBAKC( - _apeCoinStaking, - POOL_ID(), - _nftPairs, - _apeRecipient - ); - } - - function POOL_ID() internal pure virtual override returns (uint256) { - return ApeStakingLogic.MAYC_POOL_ID; - } - - function getXTokenType() external pure override returns (XTokenType) { - return XTokenType.NTokenMAYC; - } -} diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 5019ae3f1..cdbadb3ff 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -5,7 +5,6 @@ import { AccountRegistry, ACLManager, AirdropFlashClaimReceiver, - ApeStakingLogic, AStETHDebtToken, ATokenDebtToken, AuctionLogic, @@ -79,11 +78,7 @@ import { MutantApeYachtClub, NFTFloorOracle, NToken, - NTokenBAKC, - NTokenBAYC, - NTokenMAYC, NTokenMoonBirds, - NTokenOtherdeed, NTokenStakefish, NTokenUniswapV3, P2PPairStaking, @@ -210,6 +205,7 @@ import {pick, upperFirst} from "lodash"; import shell from "shelljs"; import {ZERO_ADDRESS} from "./constants"; import {GLOBAL_OVERRIDES, ZK_LIBRARIES_PATH} from "./hardhat-constants"; +import {zeroAddress} from "ethereumjs-util"; export const deployAllLibraries = async (verify?: boolean) => { const supplyLogic = await deploySupplyLogic(verify); @@ -220,7 +216,6 @@ export const deployAllLibraries = async (verify?: boolean) => { const poolLogic = await deployPoolLogic(verify); const configuratorLogic = await deployConfiguratorLogic(verify); const mintableERC721Logic = await deployMintableERC721Logic(verify); - const apeStakingLogic = await deployApeStakingLogic(verify); const merkleVerifier = await deployMerkleVerifier(verify); const libraries = { @@ -242,9 +237,6 @@ export const deployAllLibraries = async (verify?: boolean) => { "contracts/protocol/libraries/logic/PoolLogic.sol": { PoolLogic: poolLogic.address, }, - "contracts/protocol/tokenization/libraries/ApeStakingLogic.sol": { - ApeStakingLogic: apeStakingLogic.address, - }, "contracts/protocol/tokenization/libraries/MintableERC721Logic.sol": { MintableERC721Logic: mintableERC721Logic.address, }, @@ -1161,7 +1153,6 @@ export const deployGenericPTokenImpl = async ( export const deployGenericNTokenImpl = async ( poolAddress: tEthereumAddress, atomicPricing: boolean, - delegationRegistry: tEthereumAddress, verify?: boolean ) => { const mintableERC721Logic = @@ -1175,7 +1166,7 @@ export const deployGenericNTokenImpl = async ( return withSaveAndVerify( await getContractFactory("NToken", libraries), eContractid.NTokenImpl, - [poolAddress, atomicPricing, delegationRegistry], + [poolAddress, atomicPricing], verify, false, libraries @@ -1184,7 +1175,6 @@ export const deployGenericNTokenImpl = async ( export const deployUniswapV3NTokenImpl = async ( poolAddress: tEthereumAddress, - delegationRegistry: tEthereumAddress, verify?: boolean ) => { const mintableERC721Logic = @@ -1198,7 +1188,7 @@ export const deployUniswapV3NTokenImpl = async ( return withSaveAndVerify( await getContractFactory("NTokenUniswapV3", libraries), eContractid.NTokenUniswapV3Impl, - [poolAddress, delegationRegistry], + [poolAddress], verify, false, libraries @@ -1207,15 +1197,11 @@ export const deployUniswapV3NTokenImpl = async ( export const deployGenericMoonbirdNTokenImpl = async ( poolAddress: tEthereumAddress, - delegationRegistry: tEthereumAddress, verify?: boolean ) => { const mintableERC721Logic = (await getContractAddressInDb(eContractid.MintableERC721Logic)) || (await deployMintableERC721Logic(verify)).address; - const paraSpaceConfig = getParaSpaceConfig(); - - const timeLockV1 = paraSpaceConfig.ParaSpaceV1?.TimeLockV1 || ZERO_ADDRESS; const libraries = { ["contracts/protocol/tokenization/libraries/MintableERC721Logic.sol:MintableERC721Logic"]: @@ -1224,7 +1210,7 @@ export const deployGenericMoonbirdNTokenImpl = async ( return withSaveAndVerify( await getContractFactory("NTokenMoonBirds", libraries), eContractid.NTokenMoonBirdsImpl, - [poolAddress, delegationRegistry, timeLockV1], + [poolAddress], verify, false, libraries @@ -2450,99 +2436,6 @@ export const deployApeCoinStaking = async (verify?: boolean) => { return apeCoinStaking; }; -export const deployApeStakingLogic = async (verify?: boolean) => { - return withSaveAndVerify( - await getContractFactory("ApeStakingLogic"), - eContractid.ApeStakingLogic, - [], - verify - ) as Promise; -}; - -export const deployNTokenBAYCImpl = async ( - apeCoinStaking: tEthereumAddress, - poolAddress: tEthereumAddress, - delegationRegistry: tEthereumAddress, - verify?: boolean -) => { - const apeStakingLogic = - (await getContractAddressInDb(eContractid.ApeStakingLogic)) || - (await deployApeStakingLogic(verify)).address; - const mintableERC721Logic = - (await getContractAddressInDb(eContractid.MintableERC721Logic)) || - (await deployMintableERC721Logic(verify)).address; - - const libraries = { - ["contracts/protocol/tokenization/libraries/ApeStakingLogic.sol:ApeStakingLogic"]: - apeStakingLogic, - ["contracts/protocol/tokenization/libraries/MintableERC721Logic.sol:MintableERC721Logic"]: - mintableERC721Logic, - }; - - return withSaveAndVerify( - await getContractFactory("NTokenBAYC", libraries), - eContractid.NTokenBAYCImpl, - [poolAddress, apeCoinStaking, delegationRegistry], - verify, - false, - libraries - ) as Promise; -}; - -export const deployNTokenMAYCImpl = async ( - apeCoinStaking: tEthereumAddress, - poolAddress: tEthereumAddress, - delegationRegistry: tEthereumAddress, - verify?: boolean -) => { - const apeStakingLogic = - (await getContractAddressInDb(eContractid.ApeStakingLogic)) || - (await deployApeStakingLogic(verify)).address; - const mintableERC721Logic = - (await getContractAddressInDb(eContractid.MintableERC721Logic)) || - (await deployMintableERC721Logic(verify)).address; - - const libraries = { - ["contracts/protocol/tokenization/libraries/ApeStakingLogic.sol:ApeStakingLogic"]: - apeStakingLogic, - ["contracts/protocol/tokenization/libraries/MintableERC721Logic.sol:MintableERC721Logic"]: - mintableERC721Logic, - }; - return withSaveAndVerify( - await getContractFactory("NTokenMAYC", libraries), - eContractid.NTokenMAYCImpl, - [poolAddress, apeCoinStaking, delegationRegistry], - verify, - false, - libraries - ) as Promise; -}; - -export const deployNTokenBAKCImpl = async ( - poolAddress: tEthereumAddress, - apeCoinStaking: tEthereumAddress, - nBAYC: tEthereumAddress, - nMAYC: tEthereumAddress, - delegationRegistry: tEthereumAddress, - verify?: boolean -) => { - const mintableERC721Logic = - (await getContractAddressInDb(eContractid.MintableERC721Logic)) || - (await deployMintableERC721Logic(verify)).address; - const libraries = { - ["contracts/protocol/tokenization/libraries/MintableERC721Logic.sol:MintableERC721Logic"]: - mintableERC721Logic, - }; - return withSaveAndVerify( - await getContractFactory("NTokenBAKC", libraries), - eContractid.NTokenBAKCImpl, - [poolAddress, apeCoinStaking, nBAYC, nMAYC, delegationRegistry], - verify, - false, - libraries - ) as Promise; -}; - export const deployATokenDebtToken = async ( poolAddress: tEthereumAddress, verify?: boolean @@ -2695,7 +2588,7 @@ export const deployAutoCompoundApeImpl = async (verify?: boolean) => { (await getContractAddressInDb(eContractid.ApeCoinStaking)) || (await deployApeCoinStaking(verify)).address; const aclManager = await getACLManager(); - const args = [allTokens.APE.address, apeCoinStaking, aclManager.address]; + const args = [allTokens.APE.address, apeCoinStaking, aclManager.address, zeroAddress()]; return withSaveAndVerify( await getContractFactory("AutoCompoundApe"), @@ -3098,7 +2991,6 @@ export const deployReserveTimeLockStrategy = async ( export const deployChromieSquiggleNTokenImpl = async ( poolAddress: tEthereumAddress, - delegationRegistryAddress: tEthereumAddress, verify?: boolean ) => { const mintableERC721Logic = @@ -3114,7 +3006,7 @@ export const deployChromieSquiggleNTokenImpl = async ( return withSaveAndVerify( await getContractFactory("NTokenChromieSquiggle", libraries), eContractid.NTokenChromieSquiggleImpl, - [poolAddress, delegationRegistryAddress, startTokenId, endTokenId], + [poolAddress, startTokenId, endTokenId], verify, false, libraries @@ -3123,7 +3015,6 @@ export const deployChromieSquiggleNTokenImpl = async ( export const deployStakefishNTokenImpl = async ( poolAddress: tEthereumAddress, - delegationRegistryAddress: tEthereumAddress, verify?: boolean ) => { const mintableERC721Logic = @@ -3137,7 +3028,7 @@ export const deployStakefishNTokenImpl = async ( return withSaveAndVerify( await getContractFactory("NTokenStakefish", libraries), eContractid.NTokenStakefishImpl, - [poolAddress, delegationRegistryAddress], + [poolAddress], verify, false, libraries diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index a77fc556e..e42e881c1 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -16,7 +16,6 @@ import { getProtocolDataProvider, } from "./contracts-getters"; import { - getContractAddressInDb, insertContractAddressInDb, dryRunEncodedData, } from "./contracts-helpers"; @@ -33,20 +32,15 @@ import { deployReserveAuctionStrategy, deployPTokenStETH, deployPTokenAToken, - deployNTokenBAYCImpl, - deployNTokenMAYCImpl, deployATokenDebtToken, deployStETHDebtToken, deployPTokenSApe, - deployApeCoinStaking, deployPTokenCApe, deployCApeDebtToken, - deployNTokenBAKCImpl, deployPTokenAStETH, deployAStETHDebtToken, deployPYieldToken, deployReserveTimeLockStrategy, - deployOtherdeedNTokenImpl, deployStakefishNTokenImpl, deployChromieSquiggleNTokenImpl, deployAutoYieldApeImplAndAssignItToProxy, @@ -128,16 +122,12 @@ export const initReservesByHelper = async ( let nTokenImplementationAddress = genericNTokenImplAddress; let nTokenMoonBirdImplementationAddress = ""; let nTokenUniSwapV3ImplementationAddress = ""; - let nTokenBAYCImplementationAddress = ""; - let nTokenMAYCImplementationAddress = ""; let variableDebtTokenImplementationAddress = genericVariableDebtTokenAddress; let stETHVariableDebtTokenImplementationAddress = ""; let stKSMVariableDebtTokenImplementationAddress = ""; let astETHVariableDebtTokenImplementationAddress = ""; let aTokenVariableDebtTokenImplementationAddress = ""; let psApeVariableDebtTokenImplementationAddress = ""; - let nTokenBAKCImplementationAddress = ""; - let nTokenOTHRImplementationAddress = ""; let nTokenStakefishImplementationAddress = ""; if (genericPTokenImplAddress) { @@ -503,7 +493,6 @@ export const initReservesByHelper = async ( nTokenMoonBirdImplementationAddress = ( await deployGenericMoonbirdNTokenImpl( pool.address, - delegationRegistryAddress, verify ) ).address; @@ -514,89 +503,15 @@ export const initReservesByHelper = async ( nTokenUniSwapV3ImplementationAddress = ( await deployUniswapV3NTokenImpl( pool.address, - delegationRegistryAddress, verify ) ).address; } xTokenToUse = nTokenUniSwapV3ImplementationAddress; - } else if (reserveSymbol === ERC721TokenContractId.BAYC) { - const apeCoinStaking = - (await getContractAddressInDb(eContractid.ApeCoinStaking)) || - (await deployApeCoinStaking(verify)).address; - - if (!nTokenBAYCImplementationAddress) { - nTokenBAYCImplementationAddress = ( - await deployNTokenBAYCImpl( - apeCoinStaking, - pool.address, - delegationRegistryAddress, - verify - ) - ).address; - } - xTokenToUse = nTokenBAYCImplementationAddress; - } else if (reserveSymbol === ERC721TokenContractId.MAYC) { - const apeCoinStaking = - (await getContractAddressInDb(eContractid.ApeCoinStaking)) || - (await deployApeCoinStaking(verify)).address; - - if (!nTokenMAYCImplementationAddress) { - nTokenMAYCImplementationAddress = ( - await deployNTokenMAYCImpl( - apeCoinStaking, - pool.address, - delegationRegistryAddress, - verify - ) - ).address; - } - xTokenToUse = nTokenMAYCImplementationAddress; - } else if (reserveSymbol === ERC721TokenContractId.BAKC) { - if (!nTokenBAKCImplementationAddress) { - const apeCoinStaking = - (await getContractAddressInDb(eContractid.ApeCoinStaking)) || - (await deployApeCoinStaking(verify)).address; - const protocolDataProvider = await getProtocolDataProvider(); - const allTokens = await protocolDataProvider.getAllXTokens(); - const nBAYC = - // eslint-disable-next-line - allTokens.find( - (x) => x.symbol == NTokenContractId.nBAYC - )!.tokenAddress; - const nMAYC = - // eslint-disable-next-line - allTokens.find( - (x) => x.symbol == NTokenContractId.nMAYC - )!.tokenAddress; - nTokenBAKCImplementationAddress = ( - await deployNTokenBAKCImpl( - pool.address, - apeCoinStaking, - nBAYC, - nMAYC, - delegationRegistryAddress, - verify - ) - ).address; - } - xTokenToUse = nTokenBAKCImplementationAddress; - } else if (reserveSymbol == ERC721TokenContractId.OTHR) { - nTokenOTHRImplementationAddress = ( - await deployOtherdeedNTokenImpl( - pool.address, - hotWallet, - delegationRegistryAddress, - verify - ) - ).address; - - xTokenToUse = nTokenOTHRImplementationAddress; } else if (reserveSymbol == ERC721TokenContractId.SFVLDR) { nTokenStakefishImplementationAddress = ( await deployStakefishNTokenImpl( pool.address, - delegationRegistryAddress, verify ) ).address; @@ -606,7 +521,6 @@ export const initReservesByHelper = async ( xTokenToUse = ( await deployChromieSquiggleNTokenImpl( pool.address, - delegationRegistryAddress, verify ) ).address; @@ -618,7 +532,6 @@ export const initReservesByHelper = async ( await deployGenericNTokenImpl( pool.address, false, - delegationRegistryAddress, verify ) ).address; From 7ac8e80612b3e83ec28a5d6c12742a0e970e5e10 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 19 Dec 2023 11:50:55 +0800 Subject: [PATCH 3/9] chore: remove sApe --- contracts/misc/ProtocolDataProvider.sol | 8 - .../protocol/libraries/logic/SupplyLogic.sol | 11 -- .../libraries/logic/ValidationLogic.sol | 17 --- .../protocol/tokenization/PTokenSApe.sol | 100 ------------- contracts/ui/UiPoolDataProvider.sol | 10 -- contracts/ui/WalletBalanceProvider.sol | 3 - helpers/contracts-deployments.ts | 7 +- helpers/contracts-getters.ts | 36 ----- helpers/contracts-helpers.ts | 2 - helpers/init-helpers.ts | 26 +--- helpers/types.ts | 2 - market-config/index.ts | 11 +- market-config/reservesConfigs.ts | 16 -- .../deployments/steps/20_p2pPairStaking.ts | 10 +- scripts/upgrade/ntoken.ts | 139 ++---------------- test/helpers/make-suite.ts | 33 ++--- 16 files changed, 39 insertions(+), 392 deletions(-) delete mode 100644 contracts/protocol/tokenization/PTokenSApe.sol diff --git a/contracts/misc/ProtocolDataProvider.sol b/contracts/misc/ProtocolDataProvider.sol index cb16d510a..f62a4b648 100644 --- a/contracts/misc/ProtocolDataProvider.sol +++ b/contracts/misc/ProtocolDataProvider.sol @@ -28,7 +28,6 @@ contract ProtocolDataProvider is IProtocolDataProvider { address constant MKR = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2; address constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - address constant SAPE = address(0x1); IPoolAddressesProvider public immutable ADDRESSES_PROVIDER; @@ -62,13 +61,6 @@ contract ProtocolDataProvider is IProtocolDataProvider { }); continue; } - if (reserves[i] == SAPE) { - reservesTokens[i] = DataTypes.TokenData({ - symbol: "SApe", - tokenAddress: reserves[i] - }); - continue; - } reservesTokens[i] = DataTypes.TokenData({ symbol: IERC20Detailed(reserves[i]).symbol(), tokenAddress: reserves[i] diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index 50d7f1c52..9492852f0 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 94c1c0362..45da15bff 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -1014,23 +1014,6 @@ library ValidationLogic { Errors.FLASHCLAIM_NOT_ALLOWED ); - // need check sApe status when flash claim for bayc or mayc - if ( - tokenType == XTokenType.NTokenBAYC || - tokenType == XTokenType.NTokenMAYC - ) { - DataTypes.ReserveData storage sApeReserve = ps._reserves[ - DataTypes.SApeAddress - ]; - - (bool isActive, , , bool isPaused, ) = sApeReserve - .configuration - .getFlags(); - - require(isActive, Errors.RESERVE_INACTIVE); - require(!isPaused, Errors.RESERVE_PAUSED); - } - // only token owner can do flash claim for (uint256 i = 0; i < nftTokenIds.length; i++) { require( diff --git a/contracts/protocol/tokenization/PTokenSApe.sol b/contracts/protocol/tokenization/PTokenSApe.sol deleted file mode 100644 index 525f3fcb1..000000000 --- a/contracts/protocol/tokenization/PTokenSApe.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {IPool} from "../../interfaces/IPool.sol"; -import {PToken} from "./PToken.sol"; -import {INTokenApeStaking} from "../../interfaces/INTokenApeStaking.sol"; -import {WadRayMath} from "../libraries/math/WadRayMath.sol"; -import {XTokenType} from "../../interfaces/IXTokenType.sol"; -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; -import {INToken} from "../../interfaces/INToken.sol"; -import {IPToken} from "../../interfaces/IPToken.sol"; -import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; -import {IScaledBalanceToken} from "../../interfaces/IScaledBalanceToken.sol"; -import {IncentivizedERC20} from "./base/IncentivizedERC20.sol"; -import {DataTypes} from "../libraries/types/DataTypes.sol"; -import {ScaledBalanceTokenBaseERC20} from "../../protocol/tokenization/base/ScaledBalanceTokenBaseERC20.sol"; - -/** - * @title sApe PToken - * - * @notice Implementation of the interest bearing token for the ParaSpace protocol - */ -contract PTokenSApe is PToken { - using WadRayMath for uint256; - - INTokenApeStaking immutable nBAYC; - INTokenApeStaking immutable nMAYC; - - constructor(IPool pool, address _nBAYC, address _nMAYC) PToken(pool) { - require(_nBAYC != address(0) && _nMAYC != address(0)); - nBAYC = INTokenApeStaking(_nBAYC); - nMAYC = INTokenApeStaking(_nMAYC); - } - - function mint( - address, - address, - uint256, - uint256 - ) external virtual override onlyPool returns (bool) { - revert("not allowed"); - } - - function burn( - address, - address, - uint256, - uint256, - DataTypes.TimeLockParams calldata - ) external virtual override onlyPool { - revert("not allowed"); - } - - function balanceOf(address user) public view override returns (uint256) { - uint256 totalStakedAPE = nBAYC.getUserApeStakingAmount(user) + - nMAYC.getUserApeStakingAmount(user); - return totalStakedAPE; - } - - function scaledBalanceOf( - address user - ) - public - view - override(IScaledBalanceToken, ScaledBalanceTokenBaseERC20) - returns (uint256) - { - return balanceOf(user); - } - - function transferUnderlyingTo( - address, - uint256, - DataTypes.TimeLockParams calldata - ) public virtual override onlyPool { - revert("not allowed"); - } - - function transferOnLiquidation( - address, - address, - uint256 - ) external view override onlyPool { - revert("not allowed"); - } - - function _transfer(address, address, uint128) internal virtual override { - revert("not allowed"); - } - - function getXTokenType() - external - pure - virtual - override - returns (XTokenType) - { - return XTokenType.PTokenSApe; - } -} diff --git a/contracts/ui/UiPoolDataProvider.sol b/contracts/ui/UiPoolDataProvider.sol index 73a86b476..033c06bca 100644 --- a/contracts/ui/UiPoolDataProvider.sol +++ b/contracts/ui/UiPoolDataProvider.sol @@ -45,7 +45,6 @@ contract UiPoolDataProvider is IUiPoolDataProvider { uint256 public constant ETH_CURRENCY_UNIT = 1 ether; address public constant MKR_ADDRESS = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2; - address public constant SAPE_ADDRESS = address(0x1); constructor( IEACAggregatorProxy _networkBaseTokenPriceInUsdProxyAggregator, @@ -166,9 +165,6 @@ contract UiPoolDataProvider is IUiPoolDataProvider { reserveData.underlyingAsset ).name(); reserveData.name = bytes32ToString(name); - } else if (reserveData.underlyingAsset == SAPE_ADDRESS) { - reserveData.symbol = "SApe"; - reserveData.name = "SApe"; } else { reserveData.symbol = IERC20Detailed( reserveData.underlyingAsset @@ -177,12 +173,6 @@ contract UiPoolDataProvider is IUiPoolDataProvider { reserveData.underlyingAsset ).name(); } - - if (reserveData.underlyingAsset != SAPE_ADDRESS) { - reserveData.availableLiquidity = IERC20Detailed( - reserveData.underlyingAsset - ).balanceOf(reserveData.xTokenAddress); - } } else { reserveData.symbol = IERC721Metadata( reserveData.underlyingAsset diff --git a/contracts/ui/WalletBalanceProvider.sol b/contracts/ui/WalletBalanceProvider.sol index b7d1a61f2..48a94dc06 100644 --- a/contracts/ui/WalletBalanceProvider.sol +++ b/contracts/ui/WalletBalanceProvider.sol @@ -25,7 +25,6 @@ contract WalletBalanceProvider { address constant MOCK_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - address constant SAPE_ADDRESS = address(0x1); /** @dev Fallback function, don't accept any ETH @@ -48,8 +47,6 @@ contract WalletBalanceProvider { if (token == MOCK_ETH_ADDRESS) { return user.balance; // ETH balance // check if token is actually a contract - } else if (token == SAPE_ADDRESS) { - return 0; } else if (token.isContract()) { return IERC20(token).balanceOf(user); } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index cdbadb3ff..efeeffd40 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -2588,7 +2588,12 @@ export const deployAutoCompoundApeImpl = async (verify?: boolean) => { (await getContractAddressInDb(eContractid.ApeCoinStaking)) || (await deployApeCoinStaking(verify)).address; const aclManager = await getACLManager(); - const args = [allTokens.APE.address, apeCoinStaking, aclManager.address, zeroAddress()]; + const args = [ + allTokens.APE.address, + apeCoinStaking, + aclManager.address, + zeroAddress(), + ]; return withSaveAndVerify( await getContractFactory("AutoCompoundApe"), diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index 86f055b71..94197d5b8 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -57,8 +57,6 @@ import { MockMultiAssetAirdropProject__factory, IPool__factory, MockReserveAuctionStrategy__factory, - NTokenBAYC__factory, - NTokenMAYC__factory, ApeCoinStaking__factory, PTokenSApe__factory, StandardPolicyERC721__factory, @@ -76,7 +74,6 @@ import { StETHDebtToken__factory, ApeStakingLogic__factory, MintableERC721Logic__factory, - NTokenBAKC__factory, P2PPairStaking__factory, ExecutorWithTimelock__factory, MultiSendCallOnly__factory, @@ -984,39 +981,6 @@ export const getUserFlashClaimRegistry = async (address?: tEthereumAddress) => await getFirstSigner() ); -export const getNTokenBAYC = async (address?: tEthereumAddress) => - await NTokenBAYC__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.NTokenImpl}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - -export const getNTokenMAYC = async (address?: tEthereumAddress) => - await NTokenMAYC__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.NTokenImpl}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - -export const getNTokenBAKC = async (address?: tEthereumAddress) => - await NTokenBAKC__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.NTokenBAKCImpl}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - export const getApeCoinStaking = async (address?: tEthereumAddress) => await ApeCoinStaking__factory.connect( address || diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 272aa7a7d..3e7d03971 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -79,7 +79,6 @@ import { ReservesSetupHelper__factory, Seaport, Seaport__factory, - NTokenOtherdeed__factory, TimeLock__factory, P2PPairStaking__factory, NFTFloorOracle__factory, @@ -1082,7 +1081,6 @@ export const decodeInputData = (data: string) => { ...Seaport__factory.abi, ...InitializableAdminUpgradeabilityProxy__factory.abi, ...ICurve__factory.abi, - ...NTokenOtherdeed__factory.abi, ...TimeLock__factory.abi, ...P2PPairStaking__factory.abi, ...NFTFloorOracle__factory.abi, diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index e42e881c1..6e0e6deaa 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -491,49 +491,33 @@ export const initReservesByHelper = async ( if (reserveSymbol === ERC721TokenContractId.MOONBIRD) { if (!nTokenMoonBirdImplementationAddress) { nTokenMoonBirdImplementationAddress = ( - await deployGenericMoonbirdNTokenImpl( - pool.address, - verify - ) + await deployGenericMoonbirdNTokenImpl(pool.address, verify) ).address; } xTokenToUse = nTokenMoonBirdImplementationAddress; } else if (reserveSymbol === ERC721TokenContractId.UniswapV3) { if (!nTokenUniSwapV3ImplementationAddress) { nTokenUniSwapV3ImplementationAddress = ( - await deployUniswapV3NTokenImpl( - pool.address, - verify - ) + await deployUniswapV3NTokenImpl(pool.address, verify) ).address; } xTokenToUse = nTokenUniSwapV3ImplementationAddress; } else if (reserveSymbol == ERC721TokenContractId.SFVLDR) { nTokenStakefishImplementationAddress = ( - await deployStakefishNTokenImpl( - pool.address, - verify - ) + await deployStakefishNTokenImpl(pool.address, verify) ).address; xTokenToUse = nTokenStakefishImplementationAddress; } else if (reserveSymbol == ERC721TokenContractId.BLOCKS) { xTokenToUse = ( - await deployChromieSquiggleNTokenImpl( - pool.address, - verify - ) + await deployChromieSquiggleNTokenImpl(pool.address, verify) ).address; } if (!xTokenToUse) { if (!nTokenImplementationAddress) { nTokenImplementationAddress = ( - await deployGenericNTokenImpl( - pool.address, - false, - verify - ) + await deployGenericNTokenImpl(pool.address, false, verify) ).address; } xTokenToUse = nTokenImplementationAddress; diff --git a/helpers/types.ts b/helpers/types.ts index b32ff0909..8fa4d4021 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -466,7 +466,6 @@ export interface iAssetBase { awstETH: T; aWETH: T; APE: T; - sAPE: T; cAPE: T; yAPE: T; cETH: T; @@ -533,7 +532,6 @@ export type iParaSpacePoolAssets = Pick< | "awstETH" | "aWETH" | "APE" - | "sAPE" | "cAPE" | "yAPE" | "cETH" diff --git a/market-config/index.ts b/market-config/index.ts index 5eecaa4cf..10ac7ff9c 100644 --- a/market-config/index.ts +++ b/market-config/index.ts @@ -36,7 +36,6 @@ import { strategyUniswapV3, strategyClonex, strategyMeebits, - strategySAPE, strategyCAPE, strategyYAPE, strategyXCDOT, @@ -148,9 +147,7 @@ export const HardhatConfig: IParaSpaceConfiguration = { ...CommonConfig, ParaSpaceTeam: "0xc783df8a850f42e7F7e57013759C285caa701eB6", Treasury: "0xc783df8a850f42e7F7e57013759C285caa701eB6", - Tokens: { - sAPE: "0x0000000000000000000000000000000000000001", - }, + Tokens: {}, YogaLabs: {}, Uniswap: {}, Marketplace: {}, @@ -182,7 +179,6 @@ export const HardhatConfig: IParaSpaceConfiguration = { OTHR: strategyOthr, CLONEX: strategyClonex, UniswapV3: strategyUniswapV3, - sAPE: strategySAPE, cAPE: strategyCAPE, yAPE: strategyYAPE, BAKC: strategyBAKC, @@ -300,7 +296,6 @@ export const GoerliConfig: IParaSpaceConfiguration = { BAYC: "0xF40299b626ef6E197F5d9DE9315076CAB788B6Ef", MAYC: "0x3f228cBceC3aD130c45D21664f2C7f5b23130d23", BAKC: "0xd60d682764Ee04e54707Bee7B564DC65b31884D0", - sAPE: "0x0000000000000000000000000000000000000001", WETH: "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", aWETH: "0x7649e0d153752c556b8b23DB1f1D3d42993E83a5", bendETH: "0x57FEbd640424C85b72b4361fE557a781C8d2a509", @@ -369,7 +364,6 @@ export const GoerliConfig: IParaSpaceConfiguration = { OTHR: strategyOthr, CLONEX: strategyClonex, UniswapV3: strategyUniswapV3, - sAPE: strategySAPE, cAPE: strategyCAPE, yAPE: strategyYAPE, BAKC: strategyBAKC, @@ -883,7 +877,6 @@ export const MainnetConfig: IParaSpaceConfiguration = { AZUKI: "0xed5af388653567af2f388e6224dc7c4b3241c544", OTHR: "0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258", CLONEX: "0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b", - sAPE: "0x0000000000000000000000000000000000000001", UniswapV3: "0xC36442b4a4522E871399CD717aBDD847Ab11FE88", cETH: "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5", SEWER: "0x764AeebcF425d56800eF2c84F2578689415a2DAa", @@ -934,7 +927,6 @@ export const MainnetConfig: IParaSpaceConfiguration = { FRAX: "0x14d04fff8d21bd62987a5ce9ce543d2f1edf5d3e", WBTC: "0xdeb288F737066589598e9214E782fa5A8eD689e8", APE: "0xc7de7f4d4C9c991fF62a07D18b3E31e349833A18", - sAPE: "0xc7de7f4d4C9c991fF62a07D18b3E31e349833A18", cAPE: "0xc7de7f4d4C9c991fF62a07D18b3E31e349833A18", yAPE: "0xc7de7f4d4C9c991fF62a07D18b3E31e349833A18", BLUR: "0x32A880E831814CfD55dC556645Ef06816fE9bE02", @@ -977,7 +969,6 @@ export const MainnetConfig: IParaSpaceConfiguration = { AZUKI: strategyAzuki, OTHR: strategyOthr, CLONEX: strategyClonex, - sAPE: strategySAPE, cAPE: strategyCAPE, UniswapV3: strategyUniswapV3, BAKC: strategyBAKC, diff --git a/market-config/reservesConfigs.ts b/market-config/reservesConfigs.ts index 70c0ea388..b3cca27d3 100644 --- a/market-config/reservesConfigs.ts +++ b/market-config/reservesConfigs.ts @@ -228,22 +228,6 @@ export const strategyAPE: IReserveParams = { supplyCap: "18062500", }; -export const strategySAPE: IReserveParams = { - strategy: rateStrategyAPE, - auctionStrategy: auctionStrategyZero, - timeLockStrategy: timeLockStrategySAPE, - baseLTVAsCollateral: "5000", - liquidationProtocolFeePercentage: "0", - liquidationThreshold: "7000", - liquidationBonus: "10500", - borrowingEnabled: false, - reserveDecimals: "18", - xTokenImpl: eContractid.PTokenSApeImpl, - reserveFactor: "2500", - borrowCap: "0", - supplyCap: "0", -}; - export const strategyCAPE: IReserveParams = { strategy: rateStrategyAPE, auctionStrategy: auctionStrategyZero, diff --git a/scripts/deployments/steps/20_p2pPairStaking.ts b/scripts/deployments/steps/20_p2pPairStaking.ts index 00b140457..d78e92b65 100644 --- a/scripts/deployments/steps/20_p2pPairStaking.ts +++ b/scripts/deployments/steps/20_p2pPairStaking.ts @@ -1,9 +1,7 @@ import {deployP2PPairStaking} from "../../../helpers/contracts-deployments"; import { getAllTokens, - getNTokenBAKC, - getNTokenBAYC, - getNTokenMAYC, + getNToken, getPoolProxy, } from "../../../helpers/contracts-getters"; import {getParaSpaceConfig, waitForTx} from "../../../helpers/misc-utils"; @@ -28,7 +26,7 @@ export const step_20 = async (verify = false) => { const bakc = allTokens[ERC721TokenContractId.BAKC]; if (bayc) { - const nBAYC = await getNTokenBAYC( + const nBAYC = await getNToken( ( await pool.getReserveData(bayc.address) ).xTokenAddress @@ -43,7 +41,7 @@ export const step_20 = async (verify = false) => { } if (mayc) { - const nMAYC = await getNTokenMAYC( + const nMAYC = await getNToken( ( await pool.getReserveData(mayc.address) ).xTokenAddress @@ -58,7 +56,7 @@ export const step_20 = async (verify = false) => { } if (bakc) { - const nBAKC = await getNTokenBAKC( + const nBAKC = await getNToken( ( await pool.getReserveData(bakc.address) ).xTokenAddress diff --git a/scripts/upgrade/ntoken.ts b/scripts/upgrade/ntoken.ts index 21713a50b..fbdf31a3f 100644 --- a/scripts/upgrade/ntoken.ts +++ b/scripts/upgrade/ntoken.ts @@ -3,10 +3,6 @@ import { deployChromieSquiggleNTokenImpl, deployGenericMoonbirdNTokenImpl, deployGenericNTokenImpl, - deployNTokenBAKCImpl, - deployNTokenBAYCImpl, - deployNTokenMAYCImpl, - deployOtherdeedNTokenImpl, deployStakefishNTokenImpl, deployUniswapV3NTokenImpl, } from "../../helpers/contracts-deployments"; @@ -15,8 +11,6 @@ import { getPoolConfiguratorProxy, getProtocolDataProvider, getNToken, - getApeCoinStaking, - getPoolProxy, } from "../../helpers/contracts-getters"; import {NTokenContractId, XTokenType} from "../../helpers/types"; @@ -38,16 +32,11 @@ export const upgradeNToken = async (verify = false) => { const poolConfiguratorProxy = await getPoolConfiguratorProxy( await addressesProvider.getPoolConfigurator() ); - const delegationRegistry = paraSpaceConfig.DelegationRegistry; const protocolDataProvider = await getProtocolDataProvider(); const allXTokens = await protocolDataProvider.getAllXTokens(); let nTokenImplementationAddress = ""; - let nTokenBAYCImplementationAddress = ""; - let nTokenMAYCImplementationAddress = ""; - let nTokenBAKCImplementationAddress = ""; let nTokenMoonBirdImplementationAddress = ""; let nTokenUniSwapV3ImplementationAddress = ""; - let nTokenOTHRImplementationAddress = ""; let nTokenStakefishImplementationAddress = ""; let nTokenBlocksImplementationAddress = ""; let newImpl = ""; @@ -55,7 +44,6 @@ export const upgradeNToken = async (verify = false) => { for (let i = 0; i < allXTokens.length; i++) { const token = allXTokens[i]; const nToken = await getNToken(token.tokenAddress); - const pool = await getPoolProxy(); const asset = await nToken.UNDERLYING_ASSET_ADDRESS(); const incentivesController = paraSpaceConfig.IncentivesController; const name = await nToken.name(); @@ -83,70 +71,11 @@ export const upgradeNToken = async (verify = false) => { console.log(symbol + "not in XTOKEN_SYMBOL_UPGRADE_WHITELIST, skip..."); continue; } - - if (xTokenType == XTokenType.NTokenBAYC) { - if (!nTokenBAYCImplementationAddress) { - console.log("deploy NTokenBAYC implementation"); - const apeCoinStaking = await getApeCoinStaking(); - nTokenBAYCImplementationAddress = ( - await deployNTokenBAYCImpl( - apeCoinStaking.address, - poolAddress, - delegationRegistry, - verify - ) - ).address; - } - newImpl = nTokenBAYCImplementationAddress; - } else if (xTokenType == XTokenType.NTokenMAYC) { - if (!nTokenMAYCImplementationAddress) { - console.log("deploy NTokenMAYC implementation"); - const apeCoinStaking = await getApeCoinStaking(); - nTokenMAYCImplementationAddress = ( - await deployNTokenMAYCImpl( - apeCoinStaking.address, - poolAddress, - delegationRegistry, - verify - ) - ).address; - } - newImpl = nTokenMAYCImplementationAddress; - } else if (xTokenType == XTokenType.NTokenBAKC) { - if (!nTokenBAKCImplementationAddress) { - console.log("deploy NTokenBAKC implementation"); - const apeCoinStaking = await getApeCoinStaking(); - const nBAYC = - // eslint-disable-next-line - allXTokens.find( - (x) => x.symbol == NTokenContractId.nBAYC - )!.tokenAddress; - const nMAYC = - // eslint-disable-next-line - allXTokens.find( - (x) => x.symbol == NTokenContractId.nMAYC - )!.tokenAddress; - nTokenBAKCImplementationAddress = ( - await deployNTokenBAKCImpl( - pool.address, - apeCoinStaking.address, - nBAYC, - nMAYC, - delegationRegistry, - verify - ) - ).address; - } - newImpl = nTokenBAKCImplementationAddress; - } else if (xTokenType == XTokenType.NTokenUniswapV3) { + if (xTokenType == XTokenType.NTokenUniswapV3) { if (!nTokenUniSwapV3ImplementationAddress) { console.log("deploy NTokenUniswapV3 implementation"); nTokenUniSwapV3ImplementationAddress = ( - await deployUniswapV3NTokenImpl( - poolAddress, - delegationRegistry, - verify - ) + await deployUniswapV3NTokenImpl(poolAddress, verify) ).address; } newImpl = nTokenUniSwapV3ImplementationAddress; @@ -154,73 +83,28 @@ export const upgradeNToken = async (verify = false) => { if (!nTokenMoonBirdImplementationAddress) { console.log("deploy NTokenMoonBirds implementation"); nTokenMoonBirdImplementationAddress = ( - await deployGenericMoonbirdNTokenImpl( - poolAddress, - delegationRegistry, - verify - ) + await deployGenericMoonbirdNTokenImpl(poolAddress, verify) ).address; } newImpl = nTokenMoonBirdImplementationAddress; - } else if (xTokenType == XTokenType.NTokenOtherdeed) { - if (!nTokenOTHRImplementationAddress) { - console.log("deploy NTokenOtherdeed implementation"); - nTokenOTHRImplementationAddress = ( - await deployOtherdeedNTokenImpl( - poolAddress, - paraSpaceConfig.HotWallet, - delegationRegistry, - verify - ) - ).address; - } - newImpl = nTokenOTHRImplementationAddress; } else if (xTokenType == XTokenType.NTokenStakefish) { if (!nTokenStakefishImplementationAddress) { console.log("deploy NTokenStakefish implementation"); nTokenStakefishImplementationAddress = ( - await deployStakefishNTokenImpl( - poolAddress, - delegationRegistry, - verify - ) + await deployStakefishNTokenImpl(poolAddress, verify) ).address; } newImpl = nTokenStakefishImplementationAddress; } else if (xTokenType == XTokenType.NTokenChromieSquiggle) { console.log("deploy NTokenChromieSquiggle implementation"); - newImpl = ( - await deployChromieSquiggleNTokenImpl( - poolAddress, - delegationRegistry, - verify - ) - ).address; + newImpl = (await deployChromieSquiggleNTokenImpl(poolAddress, verify)) + .address; } else if (xTokenType == XTokenType.NToken) { - // compatibility - if (symbol == NTokenContractId.nOTHR) { - if (!nTokenOTHRImplementationAddress) { - console.log("deploy NTokenOtherdeed implementation"); - nTokenOTHRImplementationAddress = ( - await deployOtherdeedNTokenImpl( - poolAddress, - paraSpaceConfig.HotWallet, - delegationRegistry, - verify - ) - ).address; - } - newImpl = nTokenOTHRImplementationAddress; - // compatibility - } else if (symbol == NTokenContractId.nBLOCKS) { + if (symbol == NTokenContractId.nBLOCKS) { if (!nTokenBlocksImplementationAddress) { console.log("deploy NTokenBLOCKS implementation"); nTokenBlocksImplementationAddress = ( - await deployChromieSquiggleNTokenImpl( - poolAddress, - delegationRegistry, - verify - ) + await deployChromieSquiggleNTokenImpl(poolAddress, verify) ).address; } newImpl = nTokenBlocksImplementationAddress; @@ -228,12 +112,7 @@ export const upgradeNToken = async (verify = false) => { if (!nTokenImplementationAddress) { console.log("deploy NToken implementation"); nTokenImplementationAddress = ( - await deployGenericNTokenImpl( - poolAddress, - false, - delegationRegistry, - verify - ) + await deployGenericNTokenImpl(poolAddress, false, verify) ).address; } newImpl = nTokenImplementationAddress; diff --git a/test/helpers/make-suite.ts b/test/helpers/make-suite.ts index d77760b17..9ed608f3f 100644 --- a/test/helpers/make-suite.ts +++ b/test/helpers/make-suite.ts @@ -40,8 +40,6 @@ import { getPTokenStETH, getWPunkGatewayProxy, getWETHGatewayProxy, - getNTokenBAYC, - getNTokenMAYC, getApeCoinStaking, getBlurExchangeProxy, getExecutionDelegate, @@ -49,10 +47,8 @@ import { getLooksRareAdapter, getX2Y2Adapter, getBlurAdapter, - getNTokenBAKC, getWstETH, getMockCToken, - getNTokenOtherdeed, getStakefishNFTManager, getNTokenStakefish, getTimeLockProxy, @@ -77,11 +73,7 @@ import { LooksRareAdapter, MockCToken, NFTFloorOracle, - NTokenBAKC, - NTokenBAYC, - NTokenMAYC, NTokenMoonBirds, - NTokenOtherdeed, NTokenStakefish, NTokenUniswapV3, PausableZone, @@ -171,8 +163,8 @@ export interface TestEnv { pUsdc: PToken; usdc: MintableERC20; usdt: MintableERC20; - nBAYC: NTokenBAYC; - nOTHR: NTokenOtherdeed; + nBAYC: NToken; + nOTHR: NToken; bayc: MintableERC721; sfvldr: StakefishNFTManager; nSfvldr: NTokenStakefish; @@ -187,12 +179,12 @@ export interface TestEnv { wstETH: WstETHMocked; pstETH: PTokenStETH; ape: MintableERC20; - nMAYC: NTokenMAYC; + nMAYC: NToken; mayc: MintableERC721; nDOODLE: NToken; doodles: MintableERC721; bakc: MintableERC721; - nBAKC: NTokenBAKC; + nBAKC: NToken; mockTokenFaucet: MockTokenFaucet; wPunkGateway: WPunkGateway; wETHGateway: WETHGateway; @@ -251,9 +243,9 @@ export async function initializeMakeSuite() { pUsdc: {} as PToken, usdc: {} as MintableERC20, usdt: {} as MintableERC20, - nBAYC: {} as NTokenBAYC, + nBAYC: {} as NToken, nMOONBIRD: {} as NTokenMoonBirds, - nBAKC: {} as NTokenBAKC, + nBAKC: {} as NToken, bayc: {} as MintableERC721, sfvldr: {} as StakefishNFTManager, nSfvldr: {} as NTokenStakefish, @@ -335,8 +327,6 @@ export async function initializeMakeSuite() { testEnv.poolAdmin.signer ); - testEnv.nOTHR = await getNTokenOtherdeed(); - testEnv.protocolDataProvider = await getProtocolDataProvider(); testEnv.mockTokenFaucet = await getMockTokenFaucet(); @@ -418,6 +408,10 @@ export async function initializeMakeSuite() { (xToken) => xToken.symbol === NTokenContractId.nUniswapV3 )?.tokenAddress; + const nOTHRAddress = allTokens.find( + (xToken) => xToken.symbol === NTokenContractId.nOTHR + )?.tokenAddress; + const reservesTokens = await testEnv.protocolDataProvider.getAllReservesTokens(); @@ -525,13 +519,14 @@ export async function initializeMakeSuite() { variableDebtAWethAddress ); - testEnv.nBAYC = await getNTokenBAYC(nBAYCAddress); - testEnv.nMAYC = await getNTokenMAYC(nMAYCAddress); + testEnv.nBAYC = await getNToken(nBAYCAddress); + testEnv.nMAYC = await getNToken(nMAYCAddress); testEnv.nDOODLE = await getNToken(nDOODLEAddress); - testEnv.nBAKC = await getNTokenBAKC(nBAKCAddress); + testEnv.nBAKC = await getNToken(nBAKCAddress); testEnv.nSfvldr = await getNTokenStakefish(nSfvldrAddress); testEnv.nMOONBIRD = await getNTokenMoonBirds(nMOONBIRDAddress); + testEnv.nOTHR = await getNToken(nOTHRAddress); testEnv.dai = await getMintableERC20(daiAddress); testEnv.usdc = await getMintableERC20(usdcAddress); From 34d020e80d034d5731b7060f2670c4e35ba30d8a Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Thu, 21 Dec 2023 09:53:05 +0800 Subject: [PATCH 4/9] chore: Vault Ape Staking Implementation --- contracts/cross-chain/L1/IVault.sol | 8 + contracts/cross-chain/L1/IVaultApeStaking.sol | 41 +- contracts/cross-chain/L1/IVaultTemplate.sol | 12 + contracts/cross-chain/L1/VaultApeStaking.sol | 102 ++- contracts/cross-chain/L1/VaultTemplate.sol | 51 ++ .../openzeppelin/contracts/Pausable.sol | 2 +- .../protocol/libraries/helpers/Errors.sol | 1 + helpers/contracts-deployments.ts | 198 ++++- helpers/contracts-getters.ts | 41 +- helpers/init-helpers.ts | 20 - helpers/types.ts | 8 +- scripts/upgrade/ptoken.ts | 26 +- test/auto_compound_ape.spec.ts | 655 ---------------- test/vault_ape_staking.spec.ts | 703 ++++++++++++++++++ 14 files changed, 1120 insertions(+), 748 deletions(-) create mode 100644 contracts/cross-chain/L1/IVault.sol create mode 100644 contracts/cross-chain/L1/IVaultTemplate.sol create mode 100644 contracts/cross-chain/L1/VaultTemplate.sol create mode 100644 test/vault_ape_staking.spec.ts diff --git a/contracts/cross-chain/L1/IVault.sol b/contracts/cross-chain/L1/IVault.sol new file mode 100644 index 000000000..702e5cbc9 --- /dev/null +++ b/contracts/cross-chain/L1/IVault.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IVaultApeStaking.sol"; +import "./IVaultTemplate.sol"; +import "../../interfaces/IParaProxyInterfaces.sol"; + +interface IVault is IVaultApeStaking, IVaultTemplate, IParaProxyInterfaces {} diff --git a/contracts/cross-chain/L1/IVaultApeStaking.sol b/contracts/cross-chain/L1/IVaultApeStaking.sol index 83a211284..cb7e3c7c5 100644 --- a/contracts/cross-chain/L1/IVaultApeStaking.sol +++ b/contracts/cross-chain/L1/IVaultApeStaking.sol @@ -48,6 +48,14 @@ interface IVaultApeStaking { **/ event ApeStakingBotUpdated(address oldBot, address newBot); + /** + * @dev Emitted during setCApeIncomeRate() + * @param nft identify pool + * @param oldValue The value of the old cApe income rate + * @param newValue The value of the new cApe income rate + **/ + event CApeIncomeRateUpdated(address nft, uint32 oldValue, uint32 newValue); + /** * @dev Emitted during setCompoundFeeRate() * @param oldValue The old value of compound fee rate @@ -144,7 +152,38 @@ interface IVaultApeStaking { */ function compoundBAKC(BAKCPairActionInfo calldata actionInfo) external; - function checkApeStakingPosition(address nft, uint32 tokenId) external; + function onboardCheckApeStakingPosition( + address nft, + uint32 tokenId, + address beneficiary + ) external; + + function offboardCheckApeStakingPosition( + address nft, + uint32 tokenId + ) external; function unstakeApe(bool isBAYC, uint32[] calldata tokenIds) external; + + function setApeStakingBot(address _apeStakingBot) external; + + function setCompoundFeeRate(uint32 _compoundFeeRate) external; + + function claimCompoundFee(address receiver) external; + + function updateBeneficiary( + address nft, + uint32[] calldata tokenIds, + address newBenificiary + ) external; + + function pause() external; + + function unpause() external; + + function initialize() external; + + function compoundFee() external view returns (uint256); + + function setCApeIncomeRate(address nft, uint32 rate) external; } diff --git a/contracts/cross-chain/L1/IVaultTemplate.sol b/contracts/cross-chain/L1/IVaultTemplate.sol new file mode 100644 index 000000000..f5c066fc0 --- /dev/null +++ b/contracts/cross-chain/L1/IVaultTemplate.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IVaultTemplate { + function onboardNFTs(address nft, uint32[] calldata tokenIds) external; + + function onboardNFT(address nft, uint32 tokenId) external; + + function offboardNFTs(address nft, uint32[] calldata tokenIds) external; + + function offboardNFT(address nft, uint32 tokenId) external; +} diff --git a/contracts/cross-chain/L1/VaultApeStaking.sol b/contracts/cross-chain/L1/VaultApeStaking.sol index ddefe804e..39d79828e 100644 --- a/contracts/cross-chain/L1/VaultApeStaking.sol +++ b/contracts/cross-chain/L1/VaultApeStaking.sol @@ -75,6 +75,23 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { bakcMatchedCap = bakcPool.currentTimeRange.capPerPosition; } + function initialize() external { + //approve ApeCoin for apeCoinStaking + uint256 allowance = IERC20(apeCoin).allowance( + address(this), + address(apeCoinStaking) + ); + if (allowance == 0) { + IERC20(apeCoin).approve(address(apeCoinStaking), type(uint256).max); + } + + //approve ApeCoin for cApe + allowance = IERC20(apeCoin).allowance(address(this), address(cApe)); + if (allowance == 0) { + IERC20(apeCoin).approve(address(cApe), type(uint256).max); + } + } + /// @inheritdoc IVaultApeStaking function stakingApe( bool isBAYC, @@ -82,10 +99,12 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { ) external override whenNotPaused nonReentrant { uint256 arrayLength = tokenIds.length; require(arrayLength > 0, Errors.INVALID_PARAMETER); + ApeStakingStorage storage ds = apeStakingStorage(); + require(ds.apeStakingBot == msg.sender, Errors.NOT_APE_STAKING_BOT); address nft = isBAYC ? bayc : mayc; uint256 positionCap = isBAYC ? baycMatchedCap : maycMatchedCap; - PoolState storage poolState = apeStakingStorage().poolStates[nft]; + PoolState storage poolState = ds.poolStates[nft]; ApeCoinStaking.SingleNft[] memory _nfts = new ApeCoinStaking.SingleNft[](arrayLength); for (uint256 index = 0; index < arrayLength; index++) { @@ -133,6 +152,8 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { uint256 baycArrayLength, uint256 maycArrayLength ) = _validateBAKCPairActionInfo(actionInfo); + ApeStakingStorage storage ds = apeStakingStorage(); + require(ds.apeStakingBot == msg.sender, Errors.NOT_APE_STAKING_BOT); ApeCoinStaking.PairNftDepositWithAmount[] memory _baycPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( @@ -143,8 +164,8 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { maycArrayLength ); - PoolState storage bakcPoolState = apeStakingStorage().poolStates[bakc]; - PoolState storage baycPoolState = apeStakingStorage().poolStates[bayc]; + PoolState storage bakcPoolState = ds.poolStates[bakc]; + PoolState storage baycPoolState = ds.poolStates[bayc]; for (uint256 index = 0; index < baycArrayLength; index++) { uint32 apeTokenId = actionInfo.baycTokenIds[index]; uint32 bakcTokenId = actionInfo.bakcPairBaycTokenIds[index]; @@ -185,7 +206,7 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { emit BakcStaked(true, apeTokenId, bakcTokenId); } - PoolState storage maycPoolState = apeStakingStorage().poolStates[mayc]; + PoolState storage maycPoolState = ds.poolStates[mayc]; for (uint256 index = 0; index < maycArrayLength; index++) { uint32 apeTokenId = actionInfo.maycTokenIds[index]; uint32 bakcTokenId = actionInfo.bakcPairMaycTokenIds[index]; @@ -254,6 +275,8 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { for (uint256 index = 0; index < arrayLength; index++) { uint32 tokenId = tokenIds[index]; + //skip check if token is in pool or in staking + // construct staking data _nfts[index] = tokenId; @@ -294,6 +317,8 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { uint32 apeTokenId = actionInfo.baycTokenIds[index]; uint32 bakcTokenId = actionInfo.bakcPairBaycTokenIds[index]; + //skip check if token is in pool or in staking + // construct staking data _baycPairs[index] = ApeCoinStaking.PairNft({ mainTokenId: apeTokenId, @@ -327,10 +352,46 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } /// @inheritdoc IVaultApeStaking - function checkApeStakingPosition( + function onboardCheckApeStakingPosition( + address nft, + uint32 tokenId, + address beneficiary + ) external override { + require(msg.sender == address(this), Errors.INVALID_CALLER); + + if (nft == bayc || nft == mayc || nft == bakc) { + //ensure no ape position + uint256 poolId = (nft == bayc) ? 1 : ((nft == mayc) ? 2 : 3); + (uint256 stakedAmount, ) = apeCoinStaking.nftPosition( + poolId, + tokenId + ); + require(stakedAmount == 0, Errors.ALREADY_STAKING); + if (nft == bayc || nft == mayc) { + (, bool isPaired) = apeCoinStaking.mainToBakc(poolId, tokenId); + require(!isPaired, Errors.ALREADY_STAKING); + } + + PoolState storage poolState = apeStakingStorage().poolStates[nft]; + TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; + require( + tokenStatus.beneficiary == address(0), + Errors.INVALID_STATUS + ); + + tokenStatus.beneficiary = beneficiary; + tokenStatus.rewardsDebt = poolState.accumulatedRewardsPerNft; + poolState.tokenStatus[tokenId] = tokenStatus; + + poolState.totalPosition += 1; + } + } + + function offboardCheckApeStakingPosition( address nft, uint32 tokenId ) external override { + //ensure ownership by bridge, don't validate ownership here require(msg.sender == address(this), Errors.INVALID_CALLER); PoolState storage poolState = apeStakingStorage().poolStates[nft]; @@ -480,22 +541,21 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { ApeStakingStorage storage ds = apeStakingStorage(); PoolState storage poolState = ds.poolStates[nft]; //first part compound fee - uint256 compoundFee = totalClaimedApe.percentMul(ds.compoundFeeRate); + uint256 fee = totalClaimedApe.percentMul(ds.compoundFeeRate); //second part repay cape - uint256 cApeIncome = (totalClaimedApe - compoundFee).percentMul( + uint256 cApeIncome = (totalClaimedApe - fee).percentMul( poolState.cApeIncomeRatio ); //third ape pool income - uint256 poolIncome = totalClaimedApe - compoundFee - cApeIncome; + uint256 poolIncome = totalClaimedApe - fee - cApeIncome; cApe.notifyReward(cApeIncome); - cApe.deposit(address(this), compoundFee + poolIncome); + cApe.deposit(address(this), fee + poolIncome); uint256 cApeExchangeRate = cApe.getPooledApeByShares(WadRayMath.RAY); poolState.accumulatedRewardsPerNft += (poolIncome.rayDiv( cApeExchangeRate ) / poolState.totalPosition).toUint128(); - ds.accuCompoundFee += (compoundFee.rayDiv(cApeExchangeRate)) - .toUint128(); + ds.accuCompoundFee += (fee.rayDiv(cApeExchangeRate)).toUint128(); } function _validateBAKCPairActionInfo( @@ -566,6 +626,7 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { uint32 tokenId = tokenIds[index]; TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; + //ensure token id is in pool and caller is valid by checking beneficiary require( msg.sender == tokenStatus.beneficiary, Errors.INVALID_CALLER @@ -625,6 +686,20 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } } + function setCApeIncomeRate( + address nft, + uint32 rate + ) external onlyPoolAdmin { + require(rate <= 1e4, Errors.INVALID_PARAMETER); + ApeStakingStorage storage ds = apeStakingStorage(); + PoolState storage poolState = ds.poolStates[nft]; + uint32 oldValue = poolState.cApeIncomeRatio; + if (oldValue != rate) { + poolState.cApeIncomeRatio = rate; + emit CApeIncomeRateUpdated(nft, oldValue, rate); + } + } + function claimCompoundFee(address receiver) external { ApeStakingStorage storage ds = apeStakingStorage(); require(ds.apeStakingBot == msg.sender, Errors.NOT_APE_STAKING_BOT); @@ -638,6 +713,11 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } } + function compoundFee() external view returns (uint256) { + ApeStakingStorage storage ds = apeStakingStorage(); + return ds.accuCompoundFee; + } + function updateBeneficiary( address nft, uint32[] calldata tokenIds, diff --git a/contracts/cross-chain/L1/VaultTemplate.sol b/contracts/cross-chain/L1/VaultTemplate.sol new file mode 100644 index 000000000..9170d8307 --- /dev/null +++ b/contracts/cross-chain/L1/VaultTemplate.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../dependencies/openzeppelin/contracts/ReentrancyGuard.sol"; +import "../../dependencies/openzeppelin/contracts//Pausable.sol"; +import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; +import "./IVaultTemplate.sol"; +import "./IVaultApeStaking.sol"; + +//Mock Socket Vault Implementation, Just for Testing +contract VaultTemplate is ReentrancyGuard, Pausable, IVaultTemplate { + function onboardNFTs(address nft, uint32[] calldata tokenIds) external { + for (uint256 index = 0; index < tokenIds.length; index++) { + onboardNFT(nft, tokenIds[index]); + } + } + + function onboardNFT(address nft, uint32 tokenId) public { + IERC721(nft).safeTransferFrom(msg.sender, address(this), tokenId); + IVaultApeStaking(address(this)).onboardCheckApeStakingPosition( + nft, + tokenId, + msg.sender + ); + } + + //didn't check ownership here, just for testing + function offboardNFTs(address nft, uint32[] calldata tokenIds) external { + for (uint256 index = 0; index < tokenIds.length; index++) { + offboardNFT(nft, tokenIds[index]); + } + } + + //didn't check ownership here, just for testing + function offboardNFT(address nft, uint32 tokenId) public { + IVaultApeStaking(address(this)).offboardCheckApeStakingPosition( + nft, + tokenId + ); + IERC721(nft).safeTransferFrom(address(this), msg.sender, tokenId); + } + + function onERC721Received( + address, + address, + uint256, + bytes memory + ) external pure returns (bytes4) { + return this.onERC721Received.selector; + } +} diff --git a/contracts/dependencies/openzeppelin/contracts/Pausable.sol b/contracts/dependencies/openzeppelin/contracts/Pausable.sol index f8b37a443..5b1ffc6d4 100644 --- a/contracts/dependencies/openzeppelin/contracts/Pausable.sol +++ b/contracts/dependencies/openzeppelin/contracts/Pausable.sol @@ -36,7 +36,7 @@ abstract contract Pausable is Context { /** * @dev Returns true if the contract is paused, and false otherwise. */ - function paused() public view virtual returns (bool) { + function paused() internal view virtual returns (bool) { return _paused; } diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index 97cde563f..f966d4361 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -145,4 +145,5 @@ library Errors { string public constant NOT_APE_STAKING_BOT = "206"; //only ape staking bot string public constant NFT_NOT_IN_POOL = "207"; //nft not in the pool string public constant ALREADY_STAKING = "208"; //already staking + string public constant INVALID_STATUS = "209"; //invalid status } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index efeeffd40..0f9d1cd60 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -29,8 +29,8 @@ import { DefaultReserveAuctionStrategy, DefaultReserveInterestRateStrategy, DefaultTimeLockStrategy, - DelegationAwarePToken, DelegateRegistry, + DelegationAwarePToken, DepositContract, Doodles, ERC20OracleWrapper, @@ -82,17 +82,21 @@ import { NTokenStakefish, NTokenUniswapV3, P2PPairStaking, + ParaProxy, + ParaProxy__factory, ParaProxyInterfaces, ParaProxyInterfaces__factory, - ParaProxy__factory, ParaSpaceAirdrop, ParaSpaceOracle, PausableZoneController, PolicyManager, + PoolAAPositionMover__factory, PoolAddressesProvider, PoolAddressesProviderRegistry, PoolApeStaking, PoolApeStaking__factory, + PoolBorrowAndStake, + PoolBorrowAndStake__factory, PoolConfigurator, PoolCore, PoolCore__factory, @@ -110,7 +114,6 @@ import { PTokenAStETH, PTokenAToken, PTokenCApe, - PTokenSApe, PTokenStETH, PTokenStKSM, PYieldToken, @@ -140,6 +143,10 @@ import { UniswapV3TwapOracleWrapper, UserFlashclaimRegistry, VariableDebtToken, + VaultApeStaking, + VaultApeStaking__factory, + VaultTemplate, + VaultTemplate__factory, WalletBalanceProvider, WETH9Mocked, WETHGateway, @@ -148,9 +155,6 @@ import { WstETHMocked, X2Y2Adapter, X2Y2R1, - PoolAAPositionMover__factory, - PoolBorrowAndStake__factory, - PoolBorrowAndStake, } from "../types"; import { getACLManager, @@ -168,6 +172,7 @@ import { getPunks, getTimeLockProxy, getUniswapV3SwapRouter, + getVaultProxy, getWETH, } from "./contracts-getters"; import { @@ -335,6 +340,169 @@ export const deployACLManager = async ( verify ) as Promise; +export const getVaultSignatures = () => { + const apeStakingSelectors = getFunctionSignatures( + VaultApeStaking__factory.abi + ); + + const templateSelectors = getFunctionSignatures(VaultTemplate__factory.abi); + + const paraProxyInterfacesSelectors = getFunctionSignatures( + ParaProxyInterfaces__factory.abi + ); + + const allSelectors = {}; + const vaultSelectors = [ + ...apeStakingSelectors, + ...templateSelectors, + ...paraProxyInterfacesSelectors, + ]; + for (const selector of vaultSelectors) { + if (!allSelectors[selector.signature]) { + allSelectors[selector.signature] = selector; + } else { + throw new Error( + `added function ${selector.name} conflict with exist function:${ + allSelectors[selector.signature].name + }` + ); + } + } + + return { + apeStakingSelectors, + templateSelectors, + paraProxyInterfacesSelectors, + }; +}; + +export const deployVaultApeStaking = async (verify?: boolean) => { + const allTokens = await getAllTokens(); + const apeCoinStaking = + (await getContractAddressInDb(eContractid.ApeCoinStaking)) || + (await deployApeCoinStaking(verify)).address; + + const cApe = await getAutoCompoundApe(); + const aclManager = await getACLManager(); + + const {apeStakingSelectors} = getVaultSignatures(); + const args = [ + allTokens.BAYC.address, + allTokens.MAYC.address, + allTokens.BAKC.address, + allTokens.APE.address, + cApe.address, + apeCoinStaking, + aclManager.address, + zeroAddress(), + ]; + + const vaultApeStaking = (await withSaveAndVerify( + await getContractFactory("VaultApeStaking"), + eContractid.VaultApeStaking, + args, + verify, + false + )) as VaultApeStaking; + + return { + vaultApeStaking, + apeStakingSelectors: apeStakingSelectors.map((s) => s.signature), + }; +}; + +export const deployVaultTemplate = async (verify?: boolean) => { + const {templateSelectors} = getVaultSignatures(); + + const vaultTemplate = (await withSaveAndVerify( + await getContractFactory("VaultTemplate"), + eContractid.VaultTemplate, + [], + verify, + false + )) as VaultTemplate; + + return { + vaultTemplate, + vaultTemplateSelectors: templateSelectors.map((s) => s.signature), + }; +}; + +export const deployVaultParaProxyInterfaces = async (verify?: boolean) => { + const {paraProxyInterfacesSelectors} = await getVaultSignatures(); + const vaultParaProxyInterfaces = (await withSaveAndVerify( + await getContractFactory("ParaProxyInterfaces"), + eContractid.VaultProxyInterfacesImpl, + [], + verify, + false, + undefined, + paraProxyInterfacesSelectors + )) as ParaProxyInterfaces; + + return { + vaultParaProxyInterfaces, + paraProxyInterfacesSelectors: paraProxyInterfacesSelectors.map( + (s) => s.signature + ), + }; +}; + +export const deployVaultProxy = async (verify?: boolean) => { + const deployer = await getFirstSigner(); + const deployerAddress = await deployer.getAddress(); + + return (await withSaveAndVerify( + await getContractFactory("ParaProxy"), + eContractid.VaultProxy, + [deployerAddress], + verify, + false + )) as ParaProxy; +}; + +export const deployVault = async (verify?: boolean) => { + const proxyAddress = + (await getContractAddressInDb(eContractid.VaultProxy)) || + (await deployVaultProxy(verify)).address; + + const proxy = await getVaultProxy(proxyAddress); + + const {vaultApeStaking, apeStakingSelectors} = await deployVaultApeStaking( + verify + ); + + const {vaultTemplate, vaultTemplateSelectors} = await deployVaultTemplate( + verify + ); + + const {vaultParaProxyInterfaces, paraProxyInterfacesSelectors} = + await deployVaultParaProxyInterfaces(verify); + + await proxy.updateImplementation( + [ + { + implAddress: vaultApeStaking.address, + action: 0, + functionSelectors: apeStakingSelectors, + }, + { + implAddress: vaultTemplate.address, + action: 0, + functionSelectors: vaultTemplateSelectors, + }, + { + implAddress: vaultParaProxyInterfaces.address, + action: 0, + functionSelectors: paraProxyInterfacesSelectors, + }, + ], + proxy.address, + vaultApeStaking.interface.encodeFunctionData("initialize"), + GLOBAL_OVERRIDES + ); +}; + export const deployConfiguratorLogic = async (verify?: boolean) => withSaveAndVerify( await getContractFactory("ConfiguratorLogic"), @@ -2277,19 +2445,6 @@ export const deployPTokenAStETH = async ( verify ) as Promise; -export const deployPTokenSApe = async ( - poolAddress: tEthereumAddress, - nBAYC: tEthereumAddress, - nMAYC: tEthereumAddress, - verify?: boolean -) => - withSaveAndVerify( - await getContractFactory("PTokenSApe"), - eContractid.PTokenSApeImpl, - [poolAddress, nBAYC, nMAYC], - verify - ) as Promise; - export const deployUserFlashClaimRegistry = async ( receiverImpl: tEthereumAddress, verify?: boolean @@ -2430,7 +2585,7 @@ export const deployApeCoinStaking = async (verify?: boolean) => { amount, "1666771200", "1761465600", - parseEther("100000"), + parseEther("50000"), GLOBAL_OVERRIDES ); return apeCoinStaking; @@ -2583,6 +2738,7 @@ export const deployTimeLockExecutor = async ( }; export const deployAutoCompoundApeImpl = async (verify?: boolean) => { + const proxy = await deployVaultProxy(); const allTokens = await getAllTokens(); const apeCoinStaking = (await getContractAddressInDb(eContractid.ApeCoinStaking)) || @@ -2592,7 +2748,7 @@ export const deployAutoCompoundApeImpl = async (verify?: boolean) => { allTokens.APE.address, apeCoinStaking, aclManager.address, - zeroAddress(), + proxy.address, ]; return withSaveAndVerify( diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index 94197d5b8..053106135 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -58,7 +58,6 @@ import { IPool__factory, MockReserveAuctionStrategy__factory, ApeCoinStaking__factory, - PTokenSApe__factory, StandardPolicyERC721__factory, BlurExchange__factory, ExecutionDelegate__factory, @@ -96,6 +95,8 @@ import { Account__factory, AccountFactory__factory, AccountRegistry__factory, + IVault__factory, + ParaProxy__factory, } from "../types"; import { getEthersSigners, @@ -349,7 +350,7 @@ export const getPoolLogic = async (address?: tEthereumAddress) => ); export const getPoolProxy = async (address?: tEthereumAddress) => { - return await IPool__factory.connect( + return IPool__factory.connect( address || ( await getDb() @@ -359,6 +360,31 @@ export const getPoolProxy = async (address?: tEthereumAddress) => { await getFirstSigner() ); }; + +export const getVaultProxy = async (address?: tEthereumAddress) => { + return ParaProxy__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.VaultProxy}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); +}; + +export const getVault = async (address?: tEthereumAddress) => { + return IVault__factory.connect( + address || + ( + await getDb() + .get(`${eContractid.VaultProxy}.${DRE.network.name}`) + .value() + ).address, + await getFirstSigner() + ); +}; + export const getPriceOracle = async (address?: tEthereumAddress) => await PriceOracle__factory.connect( address || @@ -937,17 +963,6 @@ export const getPTokenStETH = async (address?: tEthereumAddress) => await getFirstSigner() ); -export const getPTokenSApe = async (address?: tEthereumAddress) => - await PTokenSApe__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.PTokenSApeImpl}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - export const getPTokenAToken = async (address?: tEthereumAddress) => await PTokenAToken__factory.connect( address || diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index 6e0e6deaa..3f4648ab0 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -34,7 +34,6 @@ import { deployPTokenAToken, deployATokenDebtToken, deployStETHDebtToken, - deployPTokenSApe, deployPTokenCApe, deployCApeDebtToken, deployPTokenAStETH, @@ -436,25 +435,6 @@ export const initReservesByHelper = async ( ).address; } variableDebtTokenToUse = astETHVariableDebtTokenImplementationAddress; - } else if (reserveSymbol === ERC20TokenContractId.sAPE) { - if (!pTokenSApeImplementationAddress) { - const protocolDataProvider = await getProtocolDataProvider(); - const allTokens = await protocolDataProvider.getAllXTokens(); - const nBAYC = - // eslint-disable-next-line - allTokens.find( - (x) => x.symbol == NTokenContractId.nBAYC - )!.tokenAddress; - const nMAYC = - // eslint-disable-next-line - allTokens.find( - (x) => x.symbol == NTokenContractId.nMAYC - )!.tokenAddress; - pTokenSApeImplementationAddress = ( - await deployPTokenSApe(pool.address, nBAYC, nMAYC, verify) - ).address; - } - xTokenToUse = pTokenSApeImplementationAddress; } else if (reserveSymbol === ERC20TokenContractId.cAPE) { await deployAutoCompoundApeImplAndAssignItToProxy(verify); if (!pTokenPsApeImplementationAddress) { diff --git a/helpers/types.ts b/helpers/types.ts index 8fa4d4021..d0e3fb8ce 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -133,7 +133,6 @@ export enum eContractid { InitializableImmutableAdminUpgradeabilityProxy = "InitializableImmutableAdminUpgradeabilityProxy", MockFlashLoanReceiver = "MockFlashLoanReceiver", PTokenImpl = "PTokenImpl", - PTokenSApeImpl = "PTokenSApeImpl", PTokenATokenImpl = "PTokenATokenImpl", PTokenStETHImpl = "PTokenStETHImpl", PTokenStKSMImpl = "PTokenStKSMImpl", @@ -237,6 +236,10 @@ export enum eContractid { PoolParametersImpl = "PoolParametersImpl", PoolApeStakingImpl = "PoolApeStakingImpl", PoolBorrowAndStakeImpl = "PoolBorrowAndStakeImpl", + VaultProxy = "VaultProxy", + VaultApeStaking = "VaultApeStaking", + VaultTemplate = "VaultTemplate", + VaultProxyInterfacesImpl = "VaultProxyInterfacesImpl", ApeCoinStaking = "ApeCoinStaking", ATokenDebtToken = "ATokenDebtToken", StETHDebtToken = "StETHDebtToken", @@ -434,6 +437,9 @@ export enum ProtocolErrors { EMEGENCY_DISABLE_CALL = "emergency disable call", MAKER_SAME_AS_TAKER = "132", + INVALID_CALLER = "200", //invalid caller + NFT_NOT_IN_POOL = "207", //nft not in the pool + ALREADY_STAKING = "208", //already staking } export type tEthereumAddress = string; diff --git a/scripts/upgrade/ptoken.ts b/scripts/upgrade/ptoken.ts index a8c7b0094..8d743b777 100644 --- a/scripts/upgrade/ptoken.ts +++ b/scripts/upgrade/ptoken.ts @@ -4,7 +4,6 @@ import { deployGenericPTokenImpl, deployPTokenAToken, deployPTokenCApe, - deployPTokenSApe, deployPTokenStETH, deployPTokenStKSM, } from "../../helpers/contracts-deployments"; @@ -14,11 +13,7 @@ import { getProtocolDataProvider, getPToken, } from "../../helpers/contracts-getters"; -import { - NTokenContractId, - PTokenContractId, - XTokenType, -} from "../../helpers/types"; +import {PTokenContractId, XTokenType} from "../../helpers/types"; import dotenv from "dotenv"; import { @@ -43,7 +38,6 @@ export const upgradePToken = async (verify = false) => { let pTokenDelegationAwareImplementationAddress = ""; let pTokenStETHImplementationAddress = ""; let pTokenStKSMImplementationAddress = ""; - let pTokenSApeImplementationAddress = ""; let pTokenCApeImplementationAddress = ""; let pTokenATokenImplementationAddress = ""; let newImpl = ""; @@ -89,24 +83,6 @@ export const upgradePToken = async (verify = false) => { } newImpl = pTokenATokenImplementationAddress; } - } else if (xTokenType == XTokenType.PTokenSApe) { - if (!pTokenSApeImplementationAddress) { - console.log("deploy PTokenSApe implementation"); - const nBAYC = - // eslint-disable-next-line - allXTokens.find( - (x) => x.symbol == NTokenContractId.nBAYC - )!.tokenAddress; - const nMAYC = - // eslint-disable-next-line - allXTokens.find( - (x) => x.symbol == NTokenContractId.nMAYC - )!.tokenAddress; - pTokenSApeImplementationAddress = ( - await deployPTokenSApe(poolAddress, nBAYC, nMAYC, verify) - ).address; - } - newImpl = pTokenSApeImplementationAddress; } else if (xTokenType == XTokenType.PTokenCApe) { if (!pTokenCApeImplementationAddress) { console.log("deploy PTokenCApe implementation"); diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index e83b5aa36..36cbd766f 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -409,559 +409,6 @@ describe("Auto Compound Ape Test", () => { almostEqual(user1ApeBalance, parseEther("5923.8")); }); - it("claimApeAndCompound function work as expected 1", async () => { - const { - pUsdc, - usdc, - users: [user1, user2, , , user3], - mayc, - pool, - ape, - } = await loadFixture(fixture); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user2.signer)["mint(address)"](user2.address) - ); - await waitForTx( - await mayc.connect(user3.signer)["mint(address)"](user3.address) - ); - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await mayc.connect(user2.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await mayc.connect(user3.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool - .connect(user1.signer) - .supplyERC721( - mayc.address, - [{tokenId: 0, useAsCollateral: true}], - user1.address, - "0" - ) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supplyERC721( - mayc.address, - [{tokenId: 1, useAsCollateral: true}], - user2.address, - "0" - ) - ); - await waitForTx( - await pool - .connect(user3.signer) - .supplyERC721( - mayc.address, - [{tokenId: 2, useAsCollateral: true}], - user3.address, - "0" - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user1Amount, - }, - [{tokenId: 0, amount: user1Amount}], - [] - ) - ); - - await waitForTx( - await pool.connect(user2.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user2Amount, - }, - [{tokenId: 1, amount: user2Amount}], - [] - ) - ); - - await waitForTx( - await pool.connect(user3.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user3Amount, - }, - [{tokenId: 2, amount: user3Amount}], - [] - ) - ); - - await advanceTimeAndBlock(3600); - - // repay then supply - await waitForTx( - await pool.connect(user1.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 0, - }) - ); - - // repay then supply - await waitForTx( - await pool.connect(user2.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 0, - }) - ); - - // swap half then supply - await waitForTx( - await pool.connect(user3.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 5000, - }) - ); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address, user2.address, user3.address], - [[0], [1], [2]], - {gasLimit: 5000000} - ) - ); - - // 3600 / 7 * 99.7% = 512.74 - const user1Balance = await pCApe.balanceOf(user1.address); - almostEqual(user1Balance, parseEther("512.7428")); - - // 3600 * 2 / 7 * 99.7% = 1025.48 - const user2Balance = await pCApe.balanceOf(user2.address); - almostEqual(user2Balance, parseEther("1025.48")); - - // 3600 * 4 / 7 * 99.7% * 50% = 1025.4857142857142858 - const user3Balance = await pCApe.balanceOf(user3.address); - almostEqual(user3Balance, parseEther("1025.48571")); - - almostEqual( - await pUsdc.balanceOf(user3.address), - await convertToCurrencyDecimals(usdc.address, "4059.235923") - ); - - // 3600 * 0.003 - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const incentiveBalance = await cApe.balanceOf(treasuryAddress); - almostEqual(incentiveBalance, parseEther("10.8")); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address, user2.address, user3.address], - [[0], [1], [2]], - {gasLimit: 5000000} - ) - ); - }); - - it("claimApeAndCompound function work as expected 2", async () => { - const { - users: [user1, user2], - mayc, - pool, - ape, - } = await loadFixture(fixture); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool.connect(user1.signer).supplyERC721( - mayc.address, - [ - {tokenId: 0, useAsCollateral: true}, - {tokenId: 1, useAsCollateral: true}, - {tokenId: 2, useAsCollateral: true}, - ], - user1.address, - "0" - ) - ); - - const totalAmount = parseEther("900"); - const userAmount = parseEther("300"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: totalAmount, - }, - [ - {tokenId: 0, amount: userAmount}, - {tokenId: 1, amount: userAmount}, - {tokenId: 2, amount: userAmount}, - ], - [] - ) - ); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound(mayc.address, [user1.address], [[0, 1, 2]], { - gasLimit: 5000000, - }) - ); - - //3600 * 0.997 = 3589.2 - const user1Balance = await pCApe.balanceOf(user1.address); - almostEqual(user1Balance, parseEther("3589.2")); - - // 3600 * 0.003 - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const incentiveBalance = await cApe.balanceOf(treasuryAddress); - almostEqual(incentiveBalance, parseEther("10.8")); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound(mayc.address, [user1.address], [[0, 1, 2]], { - gasLimit: 5000000, - }) - ); - }); - - it("claimApeAndCompound function work as expected 3", async () => { - const { - pWETH, - weth, - users: [user1, user2, , , user3], - mayc, - pool, - ape, - } = await loadFixture(fixture); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user2.signer)["mint(address)"](user2.address) - ); - await waitForTx( - await mayc.connect(user3.signer)["mint(address)"](user3.address) - ); - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await mayc.connect(user2.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await mayc.connect(user3.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool - .connect(user1.signer) - .supplyERC721( - mayc.address, - [{tokenId: 0, useAsCollateral: true}], - user1.address, - "0" - ) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supplyERC721( - mayc.address, - [{tokenId: 1, useAsCollateral: true}], - user2.address, - "0" - ) - ); - await waitForTx( - await pool - .connect(user3.signer) - .supplyERC721( - mayc.address, - [{tokenId: 2, useAsCollateral: true}], - user3.address, - "0" - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user1Amount, - }, - [{tokenId: 0, amount: user1Amount}], - [] - ) - ); - - await waitForTx( - await pool.connect(user2.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user2Amount, - }, - [{tokenId: 1, amount: user2Amount}], - [] - ) - ); - - await waitForTx( - await pool.connect(user3.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: user3Amount, - }, - [{tokenId: 2, amount: user3Amount}], - [] - ) - ); - - await advanceTimeAndBlock(3600); - - // repay then supply - await waitForTx( - await pool.connect(user1.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 0, - }) - ); - - // repay then supply - await waitForTx( - await pool.connect(user2.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 0, - swapPercent: 0, - }) - ); - - // swap half then supply - await waitForTx( - await pool.connect(user3.signer).setApeCompoundStrategy({ - ty: 0, - swapTokenOut: 1, - swapPercent: 5000, - }) - ); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address, user2.address, user3.address], - [[0], [1], [2]], - {gasLimit: 5000000} - ) - ); - - // 3600 / 7 * 99.7% = 512.74 - const user1Balance = await pCApe.balanceOf(user1.address); - almostEqual(user1Balance, parseEther("512.7428")); - - // 3600 * 2 / 7 * 99.7% = 1025.48 - const user2Balance = await pCApe.balanceOf(user2.address); - almostEqual(user2Balance, parseEther("1025.48")); - - // 3600 * 4 / 7 * 99.7% * 50% = 1025.4857142857142858 - const user3Balance = await pCApe.balanceOf(user3.address); - almostEqual(user3Balance, parseEther("1025.48571")); - - almostEqual( - await pWETH.balanceOf(user3.address), - await convertToCurrencyDecimals(weth.address, "3.732876") - ); - - // 3600 * 0.003 - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const incentiveBalance = await cApe.balanceOf(treasuryAddress); - almostEqual(incentiveBalance, parseEther("10.8")); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool - .connect(user2.signer) - .claimApeAndCompound( - mayc.address, - [user1.address, user2.address, user3.address], - [[0], [1], [2]], - {gasLimit: 5000000} - ) - ); - }); - - it("claimPairedApeRewardAndCompound function work as expected", async () => { - const { - users: [user1, user2], - mayc, - pool, - ape, - bakc, - } = await loadFixture(fixture); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await bakc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await bakc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await bakc.connect(user1.signer)["mint(address)"](user1.address) - ); - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await bakc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool.connect(user1.signer).supplyERC721( - mayc.address, - [ - {tokenId: 0, useAsCollateral: true}, - {tokenId: 1, useAsCollateral: true}, - {tokenId: 2, useAsCollateral: true}, - ], - user1.address, - "0" - ) - ); - await waitForTx( - await pool.connect(user1.signer).supplyERC721( - bakc.address, - [ - {tokenId: 0, useAsCollateral: true}, - {tokenId: 1, useAsCollateral: true}, - {tokenId: 2, useAsCollateral: true}, - ], - user1.address, - "0" - ) - ); - - const totalAmount = parseEther("900"); - const userAmount = parseEther("300"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: totalAmount, - }, - [], - [ - {mainTokenId: 0, bakcTokenId: 0, amount: userAmount}, - {mainTokenId: 1, bakcTokenId: 1, amount: userAmount}, - {mainTokenId: 2, bakcTokenId: 2, amount: userAmount}, - ] - ) - ); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool.connect(user2.signer).claimPairedApeAndCompound( - mayc.address, - [user1.address], - [ - [ - {mainTokenId: 0, bakcTokenId: 0}, - {mainTokenId: 1, bakcTokenId: 1}, - {mainTokenId: 2, bakcTokenId: 2}, - ], - ] - ) - ); - - //3600 * 0.997 = 3589.2 - const user1Balance = await pCApe.balanceOf(user1.address); - almostEqual(user1Balance, parseEther("3589.2")); - - // 3600 * 0.003 - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const incentiveBalance = await cApe.balanceOf(treasuryAddress); - almostEqual(incentiveBalance, parseEther("10.8")); - - await advanceTimeAndBlock(3600); - - await waitForTx( - await pool.connect(user2.signer).claimPairedApeAndCompound( - mayc.address, - [user1.address], - [ - [ - {mainTokenId: 0, bakcTokenId: 0}, - {mainTokenId: 1, bakcTokenId: 1}, - {mainTokenId: 2, bakcTokenId: 2}, - ], - ] - ) - ); - }); - it("bufferBalance work as expected", async () => { const { users: [user1, user2], @@ -1037,108 +484,6 @@ describe("Auto Compound Ape Test", () => { almostEqual(await ape.balanceOf(user2.address), user2Amount); }); - it("borrow cape and stake function work as expected: use 100% debt", async () => { - const { - users: [user1, user2], - mayc, - pool, - ape, - } = await loadFixture(fixture); - - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, user2Amount) - ); - - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, user2Amount) - ); - - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, user2Amount, user2.address, 0) - ); - - almostEqual(await pCApe.balanceOf(user2.address), user2Amount); - - await waitForTx( - await mayc.connect(user1.signer)["mint(address)"](user1.address) - ); - - await waitForTx( - await mayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - await waitForTx( - await pool - .connect(user1.signer) - .supplyERC721( - mayc.address, - [{tokenId: 0, useAsCollateral: true}], - user1.address, - "0" - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: user1Amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: user1Amount}], - [] - ) - ); - - const user2pCApeBalance = await pCApe.balanceOf(user2.address); - almostEqual(user2pCApeBalance, user2Amount); - let user1CApeDebtBalance = await variableDebtCAPE.balanceOf(user1.address); - almostEqual(user1CApeDebtBalance, user1Amount); - almostEqual(await pSApeCoin.balanceOf(user1.address), user1Amount); - almostEqual(await pCApe.balanceOf(user2.address), user2Amount); - almostEqual(await cApe.totalSupply(), user2Amount.sub(user1Amount)); - - const hourRewardAmount = parseEther("3600"); - await advanceTimeAndBlock(3600); - await waitForTx(await cApe.connect(user2.signer).harvestAndCompound()); - //this is a edge case here, because Ape single pool only got deposited by user2 - almostEqual( - await pCApe.balanceOf(user2.address), - user2Amount.add(hourRewardAmount.mul(2)) - ); - - user1CApeDebtBalance = await variableDebtCAPE.balanceOf(user1.address); - almostEqual(user1CApeDebtBalance, user1Amount.add(hourRewardAmount)); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: user1Amount}]) - ); - const apeBalance = await ape.balanceOf(user1.address); - //user1Amount + borrow user1Amount + hourRewardAmount - almostEqual(apeBalance, user1Amount.mul(2).add(hourRewardAmount)); - - await waitForTx( - await cApe.connect(user1.signer).deposit(user1.address, apeBalance) - ); - - await waitForTx( - await cApe.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user1.signer) - .repay(cApe.address, apeBalance, user1.address) - ); - user1CApeDebtBalance = await variableDebtCAPE.balanceOf(user1.address); - expect(user1CApeDebtBalance).to.be.equal(0); - - almostEqual(await cApe.balanceOf(user1.address), user1Amount); - }); - it("test vote delegation", async () => { const { users: [user1], diff --git a/test/vault_ape_staking.spec.ts b/test/vault_ape_staking.spec.ts new file mode 100644 index 000000000..bbc6166e4 --- /dev/null +++ b/test/vault_ape_staking.spec.ts @@ -0,0 +1,703 @@ +import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; +import {expect} from "chai"; +import {MAX_UINT_AMOUNT} from "../helpers/constants"; +import {getAutoCompoundApe, getVault} from "../helpers/contracts-getters"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; +import {AutoCompoundApe, IVault} from "../types"; +import {TestEnv} from "./helpers/make-suite"; +import {testEnvFixture} from "./helpers/setup-env"; + +import {mintAndValidate} from "./helpers/validated-steps"; +import {ProtocolErrors} from "../helpers/types"; +import {parseEther} from "ethers/lib/utils"; +import {deployVault} from "../helpers/contracts-deployments"; + +describe("Vault Ape staking Test", () => { + let testEnv: TestEnv; + let vaultProxy: IVault; + let cApe: AutoCompoundApe; + let MINIMUM_LIQUIDITY; + + const fixture = async () => { + testEnv = await loadFixture(testEnvFixture); + const { + ape, + users: [user1, , , user4, , user6], + apeCoinStaking, + poolAdmin, + } = testEnv; + + await deployVault(); + vaultProxy = await getVault(); + + await waitForTx( + await vaultProxy.connect(poolAdmin.signer).setApeStakingBot(user4.address) + ); + + cApe = await getAutoCompoundApe(); + MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); + + // send extra tokens to the apestaking contract for rewards + await waitForTx( + await ape + .connect(user1.signer) + ["mint(address,uint256)"]( + apeCoinStaking.address, + parseEther("100000000000") + ) + ); + + // user6 deposit MINIMUM_LIQUIDITY to make test case easy + await mintAndValidate(ape, "1", user6); + await waitForTx( + await ape.connect(user6.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe.connect(user6.signer).deposit(user6.address, MINIMUM_LIQUIDITY) + ); + + // user4 deposit and supply cApe to MM + await mintAndValidate(ape, "10000000000", user4); + await waitForTx( + await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) + ); + await waitForTx( + await cApe + .connect(user4.signer) + .deposit(user4.address, parseEther("10000000000")) + ); + + return testEnv; + }; + + it("test basic logic", async () => { + const { + users: [user1, user2, user3, user4], + bayc, + mayc, + bakc, + apeCoinStaking, + poolAdmin, + } = await loadFixture(fixture); + + await waitForTx( + await vaultProxy.connect(poolAdmin.signer).setCompoundFeeRate(1000) + ); + await waitForTx( + await vaultProxy + .connect(poolAdmin.signer) + .setCApeIncomeRate(bayc.address, 5000) + ); + await waitForTx( + await vaultProxy + .connect(poolAdmin.signer) + .setCApeIncomeRate(mayc.address, 5000) + ); + await waitForTx( + await vaultProxy + .connect(poolAdmin.signer) + .setCApeIncomeRate(bakc.address, 5000) + ); + + await mintAndValidate(bayc, "3", user1); + await mintAndValidate(mayc, "3", user2); + await mintAndValidate(bakc, "3", user3); + + const cApeTotalSupplyBefore = await cApe.totalSupply(); + + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + await waitForTx( + await mayc + .connect(user2.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + await waitForTx( + await bakc + .connect(user3.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + + await waitForTx( + await vaultProxy + .connect(user1.signer) + .onboardNFTs(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy + .connect(user2.signer) + .onboardNFTs(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy + .connect(user3.signer) + .onboardNFTs(bakc.address, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy.connect(user4.signer).stakingApe(true, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy.connect(user4.signer).stakingApe(false, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [2], + bakcPairMaycTokenIds: [2], + }) + ); + expect((await apeCoinStaking.nftPosition(1, 0)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(1, 1)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(1, 2)).stakedAmount).to.be.eq( + parseEther("200000") + ); + expect((await apeCoinStaking.nftPosition(2, 0)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(2, 1)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(2, 2)).stakedAmount).to.be.eq( + parseEther("100000") + ); + expect((await apeCoinStaking.nftPosition(3, 0)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 1)).stakedAmount).to.be.eq( + parseEther("50000") + ); + expect((await apeCoinStaking.nftPosition(3, 2)).stakedAmount).to.be.eq( + parseEther("50000") + ); + + expect(await cApe.nftStakingBalance()).to.be.closeTo( + parseEther("1050000"), + parseEther("10") + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await vaultProxy.connect(user4.signer).compoundApe(true, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy.connect(user4.signer).compoundApe(false, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy.connect(user4.signer).compoundBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [2], + bakcPairMaycTokenIds: [2], + }) + ); + let compoundFee = await vaultProxy.compoundFee(); + expect(compoundFee).to.be.closeTo(parseEther("1080"), parseEther("10")); + + const user1PendingReward = await vaultProxy.getPendingReward( + bayc.address, + [0, 1, 2] + ); + const user2PendingReward = await vaultProxy.getPendingReward( + mayc.address, + [0, 1, 2] + ); + const user3PendingReward = await vaultProxy.getPendingReward( + bakc.address, + [0, 1, 2] + ); + expect(user1PendingReward).to.be.closeTo( + parseEther("1620"), + parseEther("100") + ); + expect(user2PendingReward).to.be.closeTo( + parseEther("1620"), + parseEther("100") + ); + expect(user3PendingReward).to.be.closeTo( + parseEther("1620"), + parseEther("100") + ); + + await waitForTx( + await vaultProxy + .connect(user1.signer) + .claimPendingReward(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy + .connect(user2.signer) + .claimPendingReward(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy + .connect(user3.signer) + .claimPendingReward(bakc.address, [0, 1, 2]) + ); + let user1Balance = await cApe.balanceOf(user1.address); + let user2Balance = await cApe.balanceOf(user2.address); + let user3Balance = await cApe.balanceOf(user3.address); + expect(user1Balance).to.be.closeTo(user1PendingReward, parseEther("100")); + expect(user2Balance).to.be.closeTo(user2PendingReward, parseEther("100")); + expect(user3Balance).to.be.closeTo(user3PendingReward, parseEther("100")); + + const newUser1PendingReward = await vaultProxy.getPendingReward( + bayc.address, + [0, 1, 2] + ); + const newUser2PendingReward = await vaultProxy.getPendingReward( + mayc.address, + [0, 1, 2] + ); + const newUser3PendingReward = await vaultProxy.getPendingReward( + bakc.address, + [0, 1, 2] + ); + expect(newUser1PendingReward).to.be.equal(0); + expect(newUser2PendingReward).to.be.equal(0); + expect(newUser3PendingReward).to.be.equal(0); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await vaultProxy + .connect(user1.signer) + .offboardNFTs(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy + .connect(user2.signer) + .offboardNFTs(mayc.address, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy + .connect(user3.signer) + .offboardNFTs(bakc.address, [0, 1, 2]) + ); + expect(await bayc.ownerOf(0)).to.be.equal(user1.address); + expect(await bayc.ownerOf(1)).to.be.equal(user1.address); + expect(await bayc.ownerOf(2)).to.be.equal(user1.address); + expect(await mayc.ownerOf(0)).to.be.equal(user2.address); + expect(await mayc.ownerOf(1)).to.be.equal(user2.address); + expect(await mayc.ownerOf(2)).to.be.equal(user2.address); + expect(await bakc.ownerOf(0)).to.be.equal(user3.address); + expect(await bakc.ownerOf(1)).to.be.equal(user3.address); + expect(await bakc.ownerOf(2)).to.be.equal(user3.address); + + //1080 + 1080 + compoundFee = await vaultProxy.compoundFee(); + expect(compoundFee).to.be.closeTo(parseEther("2160"), parseEther("100")); + + const compoundFeeBalanceBefore = await cApe.balanceOf(user4.address); + await waitForTx( + await vaultProxy.connect(user4.signer).claimCompoundFee(user4.address) + ); + const compoundFeeBalanceAfter = await cApe.balanceOf(user4.address); + expect(compoundFeeBalanceAfter.sub(compoundFeeBalanceBefore)).to.be.closeTo( + compoundFee, + parseEther("1") + ); + + //withdraw cannot claim pending reward + user1Balance = await cApe.balanceOf(user1.address); + user2Balance = await cApe.balanceOf(user2.address); + user3Balance = await cApe.balanceOf(user3.address); + expect(user1Balance).to.be.closeTo( + user1PendingReward.mul(2), + parseEther("1") + ); + expect(user2Balance).to.be.closeTo( + user2PendingReward.mul(2), + parseEther("1") + ); + expect(user3Balance).to.be.closeTo( + user3PendingReward.mul(2), + parseEther("10") + ); + + expect(await cApe.nftStakingBalance()).to.be.closeTo( + parseEther("0"), + parseEther("10") + ); + + expect(await cApe.balanceOf(vaultProxy.address)).to.be.closeTo( + "0", + parseEther("10") + ); + + const cApeTotalSupplyAfter = await cApe.totalSupply(); + //3600 * 4 * 2 = 28800 + expect(cApeTotalSupplyAfter.sub(cApeTotalSupplyBefore)).to.be.closeTo( + parseEther("28800"), + parseEther("100") + ); + }); + + it("onboard revert test", async () => { + const { + users: [user1], + ape, + bayc, + bakc, + apeCoinStaking, + } = await loadFixture(fixture); + + await expect( + vaultProxy + .connect(user1.signer) + .onboardCheckApeStakingPosition(bayc.address, [0], user1.address) + ).to.be.revertedWith(ProtocolErrors.INVALID_CALLER); + + await mintAndValidate(bayc, "3", user1); + await mintAndValidate(bakc, "3", user1); + await mintAndValidate(ape, "10000", user1); + await waitForTx( + await ape + .connect(user1.signer) + .approve(apeCoinStaking.address, MAX_UINT_AMOUNT) + ); + await apeCoinStaking + .connect(user1.signer) + .depositBAYC([{tokenId: 0, amount: parseEther("100")}]); + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + await expect( + vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0]) + ).to.be.revertedWith(ProtocolErrors.ALREADY_STAKING); + + await apeCoinStaking.connect(user1.signer).depositBAKC( + [ + { + mainTokenId: 1, + bakcTokenId: 1, + amount: parseEther("100"), + }, + ], + [] + ); + await expect( + vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [1]) + ).to.be.revertedWith(ProtocolErrors.ALREADY_STAKING); + }); + + it("stakingApe revert test", async () => { + const { + users: [user1, , , user4], + bayc, + } = await loadFixture(fixture); + + await mintAndValidate(bayc, "1", user1); + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + + await expect( + vaultProxy.connect(user4.signer).stakingApe(true, [0]) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); + + await waitForTx( + await vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0]) + ); + + await waitForTx( + await vaultProxy.connect(user4.signer).stakingApe(true, [0]) + ); + }); + + it("stakingBAKC revert test", async () => { + const { + users: [user1, , , user4], + bayc, + mayc, + bakc, + } = await loadFixture(fixture); + + await mintAndValidate(bayc, "1", user1); + await mintAndValidate(mayc, "1", user1); + await mintAndValidate(bakc, "2", user1); + + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + await waitForTx( + await mayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + await waitForTx( + await bakc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + + await expect( + vaultProxy.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0], + bakcPairBaycTokenIds: [0], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); + + await expect( + vaultProxy.connect(user4.signer).stakingBAKC({ + baycTokenIds: [], + bakcPairBaycTokenIds: [], + maycTokenIds: [0], + bakcPairMaycTokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); + + await waitForTx( + await vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0]) + ); + + await waitForTx( + await vaultProxy.connect(user1.signer).onboardNFTs(mayc.address, [0]) + ); + + await waitForTx( + await vaultProxy.connect(user1.signer).onboardNFTs(bakc.address, [0, 1]) + ); + + await waitForTx( + await vaultProxy.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0], + bakcPairBaycTokenIds: [0], + maycTokenIds: [0], + bakcPairMaycTokenIds: [1], + }) + ); + + await expect( + vaultProxy.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0], + bakcPairBaycTokenIds: [0], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ).to.be.revertedWith(ProtocolErrors.ALREADY_STAKING); + + await expect( + vaultProxy.connect(user4.signer).stakingBAKC({ + baycTokenIds: [], + bakcPairBaycTokenIds: [], + maycTokenIds: [0], + bakcPairMaycTokenIds: [0], + }) + ).to.be.revertedWith(ProtocolErrors.ALREADY_STAKING); + }); + + it("compoundApe revert test", async () => { + const { + users: [user1, , , user4], + bayc, + } = await loadFixture(fixture); + + await mintAndValidate(bayc, "3", user1); + + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + + await waitForTx( + await vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0, 1]) + ); + + await waitForTx( + await vaultProxy.connect(user4.signer).stakingApe(true, [0, 1]) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await expect(vaultProxy.connect(user4.signer).compoundApe(true, [2])).to.be + .reverted; + + await waitForTx( + await vaultProxy.connect(user4.signer).compoundApe(true, [0, 1]) + ); + }); + + it("compoundBAKC revert test", async () => { + const { + users: [user1, , , user4], + bayc, + bakc, + } = await loadFixture(fixture); + + await mintAndValidate(bayc, "3", user1); + await mintAndValidate(bakc, "3", user1); + + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + await waitForTx( + await bakc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + + await waitForTx( + await vaultProxy + .connect(user1.signer) + .onboardNFTs(bayc.address, [0, 1, 2]) + ); + await waitForTx( + await vaultProxy + .connect(user1.signer) + .onboardNFTs(bakc.address, [0, 1, 2]) + ); + + await waitForTx( + await vaultProxy.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await expect( + vaultProxy.connect(user4.signer).compoundBAKC({ + baycTokenIds: [2], + bakcPairBaycTokenIds: [1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ).to.be.reverted; + + await expect( + vaultProxy.connect(user4.signer).compoundBAKC({ + baycTokenIds: [1], + bakcPairBaycTokenIds: [2], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ).to.be.reverted; + + await waitForTx( + await vaultProxy.connect(user4.signer).compoundBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ); + }); + + it("claimNFT revert test", async () => { + const { + users: [user1, user2, , user4], + bayc, + bakc, + } = await loadFixture(fixture); + + await mintAndValidate(bayc, "3", user1); + await mintAndValidate(bakc, "3", user1); + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + await waitForTx( + await bakc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + + await waitForTx( + await vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0, 1]) + ); + await waitForTx( + await vaultProxy.connect(user1.signer).onboardNFTs(bakc.address, [0, 1]) + ); + + await waitForTx( + await vaultProxy.connect(user4.signer).stakingApe(true, [0, 1]) + ); + await waitForTx( + await vaultProxy.connect(user4.signer).stakingBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + await waitForTx( + await vaultProxy.connect(user4.signer).compoundApe(true, [0, 1]) + ); + + await waitForTx( + await vaultProxy.connect(user4.signer).compoundBAKC({ + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [], + bakcPairMaycTokenIds: [], + }) + ); + + await expect( + vaultProxy.connect(user2.signer).claimPendingReward(bayc.address, [0, 1]) + ).to.be.revertedWith(ProtocolErrors.INVALID_CALLER); + + await expect( + vaultProxy.connect(user1.signer).claimPendingReward(bayc.address, [2]) + ).to.be.revertedWith(ProtocolErrors.INVALID_CALLER); + + await expect( + vaultProxy.connect(user2.signer).claimPendingReward(bakc.address, [0, 1]) + ).to.be.revertedWith(ProtocolErrors.INVALID_CALLER); + + await expect( + vaultProxy.connect(user1.signer).claimPendingReward(bakc.address, [2]) + ).to.be.revertedWith(ProtocolErrors.INVALID_CALLER); + + const pendingReward = await vaultProxy.getPendingReward( + bayc.address, + [0, 1] + ); + + //check repeated token id + const balanceBefore = await cApe.balanceOf(user1.address); + await waitForTx( + await vaultProxy + .connect(user1.signer) + .claimPendingReward(bayc.address, [0, 1, 0, 1]) + ); + const balanceAfter = await cApe.balanceOf(user1.address); + expect(balanceAfter.sub(balanceBefore)).to.be.closeTo( + pendingReward, + parseEther("10") + ); + + await waitForTx( + await vaultProxy + .connect(user1.signer) + .claimPendingReward(bakc.address, [0, 1]) + ); + }); +}); From 9fd63ea6810ce530cbd27597a7201c10d66d16f0 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 22 Dec 2023 10:36:03 +0800 Subject: [PATCH 5/9] chore: vault support multicall --- contracts/cross-chain/L1/IVault.sol | 8 +- contracts/cross-chain/L1/IVaultCommon.sol | 8 ++ contracts/cross-chain/L1/VaultCommon.sol | 31 ++++++ contracts/cross-chain/L1/VaultTemplate.sol | 9 -- helpers/contracts-deployments.ts | 29 ++++++ helpers/types.ts | 1 + test/vault_ape_staking.spec.ts | 109 +++++++++++++++++++++ 7 files changed, 185 insertions(+), 10 deletions(-) create mode 100644 contracts/cross-chain/L1/IVaultCommon.sol create mode 100644 contracts/cross-chain/L1/VaultCommon.sol diff --git a/contracts/cross-chain/L1/IVault.sol b/contracts/cross-chain/L1/IVault.sol index 702e5cbc9..2d87bdaa9 100644 --- a/contracts/cross-chain/L1/IVault.sol +++ b/contracts/cross-chain/L1/IVault.sol @@ -3,6 +3,12 @@ pragma solidity ^0.8.0; import "./IVaultApeStaking.sol"; import "./IVaultTemplate.sol"; +import "./IVaultCommon.sol"; import "../../interfaces/IParaProxyInterfaces.sol"; -interface IVault is IVaultApeStaking, IVaultTemplate, IParaProxyInterfaces {} +interface IVault is + IVaultApeStaking, + IVaultTemplate, + IVaultCommon, + IParaProxyInterfaces +{} diff --git a/contracts/cross-chain/L1/IVaultCommon.sol b/contracts/cross-chain/L1/IVaultCommon.sol new file mode 100644 index 000000000..d711c1571 --- /dev/null +++ b/contracts/cross-chain/L1/IVaultCommon.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IVaultCommon { + function multicall( + bytes[] calldata data + ) external returns (bytes[] memory results); +} diff --git a/contracts/cross-chain/L1/VaultCommon.sol b/contracts/cross-chain/L1/VaultCommon.sol new file mode 100644 index 000000000..08a6be926 --- /dev/null +++ b/contracts/cross-chain/L1/VaultCommon.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../dependencies/openzeppelin/contracts/ReentrancyGuard.sol"; +import "../../dependencies/openzeppelin/contracts//Pausable.sol"; +import "../../dependencies/openzeppelin/contracts/Address.sol"; +import "./IVaultCommon.sol"; + +contract VaultCommon is ReentrancyGuard, Pausable, IVaultCommon { + /** + * @dev Receives and executes a batch of function calls on this contract. + */ + function multicall( + bytes[] calldata data + ) external virtual returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + results[i] = Address.functionDelegateCall(address(this), data[i]); + } + return results; + } + + function onERC721Received( + address, + address, + uint256, + bytes memory + ) external pure returns (bytes4) { + return this.onERC721Received.selector; + } +} diff --git a/contracts/cross-chain/L1/VaultTemplate.sol b/contracts/cross-chain/L1/VaultTemplate.sol index 9170d8307..113590e45 100644 --- a/contracts/cross-chain/L1/VaultTemplate.sol +++ b/contracts/cross-chain/L1/VaultTemplate.sol @@ -39,13 +39,4 @@ contract VaultTemplate is ReentrancyGuard, Pausable, IVaultTemplate { ); IERC721(nft).safeTransferFrom(address(this), msg.sender, tokenId); } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) external pure returns (bytes4) { - return this.onERC721Received.selector; - } } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 0f9d1cd60..f20147c62 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -145,6 +145,8 @@ import { VariableDebtToken, VaultApeStaking, VaultApeStaking__factory, + VaultCommon, + VaultCommon__factory, VaultTemplate, VaultTemplate__factory, WalletBalanceProvider, @@ -341,6 +343,7 @@ export const deployACLManager = async ( ) as Promise; export const getVaultSignatures = () => { + const commonSelectors = getFunctionSignatures(VaultCommon__factory.abi); const apeStakingSelectors = getFunctionSignatures( VaultApeStaking__factory.abi ); @@ -353,6 +356,7 @@ export const getVaultSignatures = () => { const allSelectors = {}; const vaultSelectors = [ + ...commonSelectors, ...apeStakingSelectors, ...templateSelectors, ...paraProxyInterfacesSelectors, @@ -370,6 +374,7 @@ export const getVaultSignatures = () => { } return { + commonSelectors, apeStakingSelectors, templateSelectors, paraProxyInterfacesSelectors, @@ -411,6 +416,23 @@ export const deployVaultApeStaking = async (verify?: boolean) => { }; }; +export const deployVaultCommon = async (verify?: boolean) => { + const {commonSelectors} = getVaultSignatures(); + + const vaultCommon = (await withSaveAndVerify( + await getContractFactory("VaultCommon"), + eContractid.VaultCommon, + [], + verify, + false + )) as VaultCommon; + + return { + vaultCommon, + vaultCommonSelectors: commonSelectors.map((s) => s.signature), + }; +}; + export const deployVaultTemplate = async (verify?: boolean) => { const {templateSelectors} = getVaultSignatures(); @@ -468,6 +490,8 @@ export const deployVault = async (verify?: boolean) => { const proxy = await getVaultProxy(proxyAddress); + const {vaultCommon, vaultCommonSelectors} = await deployVaultCommon(verify); + const {vaultApeStaking, apeStakingSelectors} = await deployVaultApeStaking( verify ); @@ -481,6 +505,11 @@ export const deployVault = async (verify?: boolean) => { await proxy.updateImplementation( [ + { + implAddress: vaultCommon.address, + action: 0, + functionSelectors: vaultCommonSelectors, + }, { implAddress: vaultApeStaking.address, action: 0, diff --git a/helpers/types.ts b/helpers/types.ts index d0e3fb8ce..f9302d3e6 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -237,6 +237,7 @@ export enum eContractid { PoolApeStakingImpl = "PoolApeStakingImpl", PoolBorrowAndStakeImpl = "PoolBorrowAndStakeImpl", VaultProxy = "VaultProxy", + VaultCommon = "VaultCommon", VaultApeStaking = "VaultApeStaking", VaultTemplate = "VaultTemplate", VaultProxyInterfacesImpl = "VaultProxyInterfacesImpl", diff --git a/test/vault_ape_staking.spec.ts b/test/vault_ape_staking.spec.ts index bbc6166e4..39c7782da 100644 --- a/test/vault_ape_staking.spec.ts +++ b/test/vault_ape_staking.spec.ts @@ -700,4 +700,113 @@ describe("Vault Ape staking Test", () => { .claimPendingReward(bakc.address, [0, 1]) ); }); + + it("multicall test", async () => { + const { + users: [user1, , , user4], + bayc, + mayc, + bakc, + } = await loadFixture(fixture); + + await mintAndValidate(bayc, "4", user1); + await mintAndValidate(mayc, "4", user1); + await mintAndValidate(bakc, "4", user1); + await waitForTx( + await bayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + await waitForTx( + await mayc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + await waitForTx( + await bakc + .connect(user1.signer) + .setApprovalForAll(vaultProxy.address, true) + ); + + let tx0 = vaultProxy.interface.encodeFunctionData("onboardNFTs", [ + bayc.address, + [0, 1, 2], + ]); + let tx1 = vaultProxy.interface.encodeFunctionData("onboardNFTs", [ + mayc.address, + [0, 1, 2], + ]); + let tx2 = vaultProxy.interface.encodeFunctionData("onboardNFTs", [ + bakc.address, + [0, 1, 2], + ]); + + await waitForTx( + await vaultProxy.connect(user1.signer).multicall([tx0, tx1, tx2]) + ); + + tx0 = vaultProxy.interface.encodeFunctionData("stakingApe", [ + true, + [0, 1, 2], + ]); + tx1 = vaultProxy.interface.encodeFunctionData("stakingApe", [ + false, + [0, 1, 2], + ]); + tx2 = vaultProxy.interface.encodeFunctionData("stakingBAKC", [ + { + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [2], + bakcPairMaycTokenIds: [2], + }, + ]); + + await waitForTx( + await vaultProxy.connect(user4.signer).multicall([tx0, tx1, tx2]) + ); + + await advanceTimeAndBlock(parseInt("3600")); + + tx0 = vaultProxy.interface.encodeFunctionData("compoundApe", [ + true, + [0, 1, 2], + ]); + tx1 = vaultProxy.interface.encodeFunctionData("compoundApe", [ + false, + [0, 1, 2], + ]); + tx2 = vaultProxy.interface.encodeFunctionData("compoundBAKC", [ + { + baycTokenIds: [0, 1], + bakcPairBaycTokenIds: [0, 1], + maycTokenIds: [2], + bakcPairMaycTokenIds: [2], + }, + ]); + + await waitForTx( + await vaultProxy.connect(user4.signer).multicall([tx0, tx1, tx2]) + ); + + tx0 = vaultProxy.interface.encodeFunctionData("claimPendingReward", [ + bayc.address, + [0, 1, 2], + ]); + tx1 = vaultProxy.interface.encodeFunctionData("claimPendingReward", [ + mayc.address, + [0, 1, 2], + ]); + tx2 = vaultProxy.interface.encodeFunctionData("claimPendingReward", [ + bakc.address, + [0, 1, 2], + ]); + + await waitForTx( + await vaultProxy.connect(user1.signer).multicall([tx0, tx1, tx2]) + ); + + const user1Balance = await cApe.balanceOf(user1.address); + expect(user1Balance).to.be.closeTo(parseEther("10800"), parseEther("100")); + }); }); From f8dd49424a54517754b169cba234dab250e1e5b6 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 22 Dec 2023 16:36:55 +0800 Subject: [PATCH 6/9] chore: remove unused contract and test case for L2 --- contracts/apestaking/P2PPairStaking.sol | 707 ---- contracts/interfaces/INTokenApeStaking.sol | 46 - contracts/interfaces/IP2PPairStaking.sol | 176 - contracts/interfaces/IPool.sol | 6 - contracts/interfaces/IPoolApeStaking.sol | 128 - contracts/interfaces/IPoolBorrowAndStake.sol | 36 - contracts/interfaces/IPoolPositionMover.sol | 41 - contracts/misc/HelperContract.sol | 72 - .../libraries/logic/FlashClaimLogic.sol | 2 - .../protocol/libraries/logic/SupplyLogic.sol | 1 - .../libraries/logic/ValidationLogic.sol | 3 - contracts/protocol/pool/PoolApeStaking.sol | 854 ----- .../protocol/pool/PoolBorrowAndStake.sol | 321 -- contracts/protocol/pool/PoolPositionMover.sol | 157 - .../tokenization/NTokenChromieSquiggle.sol | 4 - .../libraries/ApeStakingLogic.sol | 293 -- .../libraries/MintableERC721Logic.sol | 1 + helpers/contracts-deployments.ts | 307 +- helpers/contracts-helpers.ts | 4 +- scripts/deployments/steps/06_pool.ts | 169 +- .../deployments/steps/20_p2pPairStaking.ts | 75 +- .../deployments/steps/21_helperContract.ts | 18 +- scripts/upgrade/pool.ts | 143 +- test/_pool_ape_staking.spec.ts | 2985 ----------------- test/_pool_borrow_and_stake.spec.ts | 304 -- test/_pool_position_mover.spec.ts | 217 -- test/_sape_pool_operation.spec.ts | 194 -- test/auto_compound_ape.spec.ts | 123 +- test/helper_contract.spec.ts | 132 - test/p2p_pair_staking.spec.ts | 1299 ------- test/xtoken_ntoken_bakc.spec.ts | 404 --- 31 files changed, 31 insertions(+), 9191 deletions(-) delete mode 100644 contracts/apestaking/P2PPairStaking.sol delete mode 100644 contracts/interfaces/INTokenApeStaking.sol delete mode 100644 contracts/interfaces/IP2PPairStaking.sol delete mode 100644 contracts/interfaces/IPoolApeStaking.sol delete mode 100644 contracts/interfaces/IPoolBorrowAndStake.sol delete mode 100644 contracts/interfaces/IPoolPositionMover.sol delete mode 100644 contracts/misc/HelperContract.sol delete mode 100644 contracts/protocol/pool/PoolApeStaking.sol delete mode 100644 contracts/protocol/pool/PoolBorrowAndStake.sol delete mode 100644 contracts/protocol/pool/PoolPositionMover.sol delete mode 100644 contracts/protocol/tokenization/libraries/ApeStakingLogic.sol delete mode 100644 test/_pool_ape_staking.spec.ts delete mode 100644 test/_pool_borrow_and_stake.spec.ts delete mode 100644 test/_pool_position_mover.spec.ts delete mode 100644 test/_sape_pool_operation.spec.ts delete mode 100644 test/helper_contract.spec.ts delete mode 100644 test/p2p_pair_staking.spec.ts delete mode 100644 test/xtoken_ntoken_bakc.spec.ts diff --git a/contracts/apestaking/P2PPairStaking.sol b/contracts/apestaking/P2PPairStaking.sol deleted file mode 100644 index ccd310269..000000000 --- a/contracts/apestaking/P2PPairStaking.sol +++ /dev/null @@ -1,707 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import "../dependencies/openzeppelin/upgradeability/Initializable.sol"; -import "../dependencies/openzeppelin/upgradeability/OwnableUpgradeable.sol"; -import "../dependencies/openzeppelin/upgradeability/ReentrancyGuardUpgradeable.sol"; -import "../dependencies/yoga-labs/ApeCoinStaking.sol"; -import "../interfaces/IP2PPairStaking.sol"; -import "../dependencies/openzeppelin/contracts/SafeCast.sol"; -import "../interfaces/IAutoCompoundApe.sol"; -import "../interfaces/ICApe.sol"; -import {IERC721} from "../dependencies/openzeppelin/contracts/IERC721.sol"; -import {IERC20, SafeERC20} from "../dependencies/openzeppelin/contracts/SafeERC20.sol"; -import {PercentageMath} from "../protocol/libraries/math/PercentageMath.sol"; -import {SignatureChecker} from "../dependencies/looksrare/contracts/libraries/SignatureChecker.sol"; - -contract P2PPairStaking is - Initializable, - OwnableUpgradeable, - ReentrancyGuardUpgradeable, - IP2PPairStaking -{ - using SafeERC20 for IERC20; - using PercentageMath for uint256; - using SafeCast for uint256; - - //keccak256("ListingOrder(uint8 stakingType,address offerer,address token,uint256 tokenId,uint256 share,uint256 startTime,uint256 endTime)"); - bytes32 internal constant LISTING_ORDER_HASH = - 0x227f9dd14259caacdbcf45411b33cf1c018f31bd3da27e613a66edf8ae45814f; - - //keccak256("MatchedOrder(uint8 stakingType,address apeToken,uint32 apeTokenId,uint32 apeShare,uint32 bakcTokenId,uint32 bakcShare,address apeCoinOfferer,uint32 apeCoinShare,uint256 apePrincipleAmount)"); - bytes32 internal constant MATCHED_ORDER_HASH = - 0x7db3dae7d89c86e6881a66a131841305c008b207e41ff86a804b4bb056652808; - - //keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - bytes32 internal constant EIP712_DOMAIN = - 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; - - uint256 internal constant WAD = 1e18; - - address internal immutable bayc; - address internal immutable mayc; - address internal immutable bakc; - address internal immutable nBayc; - address internal immutable nMayc; - address internal immutable nBakc; - address internal immutable apeCoin; - address internal immutable cApe; - ApeCoinStaking internal immutable apeCoinStaking; - - bytes32 internal DOMAIN_SEPARATOR; - mapping(bytes32 => ListingOrderStatus) public listingOrderStatus; - mapping(bytes32 => MatchedOrder) public matchedOrders; - mapping(address => mapping(uint32 => uint256)) private apeMatchedCount; - mapping(address => uint256) private cApeShareBalance; - address public matchingOperator; - uint256 public compoundFee; - uint256 private baycMatchedCap; - uint256 private maycMatchedCap; - uint256 private bakcMatchedCap; - - constructor( - address _bayc, - address _mayc, - address _bakc, - address _nBayc, - address _nMayc, - address _nBakc, - address _apeCoin, - address _cApe, - address _apeCoinStaking - ) { - bayc = _bayc; - mayc = _mayc; - bakc = _bakc; - nBayc = _nBayc; - nMayc = _nMayc; - nBakc = _nBakc; - apeCoin = _apeCoin; - cApe = _cApe; - apeCoinStaking = ApeCoinStaking(_apeCoinStaking); - } - - function initialize() public initializer { - __Ownable_init(); - __ReentrancyGuard_init(); - - DOMAIN_SEPARATOR = keccak256( - abi.encode( - EIP712_DOMAIN, - //keccak256("ParaSpace"), - 0x88d989289235fb06c18e3c2f7ea914f41f773e86fb0073d632539f566f4df353, - //keccak256(bytes("1")), - 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, - block.chainid, - address(this) - ) - ); - 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 - ); - } - - //approve ApeCoin for cApe - allowance = IERC20(apeCoin).allowance(address(this), cApe); - if (allowance == 0) { - IERC20(apeCoin).safeApprove(cApe, type(uint256).max); - } - } - - function cancelListing( - ListingOrder calldata listingOrder - ) external nonReentrant { - require(msg.sender == listingOrder.offerer, "not order offerer"); - bytes32 orderHash = getListingOrderHash(listingOrder); - require( - listingOrderStatus[orderHash] != ListingOrderStatus.Cancelled, - "order already cancelled" - ); - listingOrderStatus[orderHash] = ListingOrderStatus.Cancelled; - - emit OrderCancelled(orderHash, listingOrder.offerer); - } - - function matchPairStakingList( - ListingOrder calldata apeOrder, - ListingOrder calldata apeCoinOrder - ) external nonReentrant returns (bytes32 orderHash) { - //1 validate all order - _validateApeOrder(apeOrder); - bytes32 apeCoinListingOrderHash = _validateApeCoinOrder(apeCoinOrder); - - //2 check if orders can match - require( - apeOrder.stakingType <= StakingType.MAYCStaking, - "invalid stake type" - ); - require( - apeOrder.stakingType == apeCoinOrder.stakingType, - "orders type match failed" - ); - require( - apeOrder.share + apeCoinOrder.share == - PercentageMath.PERCENTAGE_FACTOR, - "orders share match failed" - ); - - //3 transfer token - _handleApeTransfer(apeOrder); - uint256 apeAmount = _handleCApeTransferAndConvert(apeCoinOrder); - - //4 create match order - MatchedOrder memory matchedOrder = MatchedOrder({ - stakingType: apeOrder.stakingType, - apeToken: apeOrder.token, - apeTokenId: apeOrder.tokenId, - apeShare: apeOrder.share, - bakcTokenId: 0, - bakcShare: 0, - apeCoinOfferer: apeCoinOrder.offerer, - apeCoinShare: apeCoinOrder.share, - apePrincipleAmount: apeAmount, - apeCoinListingOrderHash: apeCoinListingOrderHash - }); - orderHash = getMatchedOrderHash(matchedOrder); - matchedOrders[orderHash] = matchedOrder; - apeMatchedCount[apeOrder.token][apeOrder.tokenId] += 1; - - //5 stake for ApeCoinStaking - ApeCoinStaking.SingleNft[] - memory singleNft = new ApeCoinStaking.SingleNft[](1); - singleNft[0].tokenId = apeOrder.tokenId; - singleNft[0].amount = apeAmount.toUint224(); - if (apeOrder.stakingType == StakingType.BAYCStaking) { - apeCoinStaking.depositBAYC(singleNft); - } else { - apeCoinStaking.depositMAYC(singleNft); - } - - //6 update ape coin listing order status - listingOrderStatus[apeCoinListingOrderHash] = ListingOrderStatus - .Matched; - - //7 emit event - emit PairStakingMatched(orderHash); - - return orderHash; - } - - function matchBAKCPairStakingList( - ListingOrder calldata apeOrder, - ListingOrder calldata bakcOrder, - ListingOrder calldata apeCoinOrder - ) external nonReentrant returns (bytes32 orderHash) { - //1 validate all order - _validateApeOrder(apeOrder); - _validateBakcOrder(bakcOrder); - bytes32 apeCoinListingOrderHash = _validateApeCoinOrder(apeCoinOrder); - - //2 check if orders can match - require( - apeOrder.stakingType == StakingType.BAKCPairStaking, - "invalid stake type" - ); - require( - apeOrder.stakingType == bakcOrder.stakingType && - apeOrder.stakingType == apeCoinOrder.stakingType, - "orders type match failed" - ); - require( - apeOrder.share + bakcOrder.share + apeCoinOrder.share == - PercentageMath.PERCENTAGE_FACTOR, - "share match failed" - ); - - //3 transfer token - _handleApeTransfer(apeOrder); - IERC721(bakc).safeTransferFrom(nBakc, address(this), bakcOrder.tokenId); - uint256 apeAmount = _handleCApeTransferAndConvert(apeCoinOrder); - - //4 create match order - MatchedOrder memory matchedOrder = MatchedOrder({ - stakingType: apeOrder.stakingType, - apeToken: apeOrder.token, - apeTokenId: apeOrder.tokenId, - apeShare: apeOrder.share, - bakcTokenId: bakcOrder.tokenId, - bakcShare: bakcOrder.share, - apeCoinOfferer: apeCoinOrder.offerer, - apeCoinShare: apeCoinOrder.share, - apePrincipleAmount: apeAmount, - apeCoinListingOrderHash: apeCoinListingOrderHash - }); - orderHash = getMatchedOrderHash(matchedOrder); - matchedOrders[orderHash] = matchedOrder; - apeMatchedCount[apeOrder.token][apeOrder.tokenId] += 1; - - //5 stake for ApeCoinStaking - ApeCoinStaking.PairNftDepositWithAmount[] - memory _stakingPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 1 - ); - _stakingPairs[0].mainTokenId = apeOrder.tokenId; - _stakingPairs[0].bakcTokenId = bakcOrder.tokenId; - _stakingPairs[0].amount = apeAmount.toUint184(); - ApeCoinStaking.PairNftDepositWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftDepositWithAmount[]( - 0 - ); - if (apeOrder.token == bayc) { - apeCoinStaking.depositBAKC(_stakingPairs, _otherPairs); - } else { - apeCoinStaking.depositBAKC(_otherPairs, _stakingPairs); - } - - //6 update ape coin listing order status - listingOrderStatus[apeCoinListingOrderHash] = ListingOrderStatus - .Matched; - - //7 emit event - emit PairStakingMatched(orderHash); - - return orderHash; - } - - function breakUpMatchedOrder(bytes32 orderHash) external nonReentrant { - MatchedOrder memory order = matchedOrders[orderHash]; - - //1 check if have permission to break up - address apeNToken = _getApeNTokenAddress(order.apeToken); - address apeNTokenOwner = IERC721(apeNToken).ownerOf(order.apeTokenId); - address nBakcOwner = IERC721(nBakc).ownerOf(order.bakcTokenId); - require( - msg.sender == matchingOperator || - msg.sender == apeNTokenOwner || - msg.sender == order.apeCoinOfferer || - (msg.sender == nBakcOwner && - order.stakingType == StakingType.BAKCPairStaking), - "no permission to break up" - ); - - //2 claim pending reward and compound - bytes32[] memory orderHashes = new bytes32[](1); - orderHashes[0] = orderHash; - _claimForMatchedOrdersAndCompound(orderHashes); - - //3 delete matched order - delete matchedOrders[orderHash]; - - //4 exit from ApeCoinStaking - if (order.stakingType < StakingType.BAKCPairStaking) { - ApeCoinStaking.SingleNft[] - memory _nfts = new ApeCoinStaking.SingleNft[](1); - _nfts[0].tokenId = order.apeTokenId; - _nfts[0].amount = order.apePrincipleAmount.toUint224(); - if (order.stakingType == StakingType.BAYCStaking) { - apeCoinStaking.withdrawSelfBAYC(_nfts); - } else { - apeCoinStaking.withdrawSelfMAYC(_nfts); - } - } else { - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _nfts = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 1 - ); - _nfts[0].mainTokenId = order.apeTokenId; - _nfts[0].bakcTokenId = order.bakcTokenId; - _nfts[0].amount = order.apePrincipleAmount.toUint184(); - _nfts[0].isUncommit = true; - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); - if (order.apeToken == bayc) { - apeCoinStaking.withdrawBAKC(_nfts, _otherPairs); - } else { - apeCoinStaking.withdrawBAKC(_otherPairs, _nfts); - } - } - //5 transfer token - uint256 matchedCount = apeMatchedCount[order.apeToken][ - order.apeTokenId - ]; - if (matchedCount == 1) { - IERC721(order.apeToken).safeTransferFrom( - address(this), - apeNToken, - order.apeTokenId - ); - } - apeMatchedCount[order.apeToken][order.apeTokenId] = matchedCount - 1; - - IAutoCompoundApe(cApe).deposit( - order.apeCoinOfferer, - order.apePrincipleAmount - ); - if (order.stakingType == StakingType.BAKCPairStaking) { - IERC721(bakc).safeTransferFrom( - address(this), - nBakc, - order.bakcTokenId - ); - } - - //6 reset ape coin listing order status - if ( - listingOrderStatus[order.apeCoinListingOrderHash] != - ListingOrderStatus.Cancelled - ) { - listingOrderStatus[ - order.apeCoinListingOrderHash - ] = ListingOrderStatus.Pending; - } - - //7 emit event - emit PairStakingBreakUp(orderHash); - } - - function claimForMatchedOrderAndCompound( - bytes32[] calldata orderHashes - ) external nonReentrant { - _claimForMatchedOrdersAndCompound(orderHashes); - } - - function _claimForMatchedOrdersAndCompound( - bytes32[] memory orderHashes - ) internal { - //ignore getShareByPooledApe return 0 case. - uint256 cApeExchangeRate = ICApe(cApe).getPooledApeByShares(WAD); - uint256 _compoundFee = compoundFee; - uint256 totalReward; - uint256 totalFeeShare; - uint256 orderCounts = orderHashes.length; - for (uint256 index = 0; index < orderCounts; index++) { - bytes32 orderHash = orderHashes[index]; - ( - uint256 reward, - uint256 feeShare - ) = _claimForMatchedOrderAndCompound( - orderHash, - cApeExchangeRate, - _compoundFee - ); - totalReward += reward; - totalFeeShare += feeShare; - } - if (totalReward > 0) { - IAutoCompoundApe(cApe).deposit(address(this), totalReward); - _depositCApeShareForUser(address(this), totalFeeShare); - } - } - - function claimCApeReward(address receiver) external nonReentrant { - uint256 cApeAmount = pendingCApeReward(msg.sender); - if (cApeAmount > 0) { - IERC20(cApe).safeTransfer(receiver, cApeAmount); - delete cApeShareBalance[msg.sender]; - emit CApeClaimed(msg.sender, receiver, cApeAmount); - } - } - - 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]; - if (shareBalance > 0) { - amount = ICApe(cApe).getPooledApeByShares(shareBalance); - } - return amount; - } - - function getApeCoinStakingCap( - StakingType stakingType - ) public view returns (uint256) { - if (stakingType == StakingType.BAYCStaking) { - return baycMatchedCap; - } else if (stakingType == StakingType.MAYCStaking) { - return maycMatchedCap; - } else { - return bakcMatchedCap; - } - } - - function getListingOrderHash( - ListingOrder calldata order - ) public pure returns (bytes32) { - return - keccak256( - abi.encode( - LISTING_ORDER_HASH, - order.stakingType, - order.offerer, - order.token, - order.tokenId, - order.share, - order.startTime, - order.endTime - ) - ); - } - - function getMatchedOrderHash( - MatchedOrder memory order - ) public pure returns (bytes32) { - return - keccak256( - abi.encode( - MATCHED_ORDER_HASH, - order.stakingType, - order.apeToken, - order.apeTokenId, - order.apeShare, - order.bakcTokenId, - order.bakcShare, - order.apeCoinOfferer, - order.apeCoinShare, - order.apePrincipleAmount - ) - ); - } - - function _getApeNTokenAddress( - address apeToken - ) internal view returns (address) { - if (apeToken == bayc) { - return nBayc; - } else if (apeToken == mayc) { - return nMayc; - } else { - revert("unsupported ape token"); - } - } - - function _claimForMatchedOrderAndCompound( - bytes32 orderHash, - uint256 cApeExchangeRate, - uint256 _compoundFee - ) internal returns (uint256, uint256) { - MatchedOrder memory order = matchedOrders[orderHash]; - uint256 balanceBefore = IERC20(apeCoin).balanceOf(address(this)); - if (order.stakingType < StakingType.BAKCPairStaking) { - uint256[] memory _nfts = new uint256[](1); - _nfts[0] = order.apeTokenId; - if (order.stakingType == StakingType.BAYCStaking) { - apeCoinStaking.claimSelfBAYC(_nfts); - } else { - apeCoinStaking.claimSelfMAYC(_nfts); - } - } else { - ApeCoinStaking.PairNft[] - memory _nfts = new ApeCoinStaking.PairNft[](1); - _nfts[0].mainTokenId = order.apeTokenId; - _nfts[0].bakcTokenId = order.bakcTokenId; - ApeCoinStaking.PairNft[] - memory _otherPairs = new ApeCoinStaking.PairNft[](0); - if (order.apeToken == bayc) { - apeCoinStaking.claimSelfBAKC(_nfts, _otherPairs); - } else { - apeCoinStaking.claimSelfBAKC(_otherPairs, _nfts); - } - } - uint256 balanceAfter = IERC20(apeCoin).balanceOf(address(this)); - uint256 rewardAmount = balanceAfter - balanceBefore; - if (rewardAmount == 0) { - return (0, 0); - } - - uint256 rewardShare = (rewardAmount * WAD) / cApeExchangeRate; - //compound fee - uint256 _compoundFeeShare = rewardShare.percentMul(_compoundFee); - rewardShare -= _compoundFeeShare; - - _depositCApeShareForUser( - IERC721(_getApeNTokenAddress(order.apeToken)).ownerOf( - order.apeTokenId - ), - rewardShare.percentMul(order.apeShare) - ); - _depositCApeShareForUser( - IERC721(nBakc).ownerOf(order.bakcTokenId), - rewardShare.percentMul(order.bakcShare) - ); - _depositCApeShareForUser( - order.apeCoinOfferer, - rewardShare.percentMul(order.apeCoinShare) - ); - - emit OrderClaimedAndCompounded(orderHash, rewardAmount); - - return (rewardAmount, _compoundFeeShare); - } - - function _depositCApeShareForUser(address user, uint256 amount) internal { - if (amount > 0) { - cApeShareBalance[user] += amount; - } - } - - function _handleApeTransfer(ListingOrder calldata order) internal { - address currentOwner = IERC721(order.token).ownerOf(order.tokenId); - if (currentOwner != address(this)) { - address nTokenAddress = _getApeNTokenAddress(order.token); - IERC721(order.token).safeTransferFrom( - nTokenAddress, - address(this), - order.tokenId - ); - } - } - - function _handleCApeTransferAndConvert( - ListingOrder calldata apeCoinOrder - ) internal returns (uint256) { - uint256 apeAmount = getApeCoinStakingCap(apeCoinOrder.stakingType); - IERC20(cApe).safeTransferFrom( - apeCoinOrder.offerer, - address(this), - apeAmount - ); - IAutoCompoundApe(cApe).withdraw(apeAmount); - return apeAmount; - } - - function _validateOrderBasicInfo( - ListingOrder calldata listingOrder - ) internal view returns (bytes32 orderHash) { - require( - listingOrder.startTime <= block.timestamp, - "ape order not start" - ); - require(listingOrder.endTime >= block.timestamp, "ape offer expired"); - - orderHash = getListingOrderHash(listingOrder); - require( - listingOrderStatus[orderHash] != ListingOrderStatus.Cancelled, - "order already cancelled" - ); - - if ( - msg.sender != listingOrder.offerer && msg.sender != matchingOperator - ) { - require( - validateOrderSignature( - listingOrder.offerer, - orderHash, - listingOrder.v, - listingOrder.r, - listingOrder.s - ), - "invalid signature" - ); - } - } - - function _validateApeOrder(ListingOrder calldata apeOrder) internal view { - _validateOrderBasicInfo(apeOrder); - - address nToken = _getApeNTokenAddress(apeOrder.token); - require( - IERC721(nToken).ownerOf(apeOrder.tokenId) == apeOrder.offerer, - "ape order invalid NToken owner" - ); - } - - function _validateBakcOrder(ListingOrder calldata bakcOrder) internal view { - _validateOrderBasicInfo(bakcOrder); - - require(bakcOrder.token == bakc, "bakc order invalid token"); - require( - IERC721(nBakc).ownerOf(bakcOrder.tokenId) == bakcOrder.offerer, - "bakc order invalid NToken owner" - ); - } - - function _validateApeCoinOrder( - ListingOrder calldata apeCoinOrder - ) internal view returns (bytes32 orderHash) { - orderHash = _validateOrderBasicInfo(apeCoinOrder); - require(apeCoinOrder.token == cApe, "ape coin order invalid token"); - require( - listingOrderStatus[orderHash] != ListingOrderStatus.Matched, - "ape coin order already matched" - ); - } - - function validateOrderSignature( - address signer, - bytes32 orderHash, - uint8 v, - bytes32 r, - bytes32 s - ) public view returns (bool) { - return - SignatureChecker.verify( - orderHash, - signer, - v, - r, - s, - DOMAIN_SEPARATOR - ); - } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) external pure returns (bytes4) { - return this.onERC721Received.selector; - } - - function 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, - "Fee Too High" - ); - uint256 oldValue = compoundFee; - if (oldValue != _compoundFee) { - compoundFee = _compoundFee; - emit CompoundFeeUpdated(oldValue, _compoundFee); - } - } - - function claimCompoundFee(address receiver) external onlyOwner { - this.claimCApeReward(receiver); - } - - function rescueERC20( - address token, - address to, - uint256 amount - ) external onlyOwner { - IERC20(token).safeTransfer(to, amount); - emit RescueERC20(token, to, amount); - } -} diff --git a/contracts/interfaces/INTokenApeStaking.sol b/contracts/interfaces/INTokenApeStaking.sol deleted file mode 100644 index 97d5583ff..000000000 --- a/contracts/interfaces/INTokenApeStaking.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.0; - -import "../dependencies/yoga-labs/ApeCoinStaking.sol"; -import "./INToken.sol"; - -interface INTokenApeStaking { - function getBAKC() external view returns (IERC721); - - function getApeStaking() external view returns (ApeCoinStaking); - - function depositApeCoin(ApeCoinStaking.SingleNft[] calldata _nfts) external; - - function claimApeCoin( - uint256[] calldata _nfts, - address _recipient - ) external; - - function withdrawApeCoin( - ApeCoinStaking.SingleNft[] calldata _nfts, - address _recipient - ) external; - - function depositBAKC( - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external; - - function claimBAKC( - ApeCoinStaking.PairNft[] calldata _nftPairs, - address _recipient - ) external; - - function withdrawBAKC( - ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs, - address _apeRecipient - ) external; - - function unstakePositionAndRepay( - uint256 tokenId, - address unstaker - ) external; - - function getUserApeStakingAmount( - address user - ) external view returns (uint256); -} diff --git a/contracts/interfaces/IP2PPairStaking.sol b/contracts/interfaces/IP2PPairStaking.sol deleted file mode 100644 index 7b59f1e29..000000000 --- a/contracts/interfaces/IP2PPairStaking.sol +++ /dev/null @@ -1,176 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; - -import "../dependencies/openzeppelin/contracts/IERC20.sol"; - -interface IP2PPairStaking { - enum StakingType { - BAYCStaking, - MAYCStaking, - BAKCPairStaking - } - - enum ListingOrderStatus { - Pending, - Matched, - Cancelled - } - - struct ListingOrder { - StakingType stakingType; - address offerer; - address token; - uint32 tokenId; - uint32 share; - uint256 startTime; - uint256 endTime; - uint8 v; - bytes32 r; - bytes32 s; - } - - struct MatchedOrder { - StakingType stakingType; - address apeToken; - uint32 apeTokenId; - uint32 apeShare; - uint32 bakcTokenId; - uint32 bakcShare; - address apeCoinOfferer; - uint32 apeCoinShare; - uint256 apePrincipleAmount; - bytes32 apeCoinListingOrderHash; - } - - /** - * @dev Emit an event whenever an listing order is successfully cancelled. - * @param orderHash The hash of the cancelled order. - * @param offerer The offerer of the cancelled order. - */ - event OrderCancelled(bytes32 orderHash, address indexed offerer); - - /** - * @dev Emitted when a order matched. - * @param orderHash The hash of the matched order - **/ - event PairStakingMatched(bytes32 orderHash); - - /** - * @dev Emitted when a matched order break up. - * @param orderHash The hash of the break up order - **/ - event PairStakingBreakUp(bytes32 orderHash); - - /** - * @dev Emitted when user claimed pending cApe reward. - * @param user The address of the user - * @param receiver The address of the cApe receiver - * @param amount The amount of the cApe been claimed - **/ - event CApeClaimed(address user, address receiver, uint256 amount); - - /** - * @dev Emitted when we claimed pending reward for matched order and compound. - * @param orderHash The hash of the break up order - **/ - event OrderClaimedAndCompounded(bytes32 orderHash, uint256 totalReward); - - /** - * @dev Emitted during rescueERC20() - * @param token The address of the token - * @param to The address of the recipient - * @param amount The amount being rescued - **/ - event RescueERC20( - address indexed token, - address indexed to, - uint256 amount - ); - - /** - * @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 - * @param newFee The value of the new compound fee - **/ - event CompoundFeeUpdated(uint256 oldFee, uint256 newFee); - - /** - * @notice Cancel a listing order, order canceled cannot be matched. - * @param listingOrder the detail info of the order to be canceled - */ - function cancelListing(ListingOrder calldata listingOrder) external; - - /** - * @notice match an apeOrder with an apeCoinOrder to pair staking - * @param apeOrder the ape owner's listing order - * @param apeCoinOrder the Ape Coin owner's listing order - * @return orderHash matched order hash - */ - function matchPairStakingList( - ListingOrder calldata apeOrder, - ListingOrder calldata apeCoinOrder - ) external returns (bytes32 orderHash); - - /** - * @notice match an apeOrder, an bakcOrder with an apeCoinOrder to pair staking - * @param apeOrder the ape owner's listing order - * @param bakcOrder the bakc owner's listing order - * @param apeCoinOrder the Ape Coin owner's listing order - * @return orderHash matched order hash - */ - function matchBAKCPairStakingList( - ListingOrder calldata apeOrder, - ListingOrder calldata bakcOrder, - ListingOrder calldata apeCoinOrder - ) external returns (bytes32 orderHash); - - /** - * @notice break up an matched pair staking order, only participant of the matched order can call. - * @param orderHash the hash of the matched order to be break up - */ - function breakUpMatchedOrder(bytes32 orderHash) external; - - /** - * @notice claim pending reward for matched pair staking orders and deposit as cApe for user to compound. - * @param orderHashes the hash of the matched orders to be break up - */ - function claimForMatchedOrderAndCompound( - bytes32[] calldata orderHashes - ) external; - - /** - * @param user The address of the user - * @return amount Returns the amount of cApe owned by user - */ - function pendingCApeReward( - address user - ) external view returns (uint256 amount); - - /** - * @notice claim user compounded cApe - * @param receiver The address of the cApe receiver - */ - function claimCApeReward(address receiver) external; - - /** - * @notice get Ape Coin Staking cap for every position. - * @param stakingType the pair staking type - * @return Ape Coin Staking cap - */ - function getApeCoinStakingCap( - StakingType stakingType - ) external returns (uint256); - - /** - * @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/contracts/interfaces/IPool.sol b/contracts/interfaces/IPool.sol index d326fc614..423b5e667 100644 --- a/contracts/interfaces/IPool.sol +++ b/contracts/interfaces/IPool.sol @@ -5,10 +5,7 @@ import {IPoolCore} from "./IPoolCore.sol"; import {IPoolMarketplace} from "./IPoolMarketplace.sol"; import {IPoolParameters} from "./IPoolParameters.sol"; import {IParaProxyInterfaces} from "./IParaProxyInterfaces.sol"; -import {IPoolPositionMover} from "./IPoolPositionMover.sol"; import {IPoolAAPositionMover} from "./IPoolAAPositionMover.sol"; -import "./IPoolApeStaking.sol"; -import "./IPoolBorrowAndStake.sol"; import "./IPoolCrossChain.sol"; /** @@ -20,10 +17,7 @@ interface IPool is IPoolCore, IPoolMarketplace, IPoolParameters, - IPoolApeStaking, IParaProxyInterfaces, - IPoolPositionMover, - IPoolBorrowAndStake, IPoolAAPositionMover, IPoolCrossChain { diff --git a/contracts/interfaces/IPoolApeStaking.sol b/contracts/interfaces/IPoolApeStaking.sol deleted file mode 100644 index 5763be332..000000000 --- a/contracts/interfaces/IPoolApeStaking.sol +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.0; - -import "../dependencies/yoga-labs/ApeCoinStaking.sol"; - -/** - * @title IPoolApeStaking - * - * @notice Defines the basic interface for an ParaSpace Ape Staking Pool. - **/ -interface IPoolApeStaking { - struct StakingInfo { - // Contract address of BAYC/MAYC - address nftAsset; - // address of borrowing asset, can be Ape or cApe - address borrowAsset; - // Borrow amount of Ape from lending pool - uint256 borrowAmount; - // Cash amount of Ape from user wallet - uint256 cashAmount; - } - - /** - * @notice Deposit ape coin to BAYC/MAYC pool or BAKC pool - * @param stakingInfo Detail info of the staking - * @param _nfts Array of BAYC/MAYC NFT's with staked amounts - * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts - * @dev Need check User health factor > 1. - */ - function borrowApeAndStake( - StakingInfo calldata stakingInfo, - ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external; - - /** - * @notice Withdraw staked ApeCoin from the BAYC/MAYC pool - * @param nftAsset Contract address of BAYC/MAYC - * @param _nfts Array of BAYC/MAYC NFT's with staked amounts - * @dev Need check User health factor > 1. - */ - function withdrawApeCoin( - address nftAsset, - ApeCoinStaking.SingleNft[] calldata _nfts - ) external; - - /** - * @notice Claim rewards for array of tokenIds from the BAYC/MAYC pool - * @param nftAsset Contract address of BAYC/MAYC - * @param _nfts Array of NFTs owned and committed by the msg.sender - * @dev Need check User health factor > 1. - */ - function claimApeCoin(address nftAsset, uint256[] calldata _nfts) external; - - /** - * @notice Withdraw staked ApeCoin from the BAKC pool - * @param nftAsset Contract address of BAYC/MAYC - * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts - * @dev Need check User health factor > 1. - */ - function withdrawBAKC( - address nftAsset, - ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs - ) external; - - /** - * @notice Claim rewards for array of tokenIds from the BAYC/MAYC pool - * @param nftAsset Contract address of BAYC/MAYC - * @param _nftPairs Array of Paired BAYC/MAYC NFT's - * @dev Need check User health factor > 1. - */ - function claimBAKC( - address nftAsset, - ApeCoinStaking.PairNft[] calldata _nftPairs - ) external; - - /** - * @notice Unstake user Ape coin staking position and repay user debt - * @param nftAsset Contract address of BAYC/MAYC - * @param tokenId Token id of the ape staking position on - * @dev Need check User health factor > 1. - */ - function unstakeApePositionAndRepay( - address nftAsset, - uint256 tokenId - ) external; - - /** - * @notice repay asset and supply asset for user - * @param underlyingAsset Contract address of BAYC/MAYC - * @param onBehalfOf The beneficiary of the repay and supply - * @dev Convenient callback function for unstakeApePositionAndRepay. Only NToken of BAYC/MAYC can call this. - */ - function repayAndSupply( - address underlyingAsset, - address onBehalfOf, - uint256 totalAmount - ) external; - - /** - * @notice Claim user Ape coin reward and deposit to ape compound to get cApe, then deposit cApe to Lending pool for user - * @param nftAsset Contract address of BAYC/MAYC - * @param users array of user address - * @param tokenIds array of user tokenId array - */ - function claimApeAndCompound( - address nftAsset, - address[] calldata users, - uint256[][] calldata tokenIds - ) external; - - /** - * @notice Claim user BAKC paired Ape coin reward and deposit to ape compound to get cApe, then deposit cApe to Lending pool for user - * @param nftAsset Contract address of BAYC/MAYC - * @param users array of user address - * @param _nftPairs Array of Paired BAYC/MAYC NFT's - */ - function claimPairedApeAndCompound( - address nftAsset, - address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs - ) external; - - /** - * @notice get current incentive fee rate for claiming ape position reward to compound - */ - function getApeCompoundFeeRate() external returns (uint256); -} diff --git a/contracts/interfaces/IPoolBorrowAndStake.sol b/contracts/interfaces/IPoolBorrowAndStake.sol deleted file mode 100644 index 0aba48622..000000000 --- a/contracts/interfaces/IPoolBorrowAndStake.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.0; - -import "../dependencies/yoga-labs/ApeCoinStaking.sol"; - -/** - * @title IPoolBorrowAndStake - * - * @notice Defines the basic interface for an ParaSpace Ape Staking Pool. - **/ -interface IPoolBorrowAndStake { - struct StakingInfoV2 { - // Contract address of BAYC/MAYC - address nftAsset; - // address of borrowing asset, can be Ape or cApe - address borrowAsset; - // Borrow amount of Ape from lending pool - uint256 borrowAmount; - address cashAsset; - // Cash amount of Ape from user wallet - uint256 cashAmount; - } - - /** - * @notice Deposit ape coin to BAYC/MAYC pool or BAKC pool - * @param stakingInfo Detail info of the staking - * @param _nfts Array of BAYC/MAYC NFT's with staked amounts - * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts - * @dev Need check User health factor > 1. - */ - function borrowApeAndStakeV2( - StakingInfoV2 calldata stakingInfo, - ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external; -} diff --git a/contracts/interfaces/IPoolPositionMover.sol b/contracts/interfaces/IPoolPositionMover.sol deleted file mode 100644 index e5171d88e..000000000 --- a/contracts/interfaces/IPoolPositionMover.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0 -pragma solidity ^0.8.0; - -import {DataTypes} from "../protocol/libraries/types/DataTypes.sol"; -import {ApeCoinStaking} from "../dependencies/yoga-labs/ApeCoinStaking.sol"; - -/** - * @title IPool - * - * @notice Defines the basic interface for an ParaSpace Pool. - **/ -interface IPoolPositionMover { - function movePositionFromBendDAO( - uint256[] calldata loanIds, - address to - ) external; - - //# Migration step - // - //0. User needs to breakup P2P orders on their own - //1. Repay Debt - // 1. if it's cAPE then deposit borrowed APE into old cAPE pool then repay - // 2. if it's not then just repay with borrowed tokens - //2. burn old NToken - // 1. move old NToken to new Pool, if it's staking BAYC/MAYC/BAKC it'll be automatically unstaked - // 2. withdrawERC721 and specify new NToken as recipient - // 3. mint new NToken - //3. burn old PToken - // 1. move old PToken to new Pool - // 2. withdraw and specify new PToken as recipient - // 3. mint new NToken - //4. Mint new debt - function movePositionFromParaSpace( - DataTypes.ParaSpacePositionMoveInfo calldata moveInfo - ) external; - - function claimUnderlying( - address[] calldata assets, - uint256[][] calldata agreementIds - ) external; -} diff --git a/contracts/misc/HelperContract.sol b/contracts/misc/HelperContract.sol deleted file mode 100644 index 42523506f..000000000 --- a/contracts/misc/HelperContract.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.0; - -import "../dependencies/openzeppelin/upgradeability/Initializable.sol"; -import "../dependencies/openzeppelin/upgradeability/OwnableUpgradeable.sol"; -import "../dependencies/openzeppelin/upgradeability/ReentrancyGuardUpgradeable.sol"; -import "../interfaces/IPoolCore.sol"; -import "../interfaces/IAutoCompoundApe.sol"; -import "../interfaces/ICApe.sol"; -import {IERC20, SafeERC20} from "../dependencies/openzeppelin/contracts/SafeERC20.sol"; - -contract HelperContract is Initializable, OwnableUpgradeable { - using SafeERC20 for IERC20; - - address internal immutable apeCoin; - address internal immutable cApeV1; - address internal immutable cApe; - address internal immutable pcApe; - address internal immutable lendingPool; - - constructor( - address _apeCoin, - address _cApeV1, - address _cApe, - address _pcApe, - address _lendingPool - ) { - apeCoin = _apeCoin; - cApeV1 = _cApeV1; - cApe = _cApe; - pcApe = _pcApe; - lendingPool = _lendingPool; - } - - function initialize() public initializer { - __Ownable_init(); - - //approve ApeCoin for cApe - uint256 allowance = IERC20(apeCoin).allowance(address(this), cApe); - if (allowance == 0) { - IERC20(apeCoin).safeApprove(cApe, type(uint256).max); - } - - //approve cApe for lendingPool - allowance = IERC20(cApe).allowance(address(this), lendingPool); - if (allowance == 0) { - IERC20(cApe).safeApprove(lendingPool, type(uint256).max); - } - } - - function convertApeCoinToPCApe(uint256 amount) external { - IERC20(apeCoin).safeTransferFrom(msg.sender, address(this), amount); - IAutoCompoundApe(cApe).deposit(address(this), amount); - IPoolCore(lendingPool).supply(cApe, amount, msg.sender, 0); - } - - function convertPCApeToApeCoin(uint256 amount) external { - IERC20(pcApe).safeTransferFrom(msg.sender, address(this), amount); - IPoolCore(lendingPool).withdraw(cApe, amount, address(this)); - IAutoCompoundApe(cApe).withdraw(amount); - IERC20(apeCoin).safeTransfer(msg.sender, amount); - } - - function cApeMigration(uint256 amount, address to) external { - if (amount == 0 || amount == type(uint256).max) { - amount = IERC20(cApeV1).balanceOf(msg.sender); - } - IERC20(cApeV1).safeTransferFrom(msg.sender, address(this), amount); - IAutoCompoundApe(cApeV1).withdraw(amount); - IAutoCompoundApe(cApe).deposit(to, amount); - } -} diff --git a/contracts/protocol/libraries/logic/FlashClaimLogic.sol b/contracts/protocol/libraries/logic/FlashClaimLogic.sol index cf42c7ad9..4cc63d526 100644 --- a/contracts/protocol/libraries/logic/FlashClaimLogic.sol +++ b/contracts/protocol/libraries/logic/FlashClaimLogic.sol @@ -7,7 +7,6 @@ import {INToken} from "../../../interfaces/INToken.sol"; import {DataTypes} from "../types/DataTypes.sol"; import {Errors} from "../helpers/Errors.sol"; import {ValidationLogic} from "./ValidationLogic.sol"; -import "../../../interfaces/INTokenApeStaking.sol"; import {XTokenType, IXTokenType} from "../../../interfaces/IXTokenType.sol"; import {GenericLogic} from "./GenericLogic.sol"; import {ReserveConfiguration} from "../configuration/ReserveConfiguration.sol"; @@ -52,7 +51,6 @@ library FlashClaimLogic { nTokenAddresses[index] = reserve.xTokenAddress; ValidationLogic.validateFlashClaim( - ps, nTokenAddresses[index], reserve.configuration.getAssetType(), params.nftTokenIds[index] diff --git a/contracts/protocol/libraries/logic/SupplyLogic.sol b/contracts/protocol/libraries/logic/SupplyLogic.sol index 9492852f0..57eb4f3c7 100644 --- a/contracts/protocol/libraries/logic/SupplyLogic.sol +++ b/contracts/protocol/libraries/logic/SupplyLogic.sol @@ -7,7 +7,6 @@ import {GPv2SafeERC20} from "../../../dependencies/gnosis/contracts/GPv2SafeERC2 import {IPToken} from "../../../interfaces/IPToken.sol"; import {INonfungiblePositionManager} from "../../../dependencies/uniswapv3-periphery/interfaces/INonfungiblePositionManager.sol"; import {INToken} from "../../../interfaces/INToken.sol"; -import {INTokenApeStaking} from "../../../interfaces/INTokenApeStaking.sol"; import {ICollateralizableERC721} from "../../../interfaces/ICollateralizableERC721.sol"; import {IAuctionableERC721} from "../../../interfaces/IAuctionableERC721.sol"; import {ITimeLockStrategy} from "../../../interfaces/ITimeLockStrategy.sol"; diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 45da15bff..f0efcbdd4 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -27,7 +27,6 @@ import {IToken} from "../../../interfaces/IToken.sol"; import {XTokenType, IXTokenType} from "../../../interfaces/IXTokenType.sol"; import {Helpers} from "../helpers/Helpers.sol"; import {INonfungiblePositionManager} from "../../../dependencies/uniswapv3-periphery/interfaces/INonfungiblePositionManager.sol"; -import "../../../interfaces/INTokenApeStaking.sol"; /** * @title ReserveLogic library @@ -993,10 +992,8 @@ library ValidationLogic { /** * @notice Validates a flash claim. - * @param ps The pool storage */ function validateFlashClaim( - DataTypes.PoolStorage storage ps, address xTokenAddress, DataTypes.AssetType assetType, uint256[] memory nftTokenIds diff --git a/contracts/protocol/pool/PoolApeStaking.sol b/contracts/protocol/pool/PoolApeStaking.sol deleted file mode 100644 index 487cc19c2..000000000 --- a/contracts/protocol/pool/PoolApeStaking.sol +++ /dev/null @@ -1,854 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "../libraries/paraspace-upgradeability/ParaReentrancyGuard.sol"; -import "../libraries/paraspace-upgradeability/ParaVersionedInitializable.sol"; -import {PoolStorage} from "./PoolStorage.sol"; -import "../../interfaces/IPoolApeStaking.sol"; -import "../../interfaces/IPToken.sol"; -import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; -import "../../interfaces/IXTokenType.sol"; -import "../../interfaces/INTokenApeStaking.sol"; -import {ValidationLogic} from "../libraries/logic/ValidationLogic.sol"; -import {IPoolAddressesProvider} from "../../interfaces/IPoolAddressesProvider.sol"; -import {Errors} from "../libraries/helpers/Errors.sol"; -import {ReserveLogic} from "../libraries/logic/ReserveLogic.sol"; -import {GenericLogic} from "../libraries/logic/GenericLogic.sol"; -import {UserConfiguration} from "../libraries/configuration/UserConfiguration.sol"; -import {ApeStakingLogic} from "../tokenization/libraries/ApeStakingLogic.sol"; -import "../libraries/logic/BorrowLogic.sol"; -import "../libraries/logic/SupplyLogic.sol"; -import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; -import {IAutoCompoundApe} from "../../interfaces/IAutoCompoundApe.sol"; -import {PercentageMath} from "../libraries/math/PercentageMath.sol"; -import {WadRayMath} from "../libraries/math/WadRayMath.sol"; -import {Math} from "../../dependencies/openzeppelin/contracts/Math.sol"; -import {ISwapRouter} from "../../dependencies/uniswapv3-periphery/interfaces/ISwapRouter.sol"; -import {IPriceOracleGetter} from "../../interfaces/IPriceOracleGetter.sol"; -import {Helpers} from "../libraries/helpers/Helpers.sol"; - -contract PoolApeStaking is - ParaVersionedInitializable, - ParaReentrancyGuard, - PoolStorage, - IPoolApeStaking -{ - using ReserveLogic for DataTypes.ReserveData; - using UserConfiguration for DataTypes.UserConfigurationMap; - using SafeERC20 for IERC20; - using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - using SafeCast for uint256; - using PercentageMath for uint256; - using WadRayMath for uint256; - - IPoolAddressesProvider internal immutable ADDRESSES_PROVIDER; - IAutoCompoundApe internal immutable APE_COMPOUND; - IERC20 internal immutable APE_COIN; - uint256 internal constant POOL_REVISION = 200; - IERC20 internal immutable USDC; - ISwapRouter internal immutable SWAP_ROUTER; - - uint256 internal constant DEFAULT_MAX_SLIPPAGE = 500; // 5% - uint24 internal immutable APE_WETH_FEE; - uint24 internal immutable WETH_USDC_FEE; - address internal immutable WETH; - address internal immutable APE_COMPOUND_TREASURY; - - event ReserveUsedAsCollateralEnabled( - address indexed reserve, - address indexed user - ); - - struct ApeStakingLocalVars { - address xTokenAddress; - IERC721 bakcContract; - address bakcNToken; - uint256 balanceBefore; - uint256 balanceAfter; - uint256[] amounts; - uint256[] swapAmounts; - address[] transferredTokenOwners; - DataTypes.ApeCompoundStrategy[] options; - uint256 totalAmount; - uint256 totalNonDepositAmount; - uint256 compoundFee; - bytes usdcSwapPath; - bytes wethSwapPath; - } - - /** - * @dev Constructor. - * @param provider The address of the PoolAddressesProvider contract - */ - constructor( - IPoolAddressesProvider provider, - IAutoCompoundApe apeCompound, - IERC20 apeCoin, - IERC20 usdc, - ISwapRouter uniswapV3SwapRouter, - address weth, - uint24 apeWethFee, - uint24 wethUsdcFee, - address apeCompoundTreasury - ) { - require( - apeCompoundTreasury != address(0), - Errors.ZERO_ADDRESS_NOT_VALID - ); - ADDRESSES_PROVIDER = provider; - APE_COMPOUND = apeCompound; - APE_COIN = apeCoin; - USDC = IERC20(usdc); - SWAP_ROUTER = ISwapRouter(uniswapV3SwapRouter); - WETH = weth; - APE_WETH_FEE = apeWethFee; - WETH_USDC_FEE = wethUsdcFee; - APE_COMPOUND_TREASURY = apeCompoundTreasury; - } - - function getRevision() internal pure virtual override returns (uint256) { - return POOL_REVISION; - } - - /// @inheritdoc IPoolApeStaking - function withdrawApeCoin( - address nftAsset, - ApeCoinStaking.SingleNft[] calldata _nfts - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - DataTypes.ReserveData storage nftReserve = ps._reserves[nftAsset]; - address xTokenAddress = nftReserve.xTokenAddress; - INToken nToken = INToken(xTokenAddress); - for (uint256 index = 0; index < _nfts.length; index++) { - require( - nToken.ownerOf(_nfts[index].tokenId) == msg.sender, - Errors.NOT_THE_OWNER - ); - } - INTokenApeStaking(xTokenAddress).withdrawApeCoin(_nfts, msg.sender); - - _checkUserHf(ps, msg.sender, true); - } - - /// @inheritdoc IPoolApeStaking - function claimApeCoin( - address nftAsset, - uint256[] calldata _nfts - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - DataTypes.ReserveData storage nftReserve = ps._reserves[nftAsset]; - address xTokenAddress = nftReserve.xTokenAddress; - INToken nToken = INToken(xTokenAddress); - for (uint256 index = 0; index < _nfts.length; index++) { - require( - nToken.ownerOf(_nfts[index]) == msg.sender, - Errors.NOT_THE_OWNER - ); - } - INTokenApeStaking(xTokenAddress).claimApeCoin(_nfts, msg.sender); - - _checkUserHf(ps, msg.sender, true); - } - - /// @inheritdoc IPoolApeStaking - function withdrawBAKC( - address nftAsset, - ApeCoinStaking.PairNftWithdrawWithAmount[] calldata _nftPairs - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - ApeStakingLocalVars memory localVar = _generalCache(ps, nftAsset); - localVar.transferredTokenOwners = new address[](_nftPairs.length); - - uint256[] memory transferredTokenIds = new uint256[](_nftPairs.length); - uint256 actualTransferAmount = 0; - - for (uint256 index = 0; index < _nftPairs.length; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[index].mainTokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - - if ( - !_nftPairs[index].isUncommit || - localVar.bakcContract.ownerOf(_nftPairs[index].bakcTokenId) == - localVar.bakcNToken - ) { - localVar.transferredTokenOwners[ - actualTransferAmount - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[index].bakcTokenId, - msg.sender - ); - transferredTokenIds[actualTransferAmount] = _nftPairs[index] - .bakcTokenId; - actualTransferAmount++; - } - } - - INTokenApeStaking(localVar.xTokenAddress).withdrawBAKC( - _nftPairs, - msg.sender - ); - - ////transfer BAKC back for user - for (uint256 index = 0; index < actualTransferAmount; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - transferredTokenIds[index] - ); - } - - _checkUserHf(ps, msg.sender, true); - } - - /// @inheritdoc IPoolApeStaking - function claimBAKC( - address nftAsset, - ApeCoinStaking.PairNft[] calldata _nftPairs - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - ApeStakingLocalVars memory localVar = _generalCache(ps, nftAsset); - localVar.transferredTokenOwners = new address[](_nftPairs.length); - - for (uint256 index = 0; index < _nftPairs.length; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[index].mainTokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - - localVar.transferredTokenOwners[ - index - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[index].bakcTokenId, - msg.sender - ); - } - - INTokenApeStaking(localVar.xTokenAddress).claimBAKC( - _nftPairs, - msg.sender - ); - - //transfer BAKC back for user - for (uint256 index = 0; index < _nftPairs.length; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - _nftPairs[index].bakcTokenId - ); - } - } - - /// @inheritdoc IPoolApeStaking - function borrowApeAndStake( - StakingInfo calldata stakingInfo, - ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - require( - stakingInfo.borrowAsset == address(APE_COIN) || - stakingInfo.borrowAsset == address(APE_COMPOUND), - Errors.INVALID_ASSET_TYPE - ); - - ApeStakingLocalVars memory localVar = _generalCache( - ps, - stakingInfo.nftAsset - ); - localVar.transferredTokenOwners = new address[](_nftPairs.length); - localVar.balanceBefore = APE_COIN.balanceOf(localVar.xTokenAddress); - - DataTypes.ReserveData storage borrowAssetReserve = ps._reserves[ - stakingInfo.borrowAsset - ]; - // no time lock needed here - DataTypes.TimeLockParams memory timeLockParams; - // 1, handle borrow part - if (stakingInfo.borrowAmount > 0) { - if (stakingInfo.borrowAsset == address(APE_COIN)) { - IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( - localVar.xTokenAddress, - stakingInfo.borrowAmount, - timeLockParams - ); - } else { - IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( - address(this), - stakingInfo.borrowAmount, - timeLockParams - ); - APE_COMPOUND.withdraw(stakingInfo.borrowAmount); - APE_COIN.safeTransfer( - localVar.xTokenAddress, - stakingInfo.borrowAmount - ); - } - } - - // 2, send cash part to xTokenAddress - if (stakingInfo.cashAmount > 0) { - APE_COIN.safeTransferFrom( - msg.sender, - localVar.xTokenAddress, - stakingInfo.cashAmount - ); - } - - // 3, deposit bayc or mayc pool - { - uint256 nftsLength = _nfts.length; - for (uint256 index = 0; index < nftsLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nfts[index].tokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - } - - if (nftsLength > 0) { - INTokenApeStaking(localVar.xTokenAddress).depositApeCoin(_nfts); - } - } - - // 4, deposit bakc pool - { - uint256 nftPairsLength = _nftPairs.length; - for (uint256 index = 0; index < nftPairsLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[index].mainTokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - - localVar.transferredTokenOwners[ - index - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[index].bakcTokenId, - msg.sender - ); - } - - if (nftPairsLength > 0) { - INTokenApeStaking(localVar.xTokenAddress).depositBAKC( - _nftPairs - ); - } - //transfer BAKC back for user - for (uint256 index = 0; index < nftPairsLength; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - _nftPairs[index].bakcTokenId - ); - } - } - - // 5 mint debt token - if (stakingInfo.borrowAmount > 0) { - BorrowLogic.executeBorrow( - ps._reserves, - ps._reservesList, - ps._usersConfig[msg.sender], - DataTypes.ExecuteBorrowParams({ - asset: stakingInfo.borrowAsset, - user: msg.sender, - onBehalfOf: msg.sender, - amount: stakingInfo.borrowAmount, - referralCode: 0, - releaseUnderlying: false, - reservesCount: ps._reservesCount, - oracle: ADDRESSES_PROVIDER.getPriceOracle(), - priceOracleSentinel: ADDRESSES_PROVIDER - .getPriceOracleSentinel() - }) - ); - } - - //6 checkout ape balance - require( - APE_COIN.balanceOf(localVar.xTokenAddress) == - localVar.balanceBefore, - Errors.TOTAL_STAKING_AMOUNT_WRONG - ); - - //7 collateralize sAPE - DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ - msg.sender - ]; - Helpers.setAssetUsedAsCollateral( - userConfig, - ps._reserves, - DataTypes.SApeAddress, - msg.sender - ); - } - - /// @inheritdoc IPoolApeStaking - function unstakeApePositionAndRepay( - address nftAsset, - uint256 tokenId - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - DataTypes.ReserveData storage nftReserve = ps._reserves[nftAsset]; - address xTokenAddress = nftReserve.xTokenAddress; - address incentiveReceiver = address(0); - address positionOwner = INToken(xTokenAddress).ownerOf(tokenId); - if (msg.sender != positionOwner) { - _checkUserHf(ps, positionOwner, false); - incentiveReceiver = msg.sender; - } - - INTokenApeStaking(xTokenAddress).unstakePositionAndRepay( - tokenId, - incentiveReceiver - ); - } - - /// @inheritdoc IPoolApeStaking - function repayAndSupply( - address underlyingAsset, - address onBehalfOf, - uint256 totalAmount - ) external { - DataTypes.PoolStorage storage ps = poolStorage(); - require( - msg.sender == ps._reserves[underlyingAsset].xTokenAddress, - Errors.CALLER_NOT_XTOKEN - ); - - // 1, deposit APE as cAPE - APE_COIN.safeTransferFrom(msg.sender, address(this), totalAmount); - APE_COMPOUND.deposit(address(this), totalAmount); - - // 2, repay cAPE and supply cAPE for user - _repayAndSupplyForUser( - ps, - address(APE_COMPOUND), - address(this), - onBehalfOf, - totalAmount - ); - } - - /// @inheritdoc IPoolApeStaking - function claimApeAndCompound( - address nftAsset, - address[] calldata users, - uint256[][] calldata tokenIds - ) external nonReentrant { - require( - users.length == tokenIds.length, - Errors.INCONSISTENT_PARAMS_LENGTH - ); - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - ApeStakingLocalVars memory localVar = _compoundCache( - ps, - nftAsset, - users.length - ); - - for (uint256 i = 0; i < users.length; i++) { - for (uint256 j = 0; j < tokenIds[i].length; j++) { - require( - users[i] == - INToken(localVar.xTokenAddress).ownerOf(tokenIds[i][j]), - Errors.NOT_THE_OWNER - ); - } - - INTokenApeStaking(localVar.xTokenAddress).claimApeCoin( - tokenIds[i], - address(this) - ); - - _addUserToCompoundCache(ps, localVar, i, users[i]); - } - - _compoundForUsers(ps, localVar, users); - } - - /// @inheritdoc IPoolApeStaking - function claimPairedApeAndCompound( - address nftAsset, - address[] calldata users, - ApeCoinStaking.PairNft[][] calldata _nftPairs - ) external nonReentrant { - require( - users.length == _nftPairs.length, - Errors.INCONSISTENT_PARAMS_LENGTH - ); - DataTypes.PoolStorage storage ps = poolStorage(); - - ApeStakingLocalVars memory localVar = _compoundCache( - ps, - nftAsset, - users.length - ); - - for (uint256 i = 0; i < _nftPairs.length; i++) { - localVar.transferredTokenOwners = new address[]( - _nftPairs[i].length - ); - for (uint256 j = 0; j < _nftPairs[i].length; j++) { - require( - users[i] == - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[i][j].mainTokenId - ), - Errors.NOT_THE_OWNER - ); - - localVar.transferredTokenOwners[ - j - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[i][j].bakcTokenId, - users[i] - ); - } - - INTokenApeStaking(localVar.xTokenAddress).claimBAKC( - _nftPairs[i], - address(this) - ); - - for (uint256 index = 0; index < _nftPairs[i].length; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - _nftPairs[i][index].bakcTokenId - ); - } - - _addUserToCompoundCache(ps, localVar, i, users[i]); - } - - _compoundForUsers(ps, localVar, users); - } - - function _generalCache( - DataTypes.PoolStorage storage ps, - address nftAsset - ) internal view returns (ApeStakingLocalVars memory localVar) { - localVar.xTokenAddress = ps._reserves[nftAsset].xTokenAddress; - localVar.bakcContract = INTokenApeStaking(localVar.xTokenAddress) - .getBAKC(); - localVar.bakcNToken = ps - ._reserves[address(localVar.bakcContract)] - .xTokenAddress; - } - - function _compoundCache( - DataTypes.PoolStorage storage ps, - address nftAsset, - uint256 numUsers - ) internal view returns (ApeStakingLocalVars memory localVar) { - localVar = _generalCache(ps, nftAsset); - localVar.balanceBefore = APE_COIN.balanceOf(address(this)); - localVar.amounts = new uint256[](numUsers); - localVar.swapAmounts = new uint256[](numUsers); - localVar.options = new DataTypes.ApeCompoundStrategy[](numUsers); - localVar.compoundFee = ps._apeCompoundFee; - } - - function _addUserToCompoundCache( - DataTypes.PoolStorage storage ps, - ApeStakingLocalVars memory localVar, - uint256 i, - address user - ) internal view { - localVar.balanceAfter = APE_COIN.balanceOf(address(this)); - localVar.options[i] = ps._apeCompoundStrategies[user]; - unchecked { - localVar.amounts[i] = (localVar.balanceAfter - - localVar.balanceBefore).percentMul( - PercentageMath.PERCENTAGE_FACTOR - localVar.compoundFee - ); - localVar.balanceBefore = localVar.balanceAfter; - localVar.totalAmount += localVar.amounts[i]; - } - - if (localVar.options[i].ty == DataTypes.ApeCompoundType.SwapAndSupply) { - localVar.swapAmounts[i] = localVar.amounts[i].percentMul( - localVar.options[i].swapPercent - ); - localVar.totalNonDepositAmount += localVar.swapAmounts[i]; - } - } - - /// @inheritdoc IPoolApeStaking - function getApeCompoundFeeRate() external view returns (uint256) { - DataTypes.PoolStorage storage ps = poolStorage(); - return uint256(ps._apeCompoundFee); - } - - function _checkUserHf( - DataTypes.PoolStorage storage ps, - address user, - bool checkAbove - ) private view { - DataTypes.UserConfigurationMap memory userConfig = ps._usersConfig[ - user - ]; - - uint256 healthFactor; - if (!userConfig.isBorrowingAny()) { - healthFactor = type(uint256).max; - } else { - (, , , , , , , healthFactor, , ) = GenericLogic - .calculateUserAccountData( - ps._reserves, - ps._reservesList, - DataTypes.CalculateUserAccountDataParams({ - userConfig: userConfig, - reservesCount: ps._reservesCount, - user: user, - oracle: ADDRESSES_PROVIDER.getPriceOracle() - }) - ); - } - - if (checkAbove) { - require( - healthFactor > DataTypes.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, - Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - } else { - require( - healthFactor < DataTypes.HEALTH_FACTOR_LIQUIDATION_THRESHOLD, - Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD - ); - } - } - - function _checkSApeIsNotPaused( - DataTypes.PoolStorage storage ps - ) internal view { - DataTypes.ReserveData storage reserve = ps._reserves[ - DataTypes.SApeAddress - ]; - - (bool isActive, , , bool isPaused, ) = reserve.configuration.getFlags(); - - require(isActive, Errors.RESERVE_INACTIVE); - require(!isPaused, Errors.RESERVE_PAUSED); - } - - function _compoundForUsers( - DataTypes.PoolStorage storage ps, - ApeStakingLocalVars memory localVar, - address[] calldata users - ) internal { - if (localVar.totalAmount != localVar.totalNonDepositAmount) { - APE_COMPOUND.deposit( - address(this), - localVar.totalAmount - localVar.totalNonDepositAmount - ); - } - uint256 compoundFee = localVar - .totalAmount - .percentDiv(PercentageMath.PERCENTAGE_FACTOR - localVar.compoundFee) - .percentMul(localVar.compoundFee); - if (compoundFee > 0) { - APE_COMPOUND.deposit(APE_COMPOUND_TREASURY, compoundFee); - } - - uint256 usdcPrice = _getApeRelativePrice(address(USDC), 1E6); - uint256 wethPrice = _getApeRelativePrice(address(WETH), 1E18); - localVar.usdcSwapPath = abi.encodePacked( - APE_COIN, - APE_WETH_FEE, - WETH, - WETH_USDC_FEE, - USDC - ); - localVar.wethSwapPath = abi.encodePacked(APE_COIN, APE_WETH_FEE, WETH); - - for (uint256 i = 0; i < users.length; i++) { - address swapTokenOut; - bytes memory swapPath; - uint256 price; - if ( - localVar.options[i].swapTokenOut == - DataTypes.ApeCompoundTokenOut.USDC - ) { - swapTokenOut = address(USDC); - swapPath = localVar.usdcSwapPath; - price = usdcPrice; - } else { - swapTokenOut = address(WETH); - swapPath = localVar.wethSwapPath; - price = wethPrice; - } - _swapAndSupplyForUser( - ps, - swapTokenOut, - localVar.swapAmounts[i], - swapPath, - users[i], - price - ); - _repayAndSupplyForUser( - ps, - address(APE_COMPOUND), - address(this), - users[i], - localVar.amounts[i] - localVar.swapAmounts[i] - ); - } - } - - function _swapAndSupplyForUser( - DataTypes.PoolStorage storage ps, - address tokenOut, - uint256 amountIn, - bytes memory swapPath, - address user, - uint256 price - ) internal { - if (amountIn == 0) { - return; - } - uint256 amountOut = SWAP_ROUTER.exactInput( - ISwapRouter.ExactInputParams({ - path: swapPath, - recipient: address(this), - deadline: block.timestamp, - amountIn: amountIn, - amountOutMinimum: amountIn.wadMul(price) - }) - ); - _supplyForUser(ps, tokenOut, address(this), user, amountOut); - } - - function _getApeRelativePrice( - address tokenOut, - uint256 tokenOutUnit - ) internal view returns (uint256) { - IPriceOracleGetter oracle = IPriceOracleGetter( - ADDRESSES_PROVIDER.getPriceOracle() - ); - uint256 apePrice = oracle.getAssetPrice(address(APE_COIN)); - uint256 tokenOutPrice = oracle.getAssetPrice(tokenOut); - - return - ((apePrice * tokenOutUnit).wadDiv(tokenOutPrice * 1E18)).percentMul( - PercentageMath.PERCENTAGE_FACTOR - DEFAULT_MAX_SLIPPAGE - ); - } - - function _repayAndSupplyForUser( - DataTypes.PoolStorage storage ps, - address asset, - address payer, - address onBehalfOf, - uint256 totalAmount - ) internal { - address variableDebtTokenAddress = ps - ._reserves[asset] - .variableDebtTokenAddress; - uint256 repayAmount = Math.min( - IERC20(variableDebtTokenAddress).balanceOf(onBehalfOf), - totalAmount - ); - _repayForUser(ps, asset, payer, onBehalfOf, repayAmount); - _supplyForUser(ps, asset, payer, onBehalfOf, totalAmount - repayAmount); - } - - function _supplyForUser( - DataTypes.PoolStorage storage ps, - address asset, - address payer, - address onBehalfOf, - uint256 amount - ) internal { - if (amount == 0) { - return; - } - DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ - onBehalfOf - ]; - SupplyLogic.executeSupply( - ps._reserves, - userConfig, - DataTypes.ExecuteSupplyParams({ - asset: asset, - amount: amount, - onBehalfOf: onBehalfOf, - payer: payer, - referralCode: 0 - }) - ); - Helpers.setAssetUsedAsCollateral( - userConfig, - ps._reserves, - asset, - onBehalfOf - ); - } - - function _repayForUser( - DataTypes.PoolStorage storage ps, - address asset, - address payer, - address onBehalfOf, - uint256 amount - ) internal returns (uint256) { - if (amount == 0) { - return 0; - } - return - BorrowLogic.executeRepay( - ps._reserves, - ps._usersConfig[onBehalfOf], - DataTypes.ExecuteRepayParams({ - asset: asset, - amount: amount, - onBehalfOf: onBehalfOf, - payer: payer, - usePTokens: false - }) - ); - } - - function _validateBAKCOwnerAndTransfer( - ApeStakingLocalVars memory localVar, - uint256 tokenId, - address userAddress - ) internal returns (address bakcOwner) { - bakcOwner = localVar.bakcContract.ownerOf(tokenId); - require( - (userAddress == bakcOwner) || - (userAddress == INToken(localVar.bakcNToken).ownerOf(tokenId)), - Errors.NOT_THE_BAKC_OWNER - ); - localVar.bakcContract.safeTransferFrom( - bakcOwner, - localVar.xTokenAddress, - tokenId - ); - } -} diff --git a/contracts/protocol/pool/PoolBorrowAndStake.sol b/contracts/protocol/pool/PoolBorrowAndStake.sol deleted file mode 100644 index 1af514995..000000000 --- a/contracts/protocol/pool/PoolBorrowAndStake.sol +++ /dev/null @@ -1,321 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import "../libraries/paraspace-upgradeability/ParaReentrancyGuard.sol"; -import "../libraries/paraspace-upgradeability/ParaVersionedInitializable.sol"; -import {PoolStorage} from "./PoolStorage.sol"; -import "../../interfaces/IPoolBorrowAndStake.sol"; -import "../../interfaces/IPToken.sol"; -import "../../dependencies/yoga-labs/ApeCoinStaking.sol"; -import "../../interfaces/IXTokenType.sol"; -import "../../interfaces/INTokenApeStaking.sol"; -import {ValidationLogic} from "../libraries/logic/ValidationLogic.sol"; -import {IPoolAddressesProvider} from "../../interfaces/IPoolAddressesProvider.sol"; -import {Errors} from "../libraries/helpers/Errors.sol"; -import {ReserveLogic} from "../libraries/logic/ReserveLogic.sol"; -import {GenericLogic} from "../libraries/logic/GenericLogic.sol"; -import {UserConfiguration} from "../libraries/configuration/UserConfiguration.sol"; -import {ApeStakingLogic} from "../tokenization/libraries/ApeStakingLogic.sol"; -import "../libraries/logic/BorrowLogic.sol"; -import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; -import {IAutoCompoundApe} from "../../interfaces/IAutoCompoundApe.sol"; -import {Math} from "../../dependencies/openzeppelin/contracts/Math.sol"; -import {ISwapRouter} from "../../dependencies/uniswapv3-periphery/interfaces/ISwapRouter.sol"; -import {IPriceOracleGetter} from "../../interfaces/IPriceOracleGetter.sol"; -import {Helpers} from "../libraries/helpers/Helpers.sol"; - -contract PoolBorrowAndStake is - ParaVersionedInitializable, - ParaReentrancyGuard, - PoolStorage, - IPoolBorrowAndStake -{ - using ReserveLogic for DataTypes.ReserveData; - using UserConfiguration for DataTypes.UserConfigurationMap; - using SafeERC20 for IERC20; - using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - using SafeCast for uint256; - - IPoolAddressesProvider internal immutable ADDRESSES_PROVIDER; - IAutoCompoundApe internal immutable APE_COMPOUND; - IERC20 internal immutable APE_COIN; - uint256 internal constant POOL_REVISION = 200; - - event ReserveUsedAsCollateralDisabled( - address indexed reserve, - address indexed user - ); - - struct ApeStakingLocalVars { - address xTokenAddress; - IERC721 bakcContract; - address bakcNToken; - uint256 balanceBefore; - uint256 balanceAfter; - uint256[] amounts; - uint256[] swapAmounts; - address[] transferredTokenOwners; - DataTypes.ApeCompoundStrategy[] options; - uint256 totalAmount; - uint256 totalNonDepositAmount; - uint256 compoundFee; - bytes usdcSwapPath; - bytes wethSwapPath; - } - - /** - * @dev Constructor. - * @param provider The address of the PoolAddressesProvider contract - */ - constructor( - IPoolAddressesProvider provider, - IAutoCompoundApe apeCompound, - IERC20 apeCoin - ) { - ADDRESSES_PROVIDER = provider; - APE_COMPOUND = apeCompound; - APE_COIN = apeCoin; - } - - function getRevision() internal pure virtual override returns (uint256) { - return POOL_REVISION; - } - - /// @inheritdoc IPoolBorrowAndStake - function borrowApeAndStakeV2( - StakingInfoV2 calldata stakingInfo, - ApeCoinStaking.SingleNft[] calldata _nfts, - ApeCoinStaking.PairNftDepositWithAmount[] calldata _nftPairs - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - _checkSApeIsNotPaused(ps); - - require( - stakingInfo.borrowAsset == address(APE_COIN) || - stakingInfo.borrowAsset == address(APE_COMPOUND), - Errors.INVALID_ASSET_TYPE - ); - - ApeStakingLocalVars memory localVar = _generalCache( - ps, - stakingInfo.nftAsset - ); - localVar.transferredTokenOwners = new address[](_nftPairs.length); - localVar.balanceBefore = APE_COIN.balanceOf(localVar.xTokenAddress); - - // no time lock needed here - DataTypes.TimeLockParams memory timeLockParams; - // 1, handle borrow part - if (stakingInfo.borrowAmount > 0) { - DataTypes.ReserveData storage borrowAssetReserve = ps._reserves[ - stakingInfo.borrowAsset - ]; - if (stakingInfo.borrowAsset == address(APE_COIN)) { - IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( - localVar.xTokenAddress, - stakingInfo.borrowAmount, - timeLockParams - ); - } else { - IPToken(borrowAssetReserve.xTokenAddress).transferUnderlyingTo( - address(this), - stakingInfo.borrowAmount, - timeLockParams - ); - APE_COMPOUND.withdraw(stakingInfo.borrowAmount); - APE_COIN.safeTransfer( - localVar.xTokenAddress, - stakingInfo.borrowAmount - ); - } - } - - // 2, send cash part to xTokenAddress - DataTypes.UserConfigurationMap storage userConfig = ps._usersConfig[ - msg.sender - ]; - if (stakingInfo.cashAmount > 0) { - if (stakingInfo.cashAsset == address(APE_COIN)) { - APE_COIN.safeTransferFrom( - msg.sender, - localVar.xTokenAddress, - stakingInfo.cashAmount - ); - } else { - //pcApe - DataTypes.ReserveData storage cApeReserve = ps._reserves[ - address(APE_COMPOUND) - ]; - DataTypes.ReserveCache memory cApeReserveCache = cApeReserve - .cache(); - address PCAPE = cApeReserveCache.xTokenAddress; - require( - stakingInfo.cashAsset == PCAPE, - Errors.INVALID_ASSET_TYPE - ); - cApeReserve.updateState(cApeReserveCache); - cApeReserve.updateInterestRates( - cApeReserveCache, - address(APE_COMPOUND), - 0, - stakingInfo.cashAmount - ); - IPToken(PCAPE).burn( - msg.sender, - address(this), - stakingInfo.cashAmount, - cApeReserveCache.nextLiquidityIndex, - timeLockParams - ); - uint16 cApeId = cApeReserve.id; - if (userConfig.isUsingAsCollateral(cApeId)) { - uint256 userBalance = IPToken(PCAPE).balanceOf(msg.sender); - if (userBalance == 0) { - userConfig.setUsingAsCollateral(cApeId, false); - emit ReserveUsedAsCollateralDisabled( - address(APE_COMPOUND), - msg.sender - ); - } - } - - APE_COMPOUND.withdraw(stakingInfo.cashAmount); - APE_COIN.safeTransfer( - localVar.xTokenAddress, - stakingInfo.cashAmount - ); - } - } - - // 3, deposit bayc or mayc pool - { - uint256 nftsLength = _nfts.length; - for (uint256 index = 0; index < nftsLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nfts[index].tokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - } - - if (nftsLength > 0) { - INTokenApeStaking(localVar.xTokenAddress).depositApeCoin(_nfts); - } - } - - // 4, deposit bakc pool - { - uint256 nftPairsLength = _nftPairs.length; - for (uint256 index = 0; index < nftPairsLength; index++) { - require( - INToken(localVar.xTokenAddress).ownerOf( - _nftPairs[index].mainTokenId - ) == msg.sender, - Errors.NOT_THE_OWNER - ); - - localVar.transferredTokenOwners[ - index - ] = _validateBAKCOwnerAndTransfer( - localVar, - _nftPairs[index].bakcTokenId, - msg.sender - ); - } - - if (nftPairsLength > 0) { - INTokenApeStaking(localVar.xTokenAddress).depositBAKC( - _nftPairs - ); - } - //transfer BAKC back for user - for (uint256 index = 0; index < nftPairsLength; index++) { - localVar.bakcContract.safeTransferFrom( - localVar.xTokenAddress, - localVar.transferredTokenOwners[index], - _nftPairs[index].bakcTokenId - ); - } - } - - //5 collateralize sAPE - Helpers.setAssetUsedAsCollateral( - userConfig, - ps._reserves, - DataTypes.SApeAddress, - msg.sender - ); - - // 6 mint debt token - if (stakingInfo.borrowAmount > 0) { - BorrowLogic.executeBorrow( - ps._reserves, - ps._reservesList, - ps._usersConfig[msg.sender], - DataTypes.ExecuteBorrowParams({ - asset: stakingInfo.borrowAsset, - user: msg.sender, - onBehalfOf: msg.sender, - amount: stakingInfo.borrowAmount, - referralCode: 0, - releaseUnderlying: false, - reservesCount: ps._reservesCount, - oracle: ADDRESSES_PROVIDER.getPriceOracle(), - priceOracleSentinel: ADDRESSES_PROVIDER - .getPriceOracleSentinel() - }) - ); - } - - //7 checkout ape balance - require( - APE_COIN.balanceOf(localVar.xTokenAddress) == - localVar.balanceBefore, - Errors.TOTAL_STAKING_AMOUNT_WRONG - ); - } - - function _generalCache( - DataTypes.PoolStorage storage ps, - address nftAsset - ) internal view returns (ApeStakingLocalVars memory localVar) { - localVar.xTokenAddress = ps._reserves[nftAsset].xTokenAddress; - localVar.bakcContract = INTokenApeStaking(localVar.xTokenAddress) - .getBAKC(); - localVar.bakcNToken = ps - ._reserves[address(localVar.bakcContract)] - .xTokenAddress; - } - - function _checkSApeIsNotPaused( - DataTypes.PoolStorage storage ps - ) internal view { - DataTypes.ReserveData storage reserve = ps._reserves[ - DataTypes.SApeAddress - ]; - - (bool isActive, , , bool isPaused, ) = reserve.configuration.getFlags(); - - require(isActive, Errors.RESERVE_INACTIVE); - require(!isPaused, Errors.RESERVE_PAUSED); - } - - function _validateBAKCOwnerAndTransfer( - ApeStakingLocalVars memory localVar, - uint256 tokenId, - address userAddress - ) internal returns (address bakcOwner) { - bakcOwner = localVar.bakcContract.ownerOf(tokenId); - require( - (userAddress == bakcOwner) || - (userAddress == INToken(localVar.bakcNToken).ownerOf(tokenId)), - Errors.NOT_THE_BAKC_OWNER - ); - localVar.bakcContract.safeTransferFrom( - bakcOwner, - localVar.xTokenAddress, - tokenId - ); - } -} diff --git a/contracts/protocol/pool/PoolPositionMover.sol b/contracts/protocol/pool/PoolPositionMover.sol deleted file mode 100644 index 4cbfbe0f4..000000000 --- a/contracts/protocol/pool/PoolPositionMover.sol +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; - -import {ParaVersionedInitializable} from "../libraries/paraspace-upgradeability/ParaVersionedInitializable.sol"; -import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; -import {DataTypes} from "../libraries/types/DataTypes.sol"; -import {IPoolAddressesProvider} from "../../interfaces/IPoolAddressesProvider.sol"; -import {IPoolPositionMover} from "../../interfaces/IPoolPositionMover.sol"; -import {PoolStorage} from "./PoolStorage.sol"; -import {PositionMoverLogic} from "../libraries/logic/PositionMoverLogic.sol"; -import {ParaReentrancyGuard} from "../libraries/paraspace-upgradeability/ParaReentrancyGuard.sol"; -import {ILendPoolLoan} from "../../dependencies/benddao/contracts/interfaces/ILendPoolLoan.sol"; -import {ILendPool} from "../../dependencies/benddao/contracts/interfaces/ILendPool.sol"; -import {IPool} from "../../interfaces/IPool.sol"; -import {ICApe} from "../../interfaces/ICApe.sol"; -import {ITimeLock} from "../../interfaces/ITimeLock.sol"; -import {INToken} from "../../interfaces/INToken.sol"; -import {IPToken} from "../../interfaces/IPToken.sol"; -import {IP2PPairStaking} from "../../interfaces/IP2PPairStaking.sol"; -import {IProtocolDataProvider} from "../../interfaces/IProtocolDataProvider.sol"; -import {Errors} from "../libraries/helpers/Errors.sol"; -import {ReserveConfiguration} from "../../protocol/libraries/configuration/ReserveConfiguration.sol"; -import {ReserveLogic} from "../libraries/logic/ReserveLogic.sol"; -import {SafeCast} from "../../dependencies/openzeppelin/contracts/SafeCast.sol"; - -/** - * @title Pool PositionMover contract - * - **/ -contract PoolPositionMover is - ParaVersionedInitializable, - ParaReentrancyGuard, - PoolStorage, - IPoolPositionMover -{ - IPoolAddressesProvider internal immutable ADDRESSES_PROVIDER; - ILendPoolLoan internal immutable BENDDAO_LEND_POOL_LOAN; - ILendPool internal immutable BENDDAO_LEND_POOL; - IPool internal immutable POOL_V1; - IProtocolDataProvider internal immutable PROTOCOL_DATA_PROVIDER_V1; - ICApe internal immutable CAPE_V1; - ICApe internal immutable CAPE_V2; - IERC20 internal immutable APE_COIN; - ITimeLock internal immutable TIME_LOCK_V1; - IP2PPairStaking internal immutable P2P_PAIR_STAKING_V1; - uint256 internal constant POOL_REVISION = 200; - - using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - using ReserveLogic for DataTypes.ReserveData; - using SafeCast for uint256; - - constructor( - IPoolAddressesProvider addressProvider, - ILendPoolLoan benddaoLendPoolLoan, - ILendPool benddaoLendPool, - IPool paraspaceV1, - IProtocolDataProvider protocolDataProviderV1, - ICApe capeV1, - ICApe capeV2, - IERC20 apeCoin, - ITimeLock timeLockV1, - IP2PPairStaking p2pPairStakingV1 - ) { - ADDRESSES_PROVIDER = addressProvider; - BENDDAO_LEND_POOL_LOAN = benddaoLendPoolLoan; - BENDDAO_LEND_POOL = benddaoLendPool; - POOL_V1 = paraspaceV1; - PROTOCOL_DATA_PROVIDER_V1 = protocolDataProviderV1; - CAPE_V1 = capeV1; - CAPE_V2 = capeV2; - APE_COIN = apeCoin; - TIME_LOCK_V1 = timeLockV1; - P2P_PAIR_STAKING_V1 = p2pPairStakingV1; - } - - function movePositionFromBendDAO( - uint256[] calldata loanIds, - address to - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - - PositionMoverLogic.executeMovePositionFromBendDAO( - ps, - ADDRESSES_PROVIDER, - BENDDAO_LEND_POOL_LOAN, - BENDDAO_LEND_POOL, - loanIds, - to - ); - } - - function movePositionFromParaSpace( - DataTypes.ParaSpacePositionMoveInfo calldata moveInfo - ) external nonReentrant { - DataTypes.PoolStorage storage ps = poolStorage(); - - PositionMoverLogic.executeMovePositionFromParaSpaceV1( - ps, - POOL_V1, - PROTOCOL_DATA_PROVIDER_V1, - CAPE_V1, - CAPE_V2, - DataTypes.ParaSpacePositionMoveParams({ - user: msg.sender, - cTokens: moveInfo.cTokens, - cTypes: moveInfo.cTypes, - cAmountsOrTokenIds: moveInfo.cAmountsOrTokenIds, - dTokens: moveInfo.dTokens, - dAmounts: moveInfo.dAmounts, - to: moveInfo.to, - reservesCount: ps._reservesCount, - priceOracle: ADDRESSES_PROVIDER.getPriceOracle(), - priceOracleSentinel: ADDRESSES_PROVIDER.getPriceOracleSentinel() - }) - ); - } - - function claimUnderlying( - address[] calldata assets, - uint256[][] calldata agreementIds - ) external nonReentrant { - require( - assets.length == agreementIds.length, - Errors.INCONSISTENT_PARAMS_LENGTH - ); - DataTypes.PoolStorage storage ps = poolStorage(); - - for (uint256 index = 0; index < assets.length; index++) { - DataTypes.ReserveData storage reserve = ps._reserves[assets[index]]; - DataTypes.ReserveConfigurationMap - memory reserveConfigurationMap = reserve.configuration; - (, , , , DataTypes.AssetType assetType) = reserveConfigurationMap - .getFlags(); - - if (assetType == DataTypes.AssetType.ERC20) { - reserve.unbacked -= IPToken(reserve.xTokenAddress) - .claimUnderlying( - address(TIME_LOCK_V1), - address(CAPE_V1), - address(CAPE_V2), - address(APE_COIN), - agreementIds[index] - ) - .toUint128(); - } else { - INToken(reserve.xTokenAddress).claimUnderlying( - address(TIME_LOCK_V1), - agreementIds[index] - ); - } - } - } - - function getRevision() internal pure virtual override returns (uint256) { - return POOL_REVISION; - } -} diff --git a/contracts/protocol/tokenization/NTokenChromieSquiggle.sol b/contracts/protocol/tokenization/NTokenChromieSquiggle.sol index 2e6b1639f..fe61a6d55 100644 --- a/contracts/protocol/tokenization/NTokenChromieSquiggle.sol +++ b/contracts/protocol/tokenization/NTokenChromieSquiggle.sol @@ -7,11 +7,7 @@ import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; import {Errors} from "../libraries/helpers/Errors.sol"; import {XTokenType} from "../../interfaces/IXTokenType.sol"; -import {ApeStakingLogic} from "./libraries/ApeStakingLogic.sol"; -import {INTokenApeStaking} from "../../interfaces/INTokenApeStaking.sol"; -import {ApeCoinStaking} from "../../dependencies/yoga-labs/ApeCoinStaking.sol"; import {INToken} from "../../interfaces/INToken.sol"; -import {IRewardController} from "../../interfaces/IRewardController.sol"; import {DataTypes} from "../libraries/types/DataTypes.sol"; /** diff --git a/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol b/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol deleted file mode 100644 index a3e3e6589..000000000 --- a/contracts/protocol/tokenization/libraries/ApeStakingLogic.sol +++ /dev/null @@ -1,293 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.0; -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 "./MintableERC721Logic.sol"; -import "../../../interfaces/IPool.sol"; -import {DataTypes} from "../../libraries/types/DataTypes.sol"; -import {PercentageMath} from "../../libraries/math/PercentageMath.sol"; -import "../../../dependencies/openzeppelin/contracts/SafeCast.sol"; -import "../../../interfaces/INToken.sol"; - -/** - * @title ApeStakingLogic library - * - * @notice Implements the base logic for ApeStaking - */ -library ApeStakingLogic { - using SafeERC20 for IERC20; - using PercentageMath for uint256; - using SafeCast for uint256; - - uint256 constant BAYC_POOL_ID = 1; - uint256 constant MAYC_POOL_ID = 2; - uint256 constant BAKC_POOL_ID = 3; - - struct APEStakingParameter { - uint256 unstakeIncentive; - } - event UnstakeApeIncentiveUpdated(uint256 oldValue, uint256 newValue); - - /** - * @notice withdraw Ape coin staking position from ApeCoinStaking - * @param _apeCoinStaking ApeCoinStaking contract address - * @param poolId identify whether BAYC or MAYC paired with BAKC - * @param _nftPairs Array of Paired BAYC/MAYC NFT's with staked amounts - * @param _apeRecipient the receiver of ape coin - */ - function withdrawBAKC( - ApeCoinStaking _apeCoinStaking, - uint256 poolId, - ApeCoinStaking.PairNftWithdrawWithAmount[] memory _nftPairs, - address _apeRecipient - ) external { - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); - - uint256 beforeBalance = _apeCoinStaking.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) - ); - - _apeCoinStaking.apeCoin().safeTransfer( - _apeRecipient, - afterBalance - beforeBalance - ); - } - - /** - * @notice undate incentive percentage for unstakePositionAndRepay - * @param stakingParameter storage for Ape staking - * @param incentive new incentive percentage - */ - function executeSetUnstakeApeIncentive( - APEStakingParameter storage stakingParameter, - uint256 incentive - ) external { - require( - incentive < PercentageMath.HALF_PERCENTAGE_FACTOR, - "Value Too High" - ); - uint256 oldValue = stakingParameter.unstakeIncentive; - if (oldValue != incentive) { - stakingParameter.unstakeIncentive = incentive; - emit UnstakeApeIncentiveUpdated(oldValue, incentive); - } - } - - struct UnstakeAndRepayParams { - IPool POOL; - ApeCoinStaking _apeCoinStaking; - address _underlyingAsset; - uint256 poolId; - uint256 tokenId; - address incentiveReceiver; - address bakcNToken; - } - - /** - * @notice Unstake Ape coin staking position and repay user debt - * @param _owners The state of ownership for nToken - * @param stakingParameter storage for Ape staking - * @param params The additional parameters needed to execute this function - */ - function executeUnstakePositionAndRepay( - mapping(uint256 => address) storage _owners, - APEStakingParameter storage stakingParameter, - UnstakeAndRepayParams memory params - ) external { - if ( - IERC721(params._underlyingAsset).ownerOf(params.tokenId) != - address(this) - ) { - return; - } - address positionOwner = _owners[params.tokenId]; - IERC20 _apeCoin = params._apeCoinStaking.apeCoin(); - uint256 balanceBefore = _apeCoin.balanceOf(address(this)); - - //1 unstake all position - { - //1.1 unstake Main pool position - (uint256 stakedAmount, ) = params._apeCoinStaking.nftPosition( - params.poolId, - params.tokenId - ); - if (stakedAmount > 0) { - ApeCoinStaking.SingleNft[] - memory nfts = new ApeCoinStaking.SingleNft[](1); - nfts[0].tokenId = params.tokenId.toUint32(); - nfts[0].amount = stakedAmount.toUint224(); - if (params.poolId == BAYC_POOL_ID) { - params._apeCoinStaking.withdrawBAYC(nfts, address(this)); - } else { - params._apeCoinStaking.withdrawMAYC(nfts, address(this)); - } - } - //1.2 unstake bakc pool position - (uint256 bakcTokenId, bool isPaired) = params - ._apeCoinStaking - .mainToBakc(params.poolId, params.tokenId); - if (isPaired) { - (stakedAmount, ) = params._apeCoinStaking.nftPosition( - BAKC_POOL_ID, - bakcTokenId - ); - if (stakedAmount > 0) { - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _nftPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 1 - ); - _nftPairs[0].mainTokenId = params.tokenId.toUint32(); - _nftPairs[0].bakcTokenId = bakcTokenId.toUint32(); - _nftPairs[0].amount = stakedAmount.toUint184(); - _nftPairs[0].isUncommit = true; - ApeCoinStaking.PairNftWithdrawWithAmount[] - memory _otherPairs = new ApeCoinStaking.PairNftWithdrawWithAmount[]( - 0 - ); - - uint256 bakcBeforeBalance = _apeCoin.balanceOf( - params.bakcNToken - ); - if (params.poolId == BAYC_POOL_ID) { - params._apeCoinStaking.withdrawBAKC( - _nftPairs, - _otherPairs - ); - } else { - params._apeCoinStaking.withdrawBAKC( - _otherPairs, - _nftPairs - ); - } - uint256 bakcAfterBalance = _apeCoin.balanceOf( - params.bakcNToken - ); - uint256 balanceDiff = bakcAfterBalance - bakcBeforeBalance; - if (balanceDiff > 0) { - address bakcOwner = INToken(params.bakcNToken).ownerOf( - bakcTokenId - ); - _apeCoin.safeTransferFrom( - params.bakcNToken, - bakcOwner, - balanceDiff - ); - } - } - } - } - - uint256 unstakedAmount = _apeCoin.balanceOf(address(this)) - - balanceBefore; - if (unstakedAmount == 0) { - return; - } - //2 send incentive to caller - if (params.incentiveReceiver != address(0)) { - uint256 unstakeIncentive = stakingParameter.unstakeIncentive; - if (unstakeIncentive > 0) { - uint256 incentiveAmount = unstakedAmount.percentMul( - unstakeIncentive - ); - _apeCoin.safeTransfer( - params.incentiveReceiver, - incentiveAmount - ); - unstakedAmount = unstakedAmount - incentiveAmount; - } - } - - //3 repay and supply - params.POOL.repayAndSupply( - params._underlyingAsset, - positionOwner, - unstakedAmount - ); - } - - /** - * @notice get user total ape staking position - * @param userState The user state of nToken - * @param ownedTokens The ownership mapping state of nNtoken - * @param user User address - * @param poolId identify whether BAYC pool or MAYC pool - * @param _apeCoinStaking ApeCoinStaking contract address - */ - function getUserTotalStakingAmount( - mapping(address => UserState) storage userState, - mapping(address => mapping(uint256 => uint256)) storage ownedTokens, - address _underlyingAsset, - address user, - uint256 poolId, - ApeCoinStaking _apeCoinStaking - ) external view returns (uint256) { - uint256 totalBalance = uint256(userState[user].balance); - uint256 totalAmount; - for (uint256 index = 0; index < totalBalance; index++) { - uint256 tokenId = ownedTokens[user][index]; - totalAmount += getTokenIdStakingAmount( - _underlyingAsset, - poolId, - _apeCoinStaking, - tokenId - ); - } - - return totalAmount; - } - - /** - * @notice get ape staking position for a tokenId - * @param poolId identify whether BAYC pool or MAYC pool - * @param _apeCoinStaking ApeCoinStaking contract address - * @param tokenId specified the tokenId for the position - */ - function getTokenIdStakingAmount( - address _underlyingAsset, - uint256 poolId, - ApeCoinStaking _apeCoinStaking, - uint256 tokenId - ) public view returns (uint256) { - if (IERC721(_underlyingAsset).ownerOf(tokenId) != address(this)) { - return 0; - } - (uint256 apeStakedAmount, ) = _apeCoinStaking.nftPosition( - poolId, - tokenId - ); - - uint256 apeReward = _apeCoinStaking.pendingRewards( - poolId, - address(this), - tokenId - ); - - (uint256 bakcTokenId, bool isPaired) = _apeCoinStaking.mainToBakc( - poolId, - tokenId - ); - - if (isPaired) { - (uint256 bakcStakedAmount, ) = _apeCoinStaking.nftPosition( - BAKC_POOL_ID, - bakcTokenId - ); - apeStakedAmount += bakcStakedAmount; - } - - return apeStakedAmount + apeReward; - } -} diff --git a/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol b/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol index d4de32c4c..917f77952 100644 --- a/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol +++ b/contracts/protocol/tokenization/libraries/MintableERC721Logic.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import {SafeCast} from "../../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import "../../../dependencies/openzeppelin/contracts/Address.sol"; import {WadRayMath} from "../../libraries/math/WadRayMath.sol"; import "../../../interfaces/IRewardController.sol"; import "../../libraries/types/DataTypes.sol"; diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index f20147c62..7e3aac6a0 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -40,7 +40,6 @@ import { ExecutionManager, ExecutorWithTimelock, FlashClaimLogic, - HelperContract, HotWalletProxy, InitializableAdminUpgradeabilityProxy, InitializableImmutableAdminUpgradeabilityProxy, @@ -90,13 +89,10 @@ import { ParaSpaceOracle, PausableZoneController, PolicyManager, + PoolAAPositionMover, PoolAAPositionMover__factory, PoolAddressesProvider, PoolAddressesProviderRegistry, - PoolApeStaking, - PoolApeStaking__factory, - PoolBorrowAndStake, - PoolBorrowAndStake__factory, PoolConfigurator, PoolCore, PoolCore__factory, @@ -105,8 +101,6 @@ import { PoolMarketplace__factory, PoolParameters, PoolParameters__factory, - PoolPositionMover, - PoolPositionMover__factory, PositionMoverLogic, PriceOracle, ProtocolDataProvider, @@ -166,7 +160,6 @@ import { getBAYCSewerPass, getContractFactory, getFirstSigner, - getHelperContract, getInitializableAdminUpgradeabilityProxy, getP2PPairStaking, getPoolProxy, @@ -729,90 +722,6 @@ export const deployPoolMarketplace = async ( }; }; -export const deployPoolApeStaking = async ( - provider: string, - verify?: boolean -) => { - const supplyLogic = await deploySupplyLogic(verify); - const borrowLogic = await deployBorrowLogic(verify); - - const apeStakingLibraries = { - "contracts/protocol/libraries/logic/SupplyLogic.sol:SupplyLogic": - supplyLogic.address, - "contracts/protocol/libraries/logic/BorrowLogic.sol:BorrowLogic": - borrowLogic.address, - }; - - const APE_WETH_FEE = 3000; - const WETH_USDC_FEE = 500; - - const {poolApeStakingSelectors} = await getPoolSignatures(); - - const allTokens = await getAllTokens(); - - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - - const cApe = await getAutoCompoundApe(); - const poolApeStaking = (await withSaveAndVerify( - await getContractFactory("PoolApeStaking", apeStakingLibraries), - eContractid.PoolApeStakingImpl, - [ - provider, - cApe.address, - allTokens.APE.address, - allTokens.USDC.address, - (await getUniswapV3SwapRouter()).address, - allTokens.WETH.address, - APE_WETH_FEE, - WETH_USDC_FEE, - treasuryAddress, - ], - verify, - false, - apeStakingLibraries, - poolApeStakingSelectors - )) as PoolApeStaking; - - return { - poolApeStaking, - poolApeStakingSelectors: poolApeStakingSelectors.map((s) => s.signature), - }; -}; - -export const deployPoolBorrowAndStake = async ( - provider: string, - verify?: boolean -) => { - const borrowLogic = await deployBorrowLogic(verify); - - const apeStakingLibraries = { - "contracts/protocol/libraries/logic/BorrowLogic.sol:BorrowLogic": - borrowLogic.address, - }; - - const {poolBorrowAndStakeSelectors} = await getPoolSignatures(); - - const allTokens = await getAllTokens(); - const cApe = await getAutoCompoundApe(); - const poolBorrowAndStake = (await withSaveAndVerify( - await getContractFactory("PoolBorrowAndStake", apeStakingLibraries), - eContractid.PoolBorrowAndStakeImpl, - [provider, cApe.address, allTokens.APE.address], - verify, - false, - apeStakingLibraries, - poolBorrowAndStakeSelectors - )) as PoolApeStaking; - - return { - poolBorrowAndStake, - poolBorrowAndStakeSelectors: poolBorrowAndStakeSelectors.map( - (s) => s.signature - ), - }; -}; - export const deployPoolParameters = async ( provider: string, verify?: boolean @@ -872,7 +781,7 @@ export const deployAAPoolPositionMover = async (verify?: boolean) => { false, undefined, poolAAPositionMoverSelectors - )) as PoolPositionMover; + )) as PoolAAPositionMover; return { poolAAPositionMover, @@ -882,70 +791,6 @@ export const deployAAPoolPositionMover = async (verify?: boolean) => { }; }; -export const deployPoolPositionMover = async ( - provider: tEthereumAddress, - bendDaoLendPoolLoan: tEthereumAddress, - bendDaoLendPool: tEthereumAddress, - poolV1: tEthereumAddress, - protocolDataProviderV1: tEthereumAddress, - capeV1: tEthereumAddress, - capeV2: tEthereumAddress, - apeCoin: tEthereumAddress, - timeLockV1: tEthereumAddress, - p2pPairStakingV1: tEthereumAddress, - verify?: boolean -) => { - const supplyLogic = await deploySupplyLogic(verify); - const borrowLogic = await deployBorrowLogic(verify); - const positionMoverLogicLibraries = { - "contracts/protocol/libraries/logic/SupplyLogic.sol:SupplyLogic": - supplyLogic.address, - "contracts/protocol/libraries/logic/BorrowLogic.sol:BorrowLogic": - borrowLogic.address, - }; - const positionMoverLogic = await deployPositionMoverLogic( - positionMoverLogicLibraries, - verify - ); - - const positionMoverLibraries = { - ["contracts/protocol/libraries/logic/PositionMoverLogic.sol:PositionMoverLogic"]: - positionMoverLogic.address, - }; - const {poolPositionMoverSelectors} = await getPoolSignatures(); - const libraries = { - ["contracts/protocol/libraries/logic/PositionMoverLogic.sol:PositionMoverLogic"]: - positionMoverLogic.address, - }; - const poolPositionMover = (await withSaveAndVerify( - await getContractFactory("PoolPositionMover", positionMoverLibraries), - eContractid.PoolPositionMoverImpl, - [ - provider, - bendDaoLendPoolLoan, - bendDaoLendPool, - poolV1, - protocolDataProviderV1, - capeV1, - capeV2, - apeCoin, - timeLockV1, - p2pPairStakingV1, - ], - verify, - false, - libraries, - poolPositionMoverSelectors - )) as PoolPositionMover; - - return { - poolPositionMover, - poolPositionMoverSelectors: poolPositionMoverSelectors.map( - (s) => s.signature - ), - }; -}; - export const deployPoolMarketplaceLibraries = async ( coreLibraries: Libraries, verify?: boolean @@ -985,18 +830,6 @@ export const getPoolSignatures = () => { PoolMarketplace__factory.abi ); - const poolApeStakingSelectors = getFunctionSignatures( - PoolApeStaking__factory.abi - ); - - const poolBorrowAndStakeSelectors = getFunctionSignatures( - PoolBorrowAndStake__factory.abi - ); - - const poolPositionMoverSelectors = getFunctionSignatures( - PoolPositionMover__factory.abi - ); - const poolAAPositionMoverSelectors = getFunctionSignatures( PoolAAPositionMover__factory.abi ); @@ -1012,11 +845,8 @@ export const getPoolSignatures = () => { ...poolCoreSelectors, ...poolParametersSelectors, ...poolMarketplaceSelectors, - ...poolApeStakingSelectors, - ...poolBorrowAndStakeSelectors, ...poolProxySelectors, ...poolParaProxyInterfacesSelectors, - ...poolPositionMoverSelectors, ...poolAAPositionMoverSelectors, ]; for (const selector of poolSelectors) { @@ -1035,10 +865,7 @@ export const getPoolSignatures = () => { poolCoreSelectors, poolParametersSelectors, poolMarketplaceSelectors, - poolApeStakingSelectors, - poolBorrowAndStakeSelectors, poolParaProxyInterfacesSelectors, - poolPositionMoverSelectors, poolAAPositionMoverSelectors, }; }; @@ -1056,25 +883,20 @@ export const getPoolSignaturesFromDb = async () => { eContractid.PoolMarketplaceImpl ); - const poolApeStakingSelectors = await getFunctionSignaturesFromDb( - eContractid.PoolApeStakingImpl - ); - const poolParaProxyInterfacesSelectors = await getFunctionSignaturesFromDb( eContractid.ParaProxyInterfacesImpl ); - const poolPositionMoverSelectors = await getFunctionSignaturesFromDb( - eContractid.PoolPositionMoverImpl + const poolAAPositionMoverSelectors = await getFunctionSignaturesFromDb( + eContractid.PoolAAPositionMoverImpl ); return { poolCoreSelectors, poolParametersSelectors, poolMarketplaceSelectors, - poolApeStakingSelectors, poolParaProxyInterfacesSelectors, - poolPositionMoverSelectors, + poolAAPositionMoverSelectors, }; }; @@ -1090,23 +912,8 @@ export const deployPoolComponents = async ( const parametersLibraries = await deployPoolParametersLibraries(verify); - const apeStakingLibraries = pick(coreLibraries, [ - "contracts/protocol/libraries/logic/BorrowLogic.sol:BorrowLogic", - "contracts/protocol/libraries/logic/SupplyLogic.sol:SupplyLogic", - ]); - - const allTokens = await getAllTokens(); - - const APE_WETH_FEE = 3000; - const WETH_USDC_FEE = 500; - - const { - poolCoreSelectors, - poolParametersSelectors, - poolMarketplaceSelectors, - poolApeStakingSelectors, - poolBorrowAndStakeSelectors, - } = getPoolSignatures(); + const {poolCoreSelectors, poolParametersSelectors, poolMarketplaceSelectors} = + getPoolSignatures(); const poolCore = (await withSaveAndVerify( await getContractFactory("PoolCore", coreLibraries), @@ -1144,59 +951,13 @@ export const deployPoolComponents = async ( poolMarketplaceSelectors )) as PoolMarketplace; - const config = getParaSpaceConfig(); - const treasuryAddress = config.Treasury; - const cApe = await getAutoCompoundApe(); - const poolApeStaking = allTokens.APE - ? ((await withSaveAndVerify( - await getContractFactory("PoolApeStaking", apeStakingLibraries), - eContractid.PoolApeStakingImpl, - [ - provider, - cApe.address, - allTokens.APE.address, - allTokens.USDC.address, - (await getUniswapV3SwapRouter()).address, - allTokens.WETH.address, - APE_WETH_FEE, - WETH_USDC_FEE, - treasuryAddress, - ], - verify, - false, - apeStakingLibraries, - poolApeStakingSelectors - )) as PoolApeStaking) - : undefined; - - const BorrowAndStakeLibraries = pick(coreLibraries, [ - "contracts/protocol/libraries/logic/BorrowLogic.sol:BorrowLogic", - ]); - const poolBorrowAndStake = allTokens.APE - ? ((await withSaveAndVerify( - await getContractFactory("PoolBorrowAndStake", BorrowAndStakeLibraries), - eContractid.PoolBorrowAndStakeImpl, - [provider, cApe.address, allTokens.APE.address], - verify, - false, - BorrowAndStakeLibraries, - poolBorrowAndStakeSelectors - )) as PoolBorrowAndStake) - : undefined; - return { poolCore, poolParameters, poolMarketplace, - poolApeStaking, - poolBorrowAndStake, poolCoreSelectors: poolCoreSelectors.map((s) => s.signature), poolParametersSelectors: poolParametersSelectors.map((s) => s.signature), poolMarketplaceSelectors: poolMarketplaceSelectors.map((s) => s.signature), - poolApeStakingSelectors: poolApeStakingSelectors.map((s) => s.signature), - poolBorrowAndStakeSelectors: poolBorrowAndStakeSelectors.map( - (s) => s.signature - ), }; }; @@ -2948,60 +2709,6 @@ export const deployAutoYieldApeImplAndAssignItToProxy = async ( ); }; -export const deployHelperContractImpl = async ( - cApeV1: tEthereumAddress, - verify?: boolean -) => { - const allTokens = await getAllTokens(); - const protocolDataProvider = await getProtocolDataProvider(); - const pCApe = ( - await protocolDataProvider.getReserveTokensAddresses(allTokens.cAPE.address) - ).xTokenAddress; - const pool = await getPoolProxy(); - const args = [ - allTokens.APE.address, - cApeV1, - allTokens.cAPE.address, - pCApe, - pool.address, - ]; - - return withSaveAndVerify( - await getContractFactory("HelperContract"), - eContractid.HelperContractImpl, - [...args], - verify - ) as Promise; -}; - -export const deployHelperContract = async ( - cApeV1: tEthereumAddress, - verify?: boolean -) => { - const helperImplementation = await deployHelperContractImpl(cApeV1, verify); - - const deployer = await getFirstSigner(); - const deployerAddress = await deployer.getAddress(); - - const initData = - helperImplementation.interface.encodeFunctionData("initialize"); - - const proxyInstance = await withSaveAndVerify( - await getContractFactory("InitializableAdminUpgradeabilityProxy"), - eContractid.HelperContract, - [], - verify - ); - - await waitForTx( - await (proxyInstance as InitializableAdminUpgradeabilityProxy)[ - "initialize(address,address,bytes)" - ](helperImplementation.address, deployerAddress, initData, GLOBAL_OVERRIDES) - ); - - return await getHelperContract(proxyInstance.address); -}; - export const deployPTokenCApe = async ( poolAddress: tEthereumAddress, verify?: boolean diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 3e7d03971..6be82322a 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -1318,8 +1318,8 @@ export const linkLibraries = ( if (addr === undefined) { continue; } - console.log("****linkLibraries*****libName:", libName); - console.log("****linkLibraries*****addr:", addr); + // console.log("****linkLibraries*****libName:", libName); + // console.log("****linkLibraries*****addr:", addr); for (const fixup of fixups) { bytecode = diff --git a/scripts/deployments/steps/06_pool.ts b/scripts/deployments/steps/06_pool.ts index 2776c069e..f199a3017 100644 --- a/scripts/deployments/steps/06_pool.ts +++ b/scripts/deployments/steps/06_pool.ts @@ -1,133 +1,55 @@ import {ZERO_ADDRESS} from "../../../helpers/constants"; import { deployAAPoolPositionMover, - deployMockBendDaoLendPool, deployPoolComponents, deployPoolParaProxyInterfaces, - deployPoolPositionMover, } from "../../../helpers/contracts-deployments"; import { getPoolProxy, getPoolAddressesProvider, - getAutoCompoundApe, - getAllTokens, - getUniswapV3SwapRouter, - getWETH, } from "../../../helpers/contracts-getters"; -import { - getContractAddressInDb, - registerContractInDb, -} from "../../../helpers/contracts-helpers"; +import {registerContractInDb} from "../../../helpers/contracts-helpers"; import {GLOBAL_OVERRIDES} from "../../../helpers/hardhat-constants"; -import { - getParaSpaceConfig, - isLocalTestnet, - waitForTx, -} from "../../../helpers/misc-utils"; -import {eContractid, ERC20TokenContractId} from "../../../helpers/types"; +import {waitForTx} from "../../../helpers/misc-utils"; +import {eContractid} from "../../../helpers/types"; export const step_06 = async (verify = false) => { const addressesProvider = await getPoolAddressesProvider(); - const paraSpaceConfig = getParaSpaceConfig(); - const allTokens = await getAllTokens(); try { const { poolCore, poolParameters, poolMarketplace, - poolApeStaking, - poolBorrowAndStake, poolCoreSelectors, poolParametersSelectors, poolMarketplaceSelectors, - poolApeStakingSelectors, - poolBorrowAndStakeSelectors, } = await deployPoolComponents(addressesProvider.address, verify); const {poolParaProxyInterfaces, poolParaProxyInterfacesSelectors} = await deployPoolParaProxyInterfaces(verify); + const {poolAAPositionMover, poolAAPositionMoverSelectors} = + await deployAAPoolPositionMover(verify); + await waitForTx( await addressesProvider.updatePoolImpl( [ + { + implAddress: poolParaProxyInterfaces.address, + action: 0, + functionSelectors: poolParaProxyInterfacesSelectors, + }, { implAddress: poolParameters.address, action: 0, functionSelectors: poolParametersSelectors, }, - ], - ZERO_ADDRESS, - "0x", - GLOBAL_OVERRIDES - ) - ); - - await waitForTx( - await addressesProvider.updatePoolImpl( - [ { implAddress: poolMarketplace.address, action: 0, functionSelectors: poolMarketplaceSelectors, }, - ], - ZERO_ADDRESS, - "0x", - GLOBAL_OVERRIDES - ) - ); - - if ( - paraSpaceConfig.BendDAO.LendingPoolLoan || - paraSpaceConfig.ParaSpaceV1 || - isLocalTestnet() - ) { - const bendDaoLendPoolLoan = - paraSpaceConfig.BendDAO.LendingPoolLoan || - (await getContractAddressInDb(eContractid.MockBendDaoLendPool)) || - (await deployMockBendDaoLendPool((await getWETH()).address)).address; - const bendDaoLendPool = - paraSpaceConfig.BendDAO.LendingPool || - (await getContractAddressInDb(eContractid.MockBendDaoLendPool)) || - (await deployMockBendDaoLendPool((await getWETH()).address)).address; - const {poolPositionMover, poolPositionMoverSelectors} = - await deployPoolPositionMover( - addressesProvider.address, - bendDaoLendPoolLoan, - bendDaoLendPool, - paraSpaceConfig.ParaSpaceV1?.PoolV1 || ZERO_ADDRESS, - paraSpaceConfig.ParaSpaceV1?.ProtocolDataProviderV1 || ZERO_ADDRESS, - paraSpaceConfig.ParaSpaceV1?.CApeV1 || ZERO_ADDRESS, - allTokens[ERC20TokenContractId.cAPE].address, - allTokens[ERC20TokenContractId.APE].address, - paraSpaceConfig.ParaSpaceV1?.TimeLockV1 || ZERO_ADDRESS, - paraSpaceConfig.ParaSpaceV1?.P2PPairStakingV1 || ZERO_ADDRESS, - verify - ); - - await waitForTx( - await addressesProvider.updatePoolImpl( - [ - { - implAddress: poolPositionMover.address, - action: 0, - functionSelectors: poolPositionMoverSelectors, - }, - ], - ZERO_ADDRESS, - "0x", - GLOBAL_OVERRIDES - ) - ); - } - - const {poolAAPositionMover, poolAAPositionMoverSelectors} = - await deployAAPoolPositionMover(verify); - - await waitForTx( - await addressesProvider.updatePoolImpl( - [ { implAddress: poolAAPositionMover.address, action: 0, @@ -140,40 +62,6 @@ export const step_06 = async (verify = false) => { ) ); - if (poolApeStaking) { - await waitForTx( - await addressesProvider.updatePoolImpl( - [ - { - implAddress: poolApeStaking.address, - action: 0, - functionSelectors: poolApeStakingSelectors, - }, - ], - ZERO_ADDRESS, - "0x", - GLOBAL_OVERRIDES - ) - ); - } - - if (poolBorrowAndStake) { - await waitForTx( - await addressesProvider.updatePoolImpl( - [ - { - implAddress: poolBorrowAndStake.address, - action: 0, - functionSelectors: poolBorrowAndStakeSelectors, - }, - ], - ZERO_ADDRESS, - "0x", - GLOBAL_OVERRIDES - ) - ); - } - const poolAddress = await addressesProvider.getPool(); const poolProxy = await getPoolProxy(poolAddress); @@ -194,41 +82,6 @@ export const step_06 = async (verify = false) => { ) ); - await waitForTx( - await addressesProvider.updatePoolImpl( - [ - { - implAddress: poolParaProxyInterfaces.address, - action: 0, - functionSelectors: poolParaProxyInterfacesSelectors, - }, - ], - ZERO_ADDRESS, - "0x", - GLOBAL_OVERRIDES - ) - ); - - if ( - allTokens[ERC20TokenContractId.APE] && - (await getContractAddressInDb(eContractid.UniswapV3SwapRouter)) - ) { - const uniswapV3Router = await getUniswapV3SwapRouter(); - const cAPE = await getAutoCompoundApe(); - await waitForTx( - await poolProxy.unlimitedApproveTo( - allTokens[ERC20TokenContractId.APE].address, - uniswapV3Router.address - ) - ); - await waitForTx( - await poolProxy.unlimitedApproveTo( - allTokens[ERC20TokenContractId.APE].address, - cAPE.address - ) - ); - } - await registerContractInDb(eContractid.PoolProxy, poolProxy, [ addressesProvider.address, ]); diff --git a/scripts/deployments/steps/20_p2pPairStaking.ts b/scripts/deployments/steps/20_p2pPairStaking.ts index d78e92b65..1c2f7c9ab 100644 --- a/scripts/deployments/steps/20_p2pPairStaking.ts +++ b/scripts/deployments/steps/20_p2pPairStaking.ts @@ -1,76 +1,3 @@ -import {deployP2PPairStaking} from "../../../helpers/contracts-deployments"; -import { - getAllTokens, - getNToken, - getPoolProxy, -} from "../../../helpers/contracts-getters"; -import {getParaSpaceConfig, waitForTx} from "../../../helpers/misc-utils"; -import { - ERC20TokenContractId, - ERC721TokenContractId, -} from "../../../helpers/types"; - export const step_20 = async (verify = false) => { - const paraSpaceConfig = getParaSpaceConfig(); - try { - if (!paraSpaceConfig.ReservesConfig[ERC20TokenContractId.APE]) { - return; - } - // deploy P2PPairStaking - const p2pPairStaking = await deployP2PPairStaking(verify); - const allTokens = await getAllTokens(); - const pool = await getPoolProxy(); - - const bayc = allTokens[ERC721TokenContractId.BAYC]; - const mayc = allTokens[ERC721TokenContractId.MAYC]; - const bakc = allTokens[ERC721TokenContractId.BAKC]; - - if (bayc) { - const nBAYC = await getNToken( - ( - await pool.getReserveData(bayc.address) - ).xTokenAddress - ); - await waitForTx( - await nBAYC.setApprovalForAllTo( - bayc.address, - p2pPairStaking.address, - true - ) - ); - } - - if (mayc) { - const nMAYC = await getNToken( - ( - await pool.getReserveData(mayc.address) - ).xTokenAddress - ); - await waitForTx( - await nMAYC.setApprovalForAllTo( - mayc.address, - p2pPairStaking.address, - true - ) - ); - } - - if (bakc) { - const nBAKC = await getNToken( - ( - await pool.getReserveData(bakc.address) - ).xTokenAddress - ); - await waitForTx( - await nBAKC.setApprovalForAllTo( - bakc.address, - p2pPairStaking.address, - true - ) - ); - } - } catch (error) { - console.error(error); - process.exit(1); - } + console.log("nothing to for step 20 on L2", verify); }; diff --git a/scripts/deployments/steps/21_helperContract.ts b/scripts/deployments/steps/21_helperContract.ts index 2ad31a73f..7e7a8ed98 100644 --- a/scripts/deployments/steps/21_helperContract.ts +++ b/scripts/deployments/steps/21_helperContract.ts @@ -1,19 +1,3 @@ -import {deployHelperContract} from "../../../helpers/contracts-deployments"; -import {getParaSpaceConfig} from "../../../helpers/misc-utils"; -import {ERC20TokenContractId} from "../../../helpers/types"; -import {getAllTokens} from "../../../helpers/contracts-getters"; export const step_21 = async (verify = false) => { - const paraSpaceConfig = getParaSpaceConfig(); - try { - if (!paraSpaceConfig.ReservesConfig[ERC20TokenContractId.APE]) { - return; - } - - const allTokens = await getAllTokens(); - //for test env, we use same address for cApeV1 and cApeV2 - await deployHelperContract(allTokens.cAPE.address, verify); - } catch (error) { - console.error(error); - process.exit(1); - } + console.log("nothing to for step 21 on L2", verify); }; diff --git a/scripts/upgrade/pool.ts b/scripts/upgrade/pool.ts index 0166a2106..1948603c7 100644 --- a/scripts/upgrade/pool.ts +++ b/scripts/upgrade/pool.ts @@ -1,32 +1,20 @@ import {ZERO_ADDRESS} from "../../helpers/constants"; import { - deployPoolApeStaking, - deployPoolBorrowAndStake, deployPoolComponents, deployPoolCore, deployPoolMarketplace, deployPoolParameters, - deployPoolPositionMover, deployAAPoolPositionMover, } from "../../helpers/contracts-deployments"; import { - getAllTokens, getPoolAddressesProvider, getPoolProxy, } from "../../helpers/contracts-getters"; -import { - dryRunEncodedData, - getContractAddressInDb, -} from "../../helpers/contracts-helpers"; +import {dryRunEncodedData} from "../../helpers/contracts-helpers"; import {DRY_RUN, GLOBAL_OVERRIDES} from "../../helpers/hardhat-constants"; -import {getParaSpaceConfig, waitForTx} from "../../helpers/misc-utils"; -import { - eContractid, - ERC20TokenContractId, - tEthereumAddress, -} from "../../helpers/types"; +import {waitForTx} from "../../helpers/misc-utils"; +import {tEthereumAddress} from "../../helpers/types"; import {IParaProxy} from "../../types"; -import {zeroAddress} from "ethereumjs-util"; export const upgradeProxyImplementations = async ( implementations: [string, string[], string[]][] @@ -129,11 +117,9 @@ export const resetPool = async (verify = false) => { poolCore, poolParameters, poolMarketplace, - poolApeStaking, poolCoreSelectors: newPoolCoreSelectors, poolParametersSelectors: newPoolParametersSelectors, poolMarketplaceSelectors: newPoolMarketplaceSelectors, - poolApeStakingSelectors: newPoolApeStakingSelectors, } = await deployPoolComponents(addressesProvider.address, verify); console.timeEnd("deploy PoolComponent"); @@ -143,27 +129,17 @@ export const resetPool = async (verify = false) => { [poolParameters.address, newPoolParametersSelectors, []], ] as [string, string[], string[]][]; - if (poolApeStaking) { - implementations.push([ - poolApeStaking.address, - newPoolApeStakingSelectors, - [], - ]); - } - await upgradeProxyImplementations(implementations); }; export const upgradePool = async ( { oldPoolCore, - oldPoolApeStaking, oldPoolMarketplace, oldPoolParameters, }: { oldPoolCore: tEthereumAddress; oldPoolMarketplace: tEthereumAddress; - oldPoolApeStaking: tEthereumAddress; oldPoolParameters: tEthereumAddress; }, verify = false @@ -172,9 +148,6 @@ export const upgradePool = async ( const pool = await getPoolProxy(); console.time("deploy PoolComponent"); const oldPoolCoreSelectors = await pool.facetFunctionSelectors(oldPoolCore); - const oldPoolApeStakingSelectors = await pool.facetFunctionSelectors( - oldPoolApeStaking - ); const oldPoolMarketplaceSelectors = await pool.facetFunctionSelectors( oldPoolMarketplace ); @@ -186,11 +159,9 @@ export const upgradePool = async ( poolCore, poolParameters, poolMarketplace, - poolApeStaking, poolCoreSelectors: newPoolCoreSelectors, poolParametersSelectors: newPoolParametersSelectors, poolMarketplaceSelectors: newPoolMarketplaceSelectors, - poolApeStakingSelectors: newPoolApeStakingSelectors, } = await deployPoolComponents(addressesProvider.address, verify); console.timeEnd("deploy PoolComponent"); @@ -208,14 +179,6 @@ export const upgradePool = async ( ], ] as [string, string[], string[]][]; - if (poolApeStaking) { - implementations.push([ - poolApeStaking.address, - newPoolApeStakingSelectors, - oldPoolApeStakingSelectors, - ]); - } - await upgradeProxyImplementations(implementations); }; @@ -263,59 +226,6 @@ export const upgradePoolMarketplace = async ( await upgradeProxyImplementations(implementations); }; -export const upgradePoolApeStaking = async ( - oldPoolApeStaking: tEthereumAddress, - verify = false -) => { - const addressesProvider = await getPoolAddressesProvider(); - const pool = await getPoolProxy(); - const oldPoolApeStakingSelectors = await pool.facetFunctionSelectors( - oldPoolApeStaking - ); - - const {poolApeStaking, poolApeStakingSelectors: newPoolApeStakingSelectors} = - await deployPoolApeStaking(addressesProvider.address, verify); - - const implementations = [ - [ - poolApeStaking.address, - newPoolApeStakingSelectors, - oldPoolApeStakingSelectors, - ], - ] as [string, string[], string[]][]; - - await upgradeProxyImplementations(implementations); -}; - -export const upgradeBorrowApeAndStake = async ( - oldPoolApeStaking: tEthereumAddress, - verify = false -) => { - const addressesProvider = await getPoolAddressesProvider(); - let oldPoolApeStakingSelectors: Array = []; - if (oldPoolApeStaking != zeroAddress()) { - const pool = await getPoolProxy(); - oldPoolApeStakingSelectors = await pool.facetFunctionSelectors( - oldPoolApeStaking - ); - } - - const { - poolBorrowAndStake, - poolBorrowAndStakeSelectors: newPoolApeStakingSelectors, - } = await deployPoolBorrowAndStake(addressesProvider.address, verify); - - const implementations = [ - [ - poolBorrowAndStake.address, - newPoolApeStakingSelectors, - oldPoolApeStakingSelectors, - ], - ] as [string, string[], string[]][]; - - await upgradeProxyImplementations(implementations); -}; - export const upgradePoolParameters = async ( oldPoolParameters: tEthereumAddress, verify = false @@ -340,53 +250,6 @@ export const upgradePoolParameters = async ( await upgradeProxyImplementations(implementations); }; -export const upgradePoolPositionMover = async ( - oldPoolPositionMover: tEthereumAddress, - verify = false -) => { - const addressesProvider = await getPoolAddressesProvider(); - const pool = await getPoolProxy(); - const allTokens = await getAllTokens(); - const paraSpaceConfig = getParaSpaceConfig(); - const oldPoolPositionMoverSelectors = await pool.facetFunctionSelectors( - oldPoolPositionMover - ); - - const bendDaoLendPoolLoan = - paraSpaceConfig.BendDAO.LendingPoolLoan || - (await getContractAddressInDb(eContractid.MockBendDaoLendPool)); - const bendDaoLendPool = - paraSpaceConfig.BendDAO.LendingPool || - (await getContractAddressInDb(eContractid.MockBendDaoLendPool)); - - const { - poolPositionMover, - poolPositionMoverSelectors: newPoolPositionMoverSelectors, - } = await deployPoolPositionMover( - addressesProvider.address, - bendDaoLendPoolLoan, - bendDaoLendPool, - paraSpaceConfig.ParaSpaceV1?.PoolV1 || ZERO_ADDRESS, - paraSpaceConfig.ParaSpaceV1?.ProtocolDataProviderV1 || ZERO_ADDRESS, - paraSpaceConfig.ParaSpaceV1?.CApeV1 || ZERO_ADDRESS, - allTokens[ERC20TokenContractId.cAPE].address, - allTokens[ERC20TokenContractId.APE].address, - paraSpaceConfig.ParaSpaceV1?.TimeLockV1 || ZERO_ADDRESS, - paraSpaceConfig.ParaSpaceV1?.P2PPairStakingV1 || ZERO_ADDRESS, - verify - ); - - const implementations = [ - [ - poolPositionMover.address, - newPoolPositionMoverSelectors, - oldPoolPositionMoverSelectors, - ], - ] as [string, string[], string[]][]; - - await upgradeProxyImplementations(implementations); -}; - export const upgradePoolAAPositionMover = async ( oldAAPoolPositionMover: tEthereumAddress, verify = false diff --git a/test/_pool_ape_staking.spec.ts b/test/_pool_ape_staking.spec.ts deleted file mode 100644 index c88d9df4b..000000000 --- a/test/_pool_ape_staking.spec.ts +++ /dev/null @@ -1,2985 +0,0 @@ -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; -import {expect} from "chai"; -import {MAX_UINT_AMOUNT, ZERO_ADDRESS, ONE_ADDRESS} from "../helpers/constants"; -import { - getAutoCompoundApe, - getPToken, - getPTokenSApe, - getVariableDebtToken, -} from "../helpers/contracts-getters"; -import { - convertToCurrencyDecimals, - isUsingAsCollateral, -} from "../helpers/contracts-helpers"; -import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; -import {VariableDebtToken, PTokenSApe, PToken, AutoCompoundApe} from "../types"; -import {TestEnv} from "./helpers/make-suite"; -import {testEnvFixture} from "./helpers/setup-env"; - -import { - borrowAndValidate, - changePriceAndValidate, - changeSApePriceAndValidate, - mintAndValidate, - supplyAndValidate, -} from "./helpers/validated-steps"; -import {almostEqual} from "./helpers/uniswapv3-helper"; -import {ProtocolErrors} from "../helpers/types"; -import {parseEther} from "ethers/lib/utils"; -import { - executeAcceptBidWithCredit, - executeSeaportBuyWithCredit, -} from "./helpers/marketplace-helper"; -import {BigNumber} from "ethers"; - -describe("APE Coin Staking Test", () => { - let testEnv: TestEnv; - let variableDebtApeCoin: VariableDebtToken; - let variableDebtCApeCoin: VariableDebtToken; - let pApeCoin: PToken; - let cApe: AutoCompoundApe; - let pcApeCoin: PToken; - let pSApeCoin: PTokenSApe; - const sApeAddress = ONE_ADDRESS; - const InitialNTokenApeBalance = parseEther("100"); - - const fixture = async () => { - testEnv = await loadFixture(testEnvFixture); - const { - ape, - mayc, - bayc, - users, - bakc, - protocolDataProvider, - pool, - apeCoinStaking, - nMAYC, - nBAYC, - } = testEnv; - const user1 = users[0]; - const depositor = users[1]; - const user4 = users[5]; - - const { - xTokenAddress: pApeCoinAddress, - variableDebtTokenAddress: variableDebtApeCoinAddress, - } = await protocolDataProvider.getReserveTokensAddresses(ape.address); - const {xTokenAddress: pSApeCoinAddress} = - await protocolDataProvider.getReserveTokensAddresses(sApeAddress); - - cApe = await getAutoCompoundApe(); - const { - xTokenAddress: pcApeCoinAddress, - variableDebtTokenAddress: variableDebtCApeCoinAddress, - } = await protocolDataProvider.getReserveTokensAddresses(cApe.address); - - variableDebtApeCoin = await getVariableDebtToken( - variableDebtApeCoinAddress - ); - variableDebtCApeCoin = await getVariableDebtToken( - variableDebtCApeCoinAddress - ); - pApeCoin = await getPToken(pApeCoinAddress); - pSApeCoin = await getPTokenSApe(pSApeCoinAddress); - pcApeCoin = await getPToken(pcApeCoinAddress); - - await supplyAndValidate(ape, "20000", depositor, true); - await changePriceAndValidate(ape, "0.001"); - await changePriceAndValidate(cApe, "0.001"); - await changeSApePriceAndValidate(sApeAddress, "0.001"); - - await changePriceAndValidate(mayc, "50"); - await changePriceAndValidate(bayc, "50"); - - await waitForTx(await bakc["mint(uint256,address)"]("2", user1.address)); - - await waitForTx( - await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await bakc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - - // send extra tokens to the apestaking contract for rewards - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"]( - apeCoinStaking.address, - parseEther("100000000000") - ) - ); - - // send extra tokens to the nToken contract for testing ape balance check - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](nMAYC.address, InitialNTokenApeBalance) - ); - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](nBAYC.address, InitialNTokenApeBalance) - ); - - await mintAndValidate(ape, "1", user4); - await waitForTx( - await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - // user4 deposit MINIMUM_LIQUIDITY to make test case easy - const MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); - await waitForTx( - await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) - ); - - return testEnv; - }; - - it("TC-pool-ape-staking-01 test borrowApeAndStake: failed when borrow + cash < staking amount (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "16000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "16000"); - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ).to.be.revertedWith(ProtocolErrors.TOTAL_STAKING_AMOUNT_WRONG); - }); - - it("TC-pool-ape-staking-02 test borrowApeAndStake: failed when borrow + cash > staking amount (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "16000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "16000"); - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ).to.be.revertedWith(ProtocolErrors.TOTAL_STAKING_AMOUNT_WRONG); - }); - - it("TC-pool-ape-staking-03 test borrowApeAndStake: use 100% cash", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(0); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - expect(userAccount.totalDebtBase).equal(0); - //50 * 0.325 + 15 * 0.2 = 19.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "19.25") - ); - }); - - it("TC-pool-ape-staking-04 test borrowApeAndStake: part cash, part debt", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - // 50 * 0.3250 + 7000 * 0.001 * 0.2 = 17.65 - // 17.65 / 0.001 = 17650 - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount2); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - //8000*0.001 = 8 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "8") - ); - //50 * 0.325 + 15 * 0.2 - 8=11.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "11.25") - ); - }); - - it("TC-pool-ape-staking-05 test borrowApeAndStake: use 100% debt", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - //15000*0.001 = 15 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "15") - ); - //50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "4.25") - ); - }); - - it("TC-pool-ape-staking-06 test withdrawBAKC fails when hf < 1 (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - let withdrawAmount = await convertToCurrencyDecimals(ape.address, "3000"); - expect( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: withdrawAmount}]) - ); - withdrawAmount = await convertToCurrencyDecimals(ape.address, "4000"); - expect( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: withdrawAmount}]) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount2); - - await expect( - pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ).to.be.revertedWith( - ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - }); - - it("TC-pool-ape-staking-07 test withdrawApeCoin fails when hf < 1 (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - nMAYC, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - const withdrawAmount = await convertToCurrencyDecimals(ape.address, "4000"); - expect( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: withdrawAmount, - isUncommit: false, - }, - ]) - ); - expect( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: withdrawAmount, - isUncommit: true, - }, - ]) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount1); - - await expect( - pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) - ).to.be.revertedWith( - ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - - const apeBalanceForNToken = await ape.balanceOf(nMAYC.address); - expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); - }); - - it("TC-pool-ape-staking-08 test withdrawBAKC fails when hf < 1 (revert expected)", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await expect( - pool - .connect(user2.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-09 test withdrawApeCoin fails when user is not the owner (revert expected)", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await expect( - pool - .connect(user2.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-10 test claimBAKC success when hf > 1", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - nMAYC, - apeCoinStaking, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}] - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - expect(pendingRewardsPool2).to.be.gt(0); - - const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( - 3, - nMAYC.address, - "1" - ); - expect(pendingRewardsPool3).to.be.gt(0); - - const userBalance = await ape.balanceOf(user1.address); - - expect( - await pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 1}]) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount.add(pendingRewardsPool2)); - - expect(await ape.balanceOf(user1.address)).to.be.eq( - userBalance.add(pendingRewardsPool3) - ); - - const apeBalanceForNToken = await ape.balanceOf(nMAYC.address); - expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); - }); - - it("TC-pool-ape-staking-11 test claimBAKC success when hf < 1 (ape reward for bakc pool is not used as collateral)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - apeCoinStaking, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 1, amount: amount2}] - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - expect(pendingRewardsPool2).to.be.gt(0); - - const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( - 3, - nMAYC.address, - "1" - ); - expect(pendingRewardsPool3).to.be.gt(0); - - const userBalance = await ape.balanceOf(user1.address); - - // drop HF to liquidation levels - await changePriceAndValidate(mayc, "3"); - - expect( - await pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 1}]) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount.add(pendingRewardsPool2)); - - expect(await ape.balanceOf(user1.address)).to.be.eq( - userBalance.add(pendingRewardsPool3) - ); - }); - - it("TC-pool-ape-staking-12 test claimApeCoin succeeds when hf > 1", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - nMAYC, - apeCoinStaking, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - - const userBalance = await ape.balanceOf(user1.address); - - expect(await pool.connect(user1.signer).claimApeCoin(mayc.address, [0])); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - expect(await ape.balanceOf(user1.address)).to.be.eq( - userBalance.add(pendingRewardsPool2) - ); - }); - - it("TC-pool-ape-staking-13 test claimApeCoin fails when hf < 1 (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - weth, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(ape, "0.002"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - const userAccount = await pool.getUserAccountData(user1.address); - //40 + 15000*0.002 = 70 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "70") - ); - //15000*0.002 = 30 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "30") - ); - //40 * 0.325 + 30 * 0.2 - 30=-11 - almostEqual(userAccount.availableBorrowsBase, 0); - - // drop HF to liquidation levels - await changePriceAndValidate(mayc, "3"); - - await expect( - pool.connect(user1.signer).claimApeCoin(mayc.address, [0]) - ).to.be.revertedWith( - ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - }); - - it("TC-pool-ape-staking-14 test unstakeApePositionAndRepay repays cape debt - no excess", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await mintAndValidate(ape, "20000", user2); - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("20000")) - ); - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, parseEther("20000"), user2.address, 0) - ); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(0); - - const pApeBalance = await pApeCoin.balanceOf(user1.address); - expect(pApeBalance).equal(0); - - const cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - const limit = await convertToCurrencyDecimals(cApe.address, "0.1"); - expect(cApeDebt.lt(limit)).equal(true); - }); - - it("TC-pool-ape-staking-15 test unstakeApePositionAndRepay repays cape debt", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user2); - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("10000")) - ); - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, parseEther("10000"), user2.address, 0) - ); - - const amount1 = parseEther("7000"); - const amount2 = parseEther("8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount1, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [] - ) - ); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: amount2, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - almostEqual(apeDebt, amount1); - - let capeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - almostEqual(capeDebt, amount2); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(0); - - const pCApeBalance = await pcApeCoin.balanceOf(user1.address); - almostEqual(pCApeBalance, amount1); - - capeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - expect(capeDebt).equal(0); - }); - - it("TC-pool-ape-staking-16 test unstakeApePositionAndRepay bakc reward should transfer to user wallet", async () => { - const { - users: [user1], - ape, - mayc, - pool, - apeCoinStaking, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsMaycPool = await apeCoinStaking.pendingRewards( - 2, - ZERO_ADDRESS, - "0" - ); - expect(pendingRewardsMaycPool).to.be.gt(0); - const pendingRewardsBakcPool = await apeCoinStaking.pendingRewards( - 3, - ZERO_ADDRESS, - "0" - ); - expect(pendingRewardsBakcPool).to.be.gt(0); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const userBalance = await ape.balanceOf(user1.address); - - expect(userBalance).to.be.eq(pendingRewardsBakcPool); - }); - - it("TC-pool-ape-staking-17 test unstakeApePositionAndRepay by others fails when hf > 1(revert expected)", async () => { - const { - users: [user1, unstaker], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await expect( - pool.connect(unstaker.signer).unstakeApePositionAndRepay(mayc.address, 0) - ).to.be.revertedWith(ProtocolErrors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD); - }); - - it("TC-pool-ape-staking-18 test unstakeApePositionAndRepay by others succeeds when hf < 1", async () => { - const { - users: [user1, unstaker, , , user2], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await mintAndValidate(ape, "20000", user2); - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("20000")) - ); - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, parseEther("20000"), user2.address, 0) - ); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await changePriceAndValidate(mayc, "40"); - await changePriceAndValidate(cApe, "0.08"); - - expect( - await pool - .connect(unstaker.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(0); - - const pcApeBalance = await pcApeCoin.balanceOf(user1.address); - expect(pcApeBalance).equal(0); - - const cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - const target = await convertToCurrencyDecimals(cApe.address, "45"); - almostEqual(cApeDebt, target); - }); - - it("TC-pool-ape-staking-19 test can stake multiple times and partially unstake afterwards", async () => { - const { - users: [user1, unstaker, , , user2], - ape, - mayc, - bayc, - pool, - nMAYC, - nBAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await mintAndValidate(ape, "30000", user2); - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("30000")) - ); - await waitForTx( - await cApe.connect(user2.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user2.signer) - .supply(cApe.address, parseEther("30000"), user2.address, 0) - ); - - await supplyAndValidate(mayc, "2", user1, true); - await supplyAndValidate(bayc, "2", user1, true); - - const amount = await convertToCurrencyDecimals(cApe.address, "3000"); - const halfAmount = await convertToCurrencyDecimals(cApe.address, "9000"); - const totalAmount = await convertToCurrencyDecimals(cApe.address, "18000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: cApe.address, - borrowAmount: halfAmount, - cashAmount: 0, - }, - [ - {tokenId: 0, amount: amount}, - {tokenId: 1, amount: amount}, - ], - [{mainTokenId: 1, bakcTokenId: 0, amount: amount}] - ) - ); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: cApe.address, - borrowAmount: halfAmount, - cashAmount: 0, - }, - [ - {tokenId: 0, amount: amount}, - {tokenId: 1, amount: amount}, - ], - [{mainTokenId: 1, bakcTokenId: 1, amount: amount}] - ) - ); - - let maycStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(maycStake).equal(halfAmount); - - let baycStake = await nBAYC.getUserApeStakingAmount(user1.address); - expect(baycStake).equal(halfAmount); - - let pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(totalAmount); - - let cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - almostEqual(cApeDebt, totalAmount); - - let bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - let userAccount = await pool.getUserAccountData(user1.address); - //50 * 4 + 18000*0.001 = 218 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "218") - ); - //18000*0.001 = 18 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "18") - ); - //50 * 2 * 0.4 + 50 * 2 * 0.325 + 18 * 0.2 - 18 = 58.1 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "58.1") - ); - - await changePriceAndValidate(mayc, "10"); - await changePriceAndValidate(bayc, "10"); - await changePriceAndValidate(cApe, "0.01"); - await changeSApePriceAndValidate(sApeAddress, "0.01"); - - expect( - await pool - .connect(unstaker.signer) - .unstakeApePositionAndRepay(mayc.address, 1) - ); - - maycStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(maycStake).equal(amount); - - baycStake = await nBAYC.getUserApeStakingAmount(user1.address); - expect(baycStake).equal(halfAmount); - - pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount.add(halfAmount)); - - cApeDebt = await variableDebtCApeCoin.balanceOf(user1.address); - //12000 + 6000*3/1000 - almostEqual( - cApeDebt, - amount - .add(halfAmount) - .add(await convertToCurrencyDecimals(weth.address, "18")) - ); - - bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - userAccount = await pool.getUserAccountData(user1.address); - //10 * 4 + 12000*0.01 = 160 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "160") - ); - //12018*0.01 = 120.18 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "120.18") - ); - - let apeBalanceForNToken = await ape.balanceOf(nMAYC.address); - expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); - apeBalanceForNToken = await ape.balanceOf(nBAYC.address); - expect(apeBalanceForNToken).equal(InitialNTokenApeBalance); - }); - - it("TC-pool-ape-staking-20 test can liquidate NFT with existing staking positions", async () => { - const { - users: [user1, liquidator], - ape, - mayc, - pool, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8"); - const amount = await convertToCurrencyDecimals(ape.address, "7008"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const borrowAmount = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool - .connect(user1.signer) - .borrow(ape.address, borrowAmount, 0, user1.address) - ); - - await supplyAndValidate(weth, "91", liquidator, true, "200000"); - - // drop HF and ERC-721_HF below 1 - await changePriceAndValidate(mayc, "3"); - - // start auction - await waitForTx( - await pool - .connect(liquidator.signer) - .startAuction(user1.address, mayc.address, 0) - ); - - const apeDebtBefore = await variableDebtApeCoin.balanceOf(user1.address); - - // try to liquidate the NFT - expect( - await pool - .connect(liquidator.signer) - .liquidateERC721( - mayc.address, - user1.address, - 0, - await convertToCurrencyDecimals(weth.address, "13"), - false, - {gasLimit: 5000000} - ) - ); - - expect(await ape.balanceOf(user1.address)).to.be.eq(borrowAmount); - - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).to.be.eq(0); // whole position unstaked - - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - almostEqual(apeDebt, apeDebtBefore); // no debt repaid - - expect(await bakc.ownerOf("0")).to.be.eq(user1.address); - expect(await mayc.ownerOf("0")).to.be.eq(liquidator.address); - }); - - it("TC-pool-ape-staking-21 test cannot borrow and stake an amount over user's available to borrow (revert expected)", async () => { - const { - users: [user1, depositor], - ape, - mayc, - pool, - weth, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(weth, "5", depositor, true); - await changePriceAndValidate(mayc, "10"); - await borrowAndValidate(weth, "3", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ).to.be.revertedWith(ProtocolErrors.COLLATERAL_CANNOT_COVER_NEW_BORROW); - }); - - it("TC-pool-ape-staking-22 test can transfer NFT with existing staking positions", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - expect(await nMAYC.balanceOf(user1.address)).to.be.equal(1); - expect(await nMAYC.balanceOf(user2.address)).to.be.equal(0); - expect(await pSApeCoin.balanceOf(user1.address)).equal(amount); - expect(await pSApeCoin.balanceOf(user2.address)).equal(0); - - expect( - await nMAYC - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user2.address, - 0, - {gasLimit: 5000000} - ) - ); - - expect(await nMAYC.balanceOf(user1.address)).to.be.equal(0); - expect(await nMAYC.balanceOf(user2.address)).to.be.equal(1); - expect(await pSApeCoin.balanceOf(user1.address)).equal(0); - expect(await pSApeCoin.balanceOf(user2.address)).equal(0); - }); - - it("TC-pool-ape-staking-23 test market accept bid offer should success", async () => { - const { - bayc, - nBAYC, - usdc, - pool, - ape, - users: [taker, maker, middleman], - } = await loadFixture(fixture); - const makerInitialBalance = "800"; - const middlemanInitialBalance = "200"; - const payNowAmount = await convertToCurrencyDecimals(usdc.address, "800"); - const creditAmount = await convertToCurrencyDecimals(usdc.address, "200"); - - const startAmount = payNowAmount.add(creditAmount); - const endAmount = startAmount; // fixed price but offerer cannot afford this - const nftId = 0; - - // 1, mint USDC to maker - await mintAndValidate(usdc, makerInitialBalance, maker); - - // 2, middleman supplies USDC to pool to be borrowed by maker later - await supplyAndValidate(usdc, middlemanInitialBalance, middleman, true); - - // 3, mint ntoken for taker - await mintAndValidate(ape, "15000", taker); - await supplyAndValidate(bayc, "1", taker, true); - - // 4, ape staking for ntoken - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(taker.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - expect(await nBAYC.balanceOf(taker.address)).to.be.equal(1); - expect(await nBAYC.balanceOf(maker.address)).to.be.equal(0); - expect(await pSApeCoin.balanceOf(taker.address)).equal(amount); - expect(await pSApeCoin.balanceOf(maker.address)).equal(0); - - // 5, accept order - await executeAcceptBidWithCredit( - nBAYC, - usdc, - startAmount, - endAmount, - creditAmount, - nftId, - maker, - taker - ); - - // taker bayc should reduce - expect(await nBAYC.balanceOf(taker.address)).to.be.equal(0); - expect(await nBAYC.balanceOf(maker.address)).to.be.equal(1); - expect(await pSApeCoin.balanceOf(taker.address)).equal(0); - expect(await pSApeCoin.balanceOf(maker.address)).equal(0); - }); - - it("TC-pool-ape-staking-24 test market buy with credit should success", async () => { - const { - bayc, - nBAYC, - usdc, - pool, - ape, - users: [maker, taker, middleman], - } = await loadFixture(fixture); - const makerInitialBalance = "800"; - const middlemanInitialBalance = "200"; - const payNowAmount = await convertToCurrencyDecimals(usdc.address, "800"); - const creditAmount = await convertToCurrencyDecimals(usdc.address, "200"); - - const startAmount = payNowAmount.add(creditAmount); - const endAmount = startAmount; // fixed price but offerer cannot afford this - const nftId = 0; - - // 1, mint USDC to taker - await mintAndValidate(usdc, makerInitialBalance, taker); - - // 2, middleman supplies USDC to pool to be borrowed by taker later - await supplyAndValidate(usdc, middlemanInitialBalance, middleman, true); - - // 3, mint ntoken for maker - await mintAndValidate(ape, "15000", maker); - await supplyAndValidate(bayc, "1", maker, true); - - // 4, ape staking for ntoken - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - expect( - await pool.connect(maker.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - expect(await nBAYC.balanceOf(maker.address)).to.be.equal(1); - expect(await nBAYC.balanceOf(taker.address)).to.be.equal(0); - expect(await pSApeCoin.balanceOf(maker.address)).equal(amount); - expect(await pSApeCoin.balanceOf(taker.address)).equal(0); - - // 5, buy with credit - await waitForTx( - await usdc.connect(taker.signer).approve(pool.address, startAmount) - ); - await executeSeaportBuyWithCredit( - nBAYC, - usdc, - startAmount, - endAmount, - creditAmount, - nftId, - maker, - taker - ); - - // taker bayc should reduce - expect(await nBAYC.balanceOf(maker.address)).to.be.equal(0); - expect(await nBAYC.balanceOf(taker.address)).to.be.equal(1); - expect(await pSApeCoin.balanceOf(maker.address)).equal(0); - expect(await pSApeCoin.balanceOf(taker.address)).equal(0); - }); - - it("TC-pool-ape-staking-25 unstakeApePositionAndRepay should set cApe as collateral", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - const apeData = await pool.getReserveData(cApe.address); - await supplyAndValidate(ape, "1", user1, true); - await waitForTx( - await pool - .connect(user1.signer) - .setUserUseERC20AsCollateral(ape.address, false) - ); - let userConfig = BigNumber.from( - (await pool.getUserConfiguration(user1.address)).data - ); - expect(isUsingAsCollateral(userConfig, apeData.id)).to.be.false; - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - userConfig = BigNumber.from( - (await pool.getUserConfiguration(user1.address)).data - ); - expect(isUsingAsCollateral(userConfig, apeData.id)).to.be.true; - }); - - it("TC-pool-ape-staking-26 test borrowApeAndStake: User tries to staking on not Supplying (revert expected)", async () => { - const { - users: [user1], - ape, - bayc, - pool, - } = await loadFixture(fixture); - - await mintAndValidate(bayc, "1", user1); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-27 test borrowApeAndStake: User tries to staking 0 ape icon for BAYC (revert expected)", async () => { - const { - users: [user1], - ape, - bayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "0"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = amount1.add(amount2); - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ).to.be.revertedWith("DepositMoreThanOneAPE()"); - }); - - it("TC-pool-ape-staking-28 test borrowApeAndStake: only staking BAKC", async () => { - const { - users: [user1], - ape, - bayc, - nBAYC, - pool, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "0"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = amount1.add(amount2); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] - ) - ); - - // User 1 - totalStake should increased in Stake amount - const totalStake = await nBAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - // User 1 - Debt should increased in borrowAmount - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount2); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 8000*0.001 = 58 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "58") - ); - //8000*0.001 = 8 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "8") - ); - - //50 * 0.4 + 8 * 0.2 - 8=13.6 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "13.6") - ); - }); - - it("TC-pool-ape-staking-29 test borrowApeAndStake: BAYC staked Add BAKC after first Pairing", async () => { - const { - users: [user1], - bayc, - ape, - pool, - weth, - nBAYC, - bakc, - } = await loadFixture(fixture); - await supplyAndValidate(bayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = amount1.add(amount2); - - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](user1.address, amount) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [] - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - // User 1 - totalStake should increased in Stake amount - const totalStake = await nBAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - // User 1 - Debt should increased in borrowAmount - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount2); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - //8000*0.001 = 8 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "8") - ); - //50 * 0.4 + 15 * 0.2 - 8=15 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "15") - ); - }); - - it("TC-pool-ape-staking-30 test borrowApeAndStake: MAYC staked Add BAKC after first Pairing", async () => { - const { - users: [user1], - mayc, - weth, - nMAYC, - ape, - pool, - bakc, - } = await loadFixture(fixture); - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = amount1.add(amount2); - - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](user1.address, amount) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [] - ) - ); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - // User 1 - totalStake should increased in Stake amount - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - // User 1 - Debt should increased in borrowAmount - const apeDebt = await variableDebtApeCoin.balanceOf(user1.address); - expect(apeDebt).equal(amount2); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - //50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "65") - ); - //8000*0.001 = 8 - expect(userAccount.totalDebtBase).equal( - await convertToCurrencyDecimals(weth.address, "8") - ); - //50 * 0.325 + 15 * 0.2 - 8=11.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "11.25") - ); - }); - - it("TC-pool-ape-staking-31 test borrowApeAndStake: Insufficient liquidity of borrow ape (revert expected)", async () => { - const { - users: [user1], - bayc, - ape, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - - // reduce pool liquidity - await borrowAndValidate(ape, "13000", user1); - const amount1 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount = amount1.add(amount2); - - await ape - .connect(user1.signer) - ["mint(address,uint256)"](user1.address, amount); - - await expect( - pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: bayc.address, - borrowAsset: ape.address, - borrowAmount: amount1, - cashAmount: amount2, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ).to.be.revertedWith("ERC20: transfer amount exceeds balance"); - }); - - it("TC-pool-ape-staking-32 test borrowApeAndStake: success use 100% cash when hf < 1", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - usdt, - nMAYC, - weth, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(usdt, "1000", user2, true); - await borrowAndValidate(ape, "5000", user1); - await borrowAndValidate(usdt, "800", user1); - await mintAndValidate(ape, "7000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "7000"); - - await changePriceAndValidate(mayc, "20"); - await changePriceAndValidate(usdt, "0.0009"); - await changePriceAndValidate(ape, "0.005"); - await changeSApePriceAndValidate(sApeAddress, "0.005"); - - const healthFactor = (await pool.getUserAccountData(user1.address)) - .healthFactor; - - expect(healthFactor).to.be.lt(parseEther("1")); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] - ) - ); - const healthFactorAfter = (await pool.getUserAccountData(user1.address)) - .healthFactor; - - // health factor should improve greater than 1 - expect(healthFactorAfter).to.be.gt(parseEther("1")); - - // User 1 - totalStake should increased in Stake amount - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(2); - - const userAccount = await pool.getUserAccountData(user1.address); - - //20 + 7000*0.005 = 55 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(weth.address, "55") - ); - - //5000*0.005 + 800 * 0.0009 = 25.72 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(weth.address, "25.72") - ); - - //availableBorrowsInBaseCurrency < totalDebtInBaseCurrency = 0 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(weth.address, "0") - ); - }); - - it("TC-pool-ape-staking-33 test safeTransferFrom BAKC: original owner withdraws all", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - const userBalance = await ape.balanceOf(user1.address); - const user3Balance = await ape.balanceOf(user3.address); - - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - "0" - ) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(1); - - const bakcBalanceUser3 = await bakc.balanceOf(user3.address); - expect(bakcBalanceUser3).equal(1); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ); - - // User 1 - totalStake should have decreased in BAKC amount - const totalStakeAfter = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStakeAfter).equal(totalStake.sub(amount2)); - - // User 1 - totalStake should have increased in BAKC amount - const pSApeBalanceAfter = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalanceAfter).equal(pSApeBalance.sub(amount2)); - - // User 1 - Ape Balance should have increased in BAKC amount - const userBalanceAfter = await ape.balanceOf(user1.address); - expect(userBalanceAfter).equal(userBalance.add(amount2)); - - // User 3 - Ape Balance should remain the same - const user3BalanceAfter = await ape.balanceOf(user3.address); - expect(user3BalanceAfter).equal(user3Balance); - }); - - it("TC-pool-ape-staking-34 test safeTransferFrom BAKC: original owner withdraws part ape (revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - "0" - ) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(1); - - const bakcBalanceUser3 = await bakc.balanceOf(user3.address); - expect(bakcBalanceUser3).equal(1); - - // Only withdraw all - await expect( - pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount1, isUncommit: false}, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_BAKC_OWNER); - }); - - it("TC-pool-ape-staking-35 test safeTransferFrom BAKC: original owner claim bakc reward (revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - "0" - ) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(1); - - const bakcBalanceUser3 = await bakc.balanceOf(user3.address); - expect(bakcBalanceUser3).equal(1); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - await expect( - pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 0}]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_BAKC_OWNER); - }); - - it("TC-pool-ape-staking-36 test safeTransferFrom BAKC: new owner withdraw all (revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - "0" - ) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - expect(bakcBalance).equal(1); - - const bakcBalanceUser3 = await bakc.balanceOf(user3.address); - expect(bakcBalanceUser3).equal(1); - - // New owner - await expect( - pool - .connect(user3.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-37 test safeTransferFrom: transfer fails when hf < 1 (revert expected)", async () => { - const { - users: [user1, user2, user3], - ape, - mayc, - nMAYC, - pool, - usdt, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(usdt, "1000", user2, true); - await borrowAndValidate(ape, "5000", user1); - await borrowAndValidate(usdt, "800", user1); - await mintAndValidate(ape, "7000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "100"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount2, - cashAmount: amount1, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - await changePriceAndValidate(mayc, "0.001"); - await changePriceAndValidate(ape, "0.1"); - await changeSApePriceAndValidate(sApeAddress, "0.002"); - - await expect( - nMAYC - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - 0, - {gasLimit: 5000000} - ) - ).to.be.revertedWith( - ProtocolErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD - ); - }); - - it("TC-pool-ape-staking-38 test withdrawBAKC success when hf > 1 after withdrawBAKC", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - bakc, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - const healthFactor = (await pool.getUserAccountData(user1.address)) - .healthFactor; - - expect(healthFactor.gt(parseEther("1"))).to.be.true; - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in Stake amount - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - const withdrawAmount = await convertToCurrencyDecimals(ape.address, "8000"); - await waitForTx( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: withdrawAmount, - isUncommit: true, - }, - ]) - ); - - const bakcBalance = await bakc.balanceOf(user1.address); - // User 1 - bakc balanace should increased 2 - expect(bakcBalance).equal(2); - // User1 - ape balance should increased amount2 - expect(await ape.balanceOf(user1.address)).eq(amount2); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User1 - total stake should increased amount1 - expect(totalStake).equal(amount1); - }); - - it("TC-pool-ape-staking-39 test withdrawApeCoin success when hf > 1 after withdrawApeCoin", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - // supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // borrow and stake 15000 - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - const healthFactor = (await pool.getUserAccountData(user1.address)) - .healthFactor; - - expect(healthFactor.gt(parseEther("1"))).to.be.true; - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in Stake amount - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) - ); - const apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).equal(amount1); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in amount2 - expect(totalStake).equal(amount2); - }); - - it("TC-pool-ape-staking-40 test withdrawBAKC fails when sender is not NFT owner (revert expected)", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in Stake amount - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await expect( - pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 1, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-41 test withdrawBAKC fails when amount != total staking, the sender is the NFT owner, but the sender is not the BAKC owner(revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - bakc, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User 1 - totalStake should increased in Stake amount - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await waitForTx( - await bakc - .connect(user1.signer) - .transferFrom(user1.address, user3.address, 0) - ); - // User 3 - The NFT owner with bakc id 0 should be changed to user3 - expect(await bakc.balanceOf(user3.address)).equal(1); - expect(await bakc.ownerOf(0)).eq(user3.address); - - const withdrawAmount = await convertToCurrencyDecimals(ape.address, "6000"); - - await expect( - pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: withdrawAmount, - isUncommit: false, - }, - ]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_BAKC_OWNER); - }); - - it("TC-pool-ape-staking-42 test withdrawBAKC success when withdraw amount == bakc staking amount, it will automatically claim and transfer the reward to the BACK owner", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - apeCoinStaking, - bakc, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - // User 1 - totalStake should increased in Stake amount - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await advanceTimeAndBlock(parseInt("86400")); - - // bayc rewards - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - // bakc rewards - const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( - 3, - nMAYC.address, - "0" - ); - - await waitForTx( - await bakc - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - 0 - ) - ); - // User 3 - The NFT owner with bakc id 0 should be changed to user3 - expect(await bakc.ownerOf(0)).eq(user3.address); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ); - - // User1 - ape balance should increased amount2 - expect(await ape.balanceOf(user1.address)).eq(amount2); - // User 3 - ape balance should increased pendingRewardsPool3 - expect(await ape.balanceOf(user3.address)).eq(pendingRewardsPool3); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User1 - total stake should increased amount1 + pendingRewardsPool2 - expect(totalStake).equal(amount1.add(pendingRewardsPool2)); - }); - - it("TC-pool-ape-staking-43 test withdrawBAKC success when withdraw amount == bakc staking amount, and the sender is not the BAKC owner, it will automatically claim and transfer the reward to the BACK owner", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - apeCoinStaking, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - // User 1 - totalStake should increased in Stake amount - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await advanceTimeAndBlock(parseInt("86400")); - - // bayc rewards - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - // bakc rewards - const pendingRewardsPool3 = await apeCoinStaking.pendingRewards( - 3, - nMAYC.address, - "0" - ); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawBAKC(mayc.address, [ - {mainTokenId: 0, bakcTokenId: 0, amount: amount2, isUncommit: true}, - ]) - ); - - // User1 - ape balance should increased amount2 + pendingRewardsPool3 - expect(await ape.balanceOf(user1.address)).eq( - amount2.add(pendingRewardsPool3) - ); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - // User1 - total stake should increased amount1 + pendingRewardsPool2 - expect(totalStake).equal(amount1.add(pendingRewardsPool2)); - }); - - it("TC-pool-ape-staking-44 test withdrawApeCoin fails when the sender is not the NFT owner(revert expected)", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - // supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "1000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "1000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount}], - [] - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 1000*0.001 = 51 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "51") - ); - // User1 - debt amount should increased 0 - almostEqual(userAccount.totalDebtBase, 0); - // User1 - available borrow should increased amount * baseLTVasCollateral = 50 * 0.325 + 1 * 0.2=16.45 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "16.45") - ); - // User 1 - totalStake should increased in Stake amount - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - await waitForTx( - await nMAYC - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user3.address, - 0 - ) - ); - expect(await nMAYC.balanceOf(user3.address)).eq(1); - expect(await nMAYC.ownerOf(0)).eq(user3.address); - - await expect( - pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 1, amount: amount}]) - ).to.be.revertedWith(ProtocolErrors.NOT_THE_OWNER); - }); - - it("TC-pool-ape-staking-45 test withdrawApeCoin success when withdraw amount == NFT staking amount, it will automatically claim and transfer the reward to the user account", async () => { - const { - users: [user1], - ape, - mayc, - pool, - nMAYC, - apeCoinStaking, - } = await loadFixture(fixture); - // 1. supply 1 mayc - await supplyAndValidate(mayc, "1", user1, true); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - // 2. stake one bakc and borrow 15000 ape - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const userAccount = await pool.getUserAccountData(user1.address); - - // User1 - collateral amount should increased mayc amount * price + ape amount * ape price = 50 + 15000*0.001 = 65 - expect(userAccount.totalCollateralBase).equal( - await convertToCurrencyDecimals(ape.address, "65") - ); - // User1 - debt amount should increased ape amount * ape price = 15000*0.001 = 15 - almostEqual( - userAccount.totalDebtBase, - await convertToCurrencyDecimals(ape.address, "15") - ); - // User1 - available borrow should increased amount * baseLTVasCollateral - debt amount = 50 * 0.325 + 15 * 0.2 - 15=4.25 - almostEqual( - userAccount.availableBorrowsBase, - await convertToCurrencyDecimals(ape.address, "4.25") - ); - // User 1 - totalStake should increased in Stake amount - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - // User 1 - pSape should increased in Stake amount - const pSApeBalance = await pSApeCoin.balanceOf(user1.address); - expect(pSApeBalance).equal(amount); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - const pendingRewardsPool2 = await apeCoinStaking.pendingRewards( - 2, - nMAYC.address, - "0" - ); - await waitForTx( - await pool - .connect(user1.signer) - .withdrawApeCoin(mayc.address, [{tokenId: 0, amount: amount1}]) - ); - - // User1 - ape balance should increased amount1 + pendingRewardsPool2 - expect(await ape.balanceOf(user1.address)).to.be.eq( - amount1.add(pendingRewardsPool2) - ); - // User1 - total stake should increased amount2 - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount2); - }); - - it("TC-pool-ape-staking-46 test borrowApeAndStake will turn on the sAPE collateral", async () => { - const { - users: [user1, , user3], - ape, - mayc, - pool, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user3, true); - // transfer mayc#0 to user1 who hasn't collateralized sAPE - await waitForTx( - await nMAYC - .connect(user3.signer) - ["safeTransferFrom(address,address,uint256)"]( - user3.address, - user1.address, - "0" - ) - ); - await mintAndValidate(ape, "15000", user1); - - const amount1 = await convertToCurrencyDecimals(ape.address, "7000"); - const amount2 = await convertToCurrencyDecimals(ape.address, "8000"); - const amount = await convertToCurrencyDecimals(ape.address, "15000"); - const sApeReserveData = await pool.getReserveData(sApeAddress); - const configDataBefore = (await pool.getUserConfiguration(user1.address)) - .data; - expect(isUsingAsCollateral(configDataBefore, sApeReserveData.id)).false; - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount1}], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount2}] - ) - ); - - const configDataAfter = (await pool.getUserConfiguration(user1.address)) - .data; - expect(isUsingAsCollateral(configDataAfter, sApeReserveData.id)).true; - }); -}); diff --git a/test/_pool_borrow_and_stake.spec.ts b/test/_pool_borrow_and_stake.spec.ts deleted file mode 100644 index 1e4fc8107..000000000 --- a/test/_pool_borrow_and_stake.spec.ts +++ /dev/null @@ -1,304 +0,0 @@ -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; -import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; -import {getAutoCompoundApe, getPToken} from "../helpers/contracts-getters"; -import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; -import {PToken, AutoCompoundApe} from "../types"; -import {TestEnv} from "./helpers/make-suite"; -import {testEnvFixture} from "./helpers/setup-env"; - -import { - changePriceAndValidate, - changeSApePriceAndValidate, - mintAndValidate, - supplyAndValidate, -} from "./helpers/validated-steps"; -import {parseEther} from "ethers/lib/utils"; - -describe("APE Coin Staking Test", () => { - let testEnv: TestEnv; - let cApe: AutoCompoundApe; - let pcApeCoin: PToken; - const sApeAddress = ONE_ADDRESS; - const InitialNTokenApeBalance = parseEther("100"); - - const fixture = async () => { - testEnv = await loadFixture(testEnvFixture); - const { - ape, - mayc, - bayc, - users, - bakc, - protocolDataProvider, - pool, - apeCoinStaking, - nMAYC, - nBAYC, - } = testEnv; - const user1 = users[0]; - const user4 = users[5]; - - cApe = await getAutoCompoundApe(); - const {xTokenAddress: pcApeCoinAddress} = - await protocolDataProvider.getReserveTokensAddresses(cApe.address); - pcApeCoin = await getPToken(pcApeCoinAddress); - - await changePriceAndValidate(ape, "0.001"); - await changePriceAndValidate(cApe, "0.001"); - await changeSApePriceAndValidate(sApeAddress, "0.001"); - - await changePriceAndValidate(mayc, "50"); - await changePriceAndValidate(bayc, "50"); - - await waitForTx( - await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await bakc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - - // send extra tokens to the apestaking contract for rewards - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"]( - apeCoinStaking.address, - parseEther("100000000000") - ) - ); - - // send extra tokens to the nToken contract for testing ape balance check - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](nMAYC.address, InitialNTokenApeBalance) - ); - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"](nBAYC.address, InitialNTokenApeBalance) - ); - - await mintAndValidate(ape, "1", user4); - await waitForTx( - await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - // user4 deposit MINIMUM_LIQUIDITY to make test case easy - const MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); - await waitForTx( - await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) - ); - - //prepare user1 asset - // await supplyAndValidate(bakc, "10", user1, true); - await mintAndValidate(ape, "2000000", user1); - await waitForTx( - await ape.connect(user1.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user1.signer) - .deposit(user1.address, parseEther("2000000")) - ); - await waitForTx( - await cApe.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await pool - .connect(user1.signer) - .supply(cApe.address, parseEther("2000000"), user1.address, 0) - ); - - return testEnv; - }; - - it("only borrow with 1 tokenId", async () => { - const { - users: [user1], - bayc, - pool, - } = await loadFixture(fixture); - - await advanceTimeAndBlock(4000); - - await supplyAndValidate(bayc, "1", user1, true); - const amount = parseEther("7000"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStakeV2( - { - nftAsset: bayc.address, - borrowAsset: cApe.address, - borrowAmount: amount, - cashAsset: pcApeCoin.address, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount}], - [] - ) - ); - }); - - it("only borrow with 5 tokenId", async () => { - const { - users: [user1], - bayc, - pool, - } = await loadFixture(fixture); - - await advanceTimeAndBlock(4000); - - await supplyAndValidate(bayc, "5", user1, true); - const amount = parseEther("7000"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStakeV2( - { - nftAsset: bayc.address, - borrowAsset: cApe.address, - borrowAmount: amount.mul(5), - cashAsset: pcApeCoin.address, - cashAmount: 0, - }, - [ - {tokenId: 0, amount: amount}, - {tokenId: 1, amount: amount}, - {tokenId: 2, amount: amount}, - {tokenId: 3, amount: amount}, - {tokenId: 4, amount: amount}, - ], - [] - ) - ); - }); - - it("only borrow with 10 tokenId", async () => { - const { - users: [user1], - bayc, - pool, - } = await loadFixture(fixture); - - await advanceTimeAndBlock(4000); - - await supplyAndValidate(bayc, "10", user1, true); - const amount = parseEther("7000"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStakeV2( - { - nftAsset: bayc.address, - borrowAsset: cApe.address, - borrowAmount: amount.mul(10), - cashAsset: pcApeCoin.address, - cashAmount: 0, - }, - [ - {tokenId: 0, amount: amount}, - {tokenId: 1, amount: amount}, - {tokenId: 2, amount: amount}, - {tokenId: 3, amount: amount}, - {tokenId: 4, amount: amount}, - {tokenId: 5, amount: amount}, - {tokenId: 6, amount: amount}, - {tokenId: 7, amount: amount}, - {tokenId: 8, amount: amount}, - {tokenId: 9, amount: amount}, - ], - [] - ) - ); - }); - - it("only use pcApe cash with 1 tokenId", async () => { - const { - users: [user1], - bayc, - pool, - } = await loadFixture(fixture); - - await advanceTimeAndBlock(4000); - - await supplyAndValidate(bayc, "1", user1, true); - const amount = parseEther("7000"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStakeV2( - { - nftAsset: bayc.address, - borrowAsset: cApe.address, - borrowAmount: 0, - cashAsset: pcApeCoin.address, - cashAmount: amount, - }, - [{tokenId: 0, amount: amount}], - [] - ) - ); - }); - - it("only use pcApe cash with 5 tokenId", async () => { - const { - users: [user1], - bayc, - pool, - } = await loadFixture(fixture); - - await advanceTimeAndBlock(4000); - - await supplyAndValidate(bayc, "5", user1, true); - const amount = parseEther("7000"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStakeV2( - { - nftAsset: bayc.address, - borrowAsset: cApe.address, - borrowAmount: 0, - cashAsset: pcApeCoin.address, - cashAmount: amount.mul(5), - }, - [ - {tokenId: 0, amount: amount}, - {tokenId: 1, amount: amount}, - {tokenId: 2, amount: amount}, - {tokenId: 3, amount: amount}, - {tokenId: 4, amount: amount}, - ], - [] - ) - ); - }); - - it("only use pcApe cash with 10 tokenId", async () => { - const { - users: [user1], - bayc, - pool, - } = await loadFixture(fixture); - - await advanceTimeAndBlock(4000); - - await supplyAndValidate(bayc, "10", user1, true); - const amount = parseEther("7000"); - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStakeV2( - { - nftAsset: bayc.address, - borrowAsset: cApe.address, - borrowAmount: 0, - cashAsset: pcApeCoin.address, - cashAmount: amount.mul(10), - }, - [ - {tokenId: 0, amount: amount}, - {tokenId: 1, amount: amount}, - {tokenId: 2, amount: amount}, - {tokenId: 3, amount: amount}, - {tokenId: 4, amount: amount}, - {tokenId: 5, amount: amount}, - {tokenId: 6, amount: amount}, - {tokenId: 7, amount: amount}, - {tokenId: 8, amount: amount}, - {tokenId: 9, amount: amount}, - ], - [] - ) - ); - }); -}); diff --git a/test/_pool_position_mover.spec.ts b/test/_pool_position_mover.spec.ts deleted file mode 100644 index 43f1f1e1e..000000000 --- a/test/_pool_position_mover.spec.ts +++ /dev/null @@ -1,217 +0,0 @@ -import {expect} from "chai"; -import {TestEnv} from "./helpers/make-suite"; -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; -import {testEnvFixture} from "./helpers/setup-env"; -import { - getAggregator, - getMockBendDaoLendPool, -} from "../helpers/contracts-getters"; -import {waitForTx} from "../helpers/misc-utils"; -import { - changePriceAndValidate, - mintAndValidate, - supplyAndValidate, -} from "./helpers/validated-steps"; -import {deployMintableERC721} from "../helpers/contracts-deployments"; - -describe("Pool: rescue tokens", () => { - let testEnv: TestEnv; - let bendDaoLendPool; - - before(async () => { - testEnv = await loadFixture(testEnvFixture); - const { - users: [user1, user2], - bayc, - pool, - } = testEnv; - - bendDaoLendPool = await getMockBendDaoLendPool(); - - await mintAndValidate(bayc, "5", user1); - await bayc - .connect(user1.signer) - .transferFrom(user1.address, user2.address, 2); - await waitForTx( - await bayc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(bendDaoLendPool.address, true) - ); - await waitForTx( - await bayc.connect(user2.signer).setApprovalForAll(pool.address, true) - ); - - await waitForTx( - await bayc - .connect(user2.signer) - .setApprovalForAll(bendDaoLendPool.address, true) - ); - }); - - it("moving position should fail if there's no enough WETH in the protocol", async () => { - const { - users: [user1], - bayc, - pool, - } = testEnv; - await bendDaoLendPool.setLoan( - 1, - bayc.address, - 1, - user1.address, - "200000", - 2 - ); - - await expect( - pool.connect(user1.signer).movePositionFromBendDAO([1], user1.address) - ).to.be.reverted; - }); - - it("moving position should succeed for an active loan", async () => { - const { - users: [user1], - pool, - weth, - variableDebtWeth, - nBAYC, - } = testEnv; - await supplyAndValidate(weth, "20000000000", user1, true); - - await expect( - await pool - .connect(user1.signer) - .movePositionFromBendDAO([1], user1.address) - ); - - await expect(await variableDebtWeth.balanceOf(user1.address)).to.be.eq( - "200000" - ); - await expect(await nBAYC.balanceOf(user1.address)).to.be.eq(1); - }); - - it("moving position should fail for a non-active loan", async () => { - const { - users: [user1], - pool, - } = testEnv; - - await expect( - pool.connect(user1.signer).movePositionFromBendDAO([1], user1.address) - ).to.be.revertedWith("Loan not active"); - }); - - it("moving position should fail when user HF < 1 after moving the position", async () => { - const { - users: [, user2], - bayc, - pool, - } = testEnv; - - await bendDaoLendPool.setLoan( - 2, - bayc.address, - 2, - user2.address, - "200000", - 2 - ); - - const agg = await getAggregator(undefined, await bayc.symbol()); - await agg.updateLatestAnswer("100000"); - - await expect( - pool.connect(user2.signer).movePositionFromBendDAO([2], user2.address) - ).to.be.reverted; - - await changePriceAndValidate(bayc, "50"); - }); - - it("moving position should fail when sender is not the owner of the position", async () => { - const { - users: [user1, user2], - bayc, - pool, - } = testEnv; - - await bendDaoLendPool.setLoan( - 3, - bayc.address, - 0, - user1.address, - "200000", - 2 - ); - - await expect( - pool.connect(user2.signer).movePositionFromBendDAO([3], user2.address) - ).to.be.reverted; - }); - - it("moving position should fail if the asset is not supported", async () => { - const { - users: [user1], - pool, - } = testEnv; - - const randomAsset = await deployMintableERC721(["test", "test", "0"]); - - await randomAsset["mint(address)"](user1.address); - await waitForTx( - await randomAsset - .connect(user1.signer) - .setApprovalForAll(pool.address, true) - ); - - await waitForTx( - await randomAsset - .connect(user1.signer) - .setApprovalForAll(bendDaoLendPool.address, true) - ); - await bendDaoLendPool.setLoan( - 4, - randomAsset.address, - 0, - user1.address, - "200000", - 2 - ); - - await expect( - pool.connect(user1.signer).movePositionFromBendDAO([4], user1.address) - ).to.be.reverted; - }); - - it("moving multiple positions should succeed for active loans", async () => { - const { - users: [user1], - pool, - bayc, - variableDebtWeth, - nBAYC, - } = testEnv; - await bendDaoLendPool.setLoan( - 4, - bayc.address, - 4, - user1.address, - "200000", - 2 - ); - - await expect( - await pool - .connect(user1.signer) - .movePositionFromBendDAO([3, 4], user1.address) - ); - - await expect(await variableDebtWeth.balanceOf(user1.address)).to.be.eq( - "600000" - ); - await expect(await nBAYC.balanceOf(user1.address)).to.be.eq(3); - }); -}); diff --git a/test/_sape_pool_operation.spec.ts b/test/_sape_pool_operation.spec.ts deleted file mode 100644 index 8e8a89ec6..000000000 --- a/test/_sape_pool_operation.spec.ts +++ /dev/null @@ -1,194 +0,0 @@ -import {expect} from "chai"; -import {TestEnv} from "./helpers/make-suite"; -import {waitForTx} from "../helpers/misc-utils"; -import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; - -import {ProtocolErrors} from "../helpers/types"; -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; -import {testEnvFixture} from "./helpers/setup-env"; -import { - changePriceAndValidate, - mintAndValidate, - supplyAndValidate, -} from "./helpers/validated-steps"; -import {convertToCurrencyDecimals} from "../helpers/contracts-helpers"; -import {PTokenSApe} from "../types"; -import {getPTokenSApe} from "../helpers/contracts-getters"; - -describe("SApe Pool Operation Test", () => { - let testEnv: TestEnv; - const sApeAddress = ONE_ADDRESS; - let pSApeCoin: PTokenSApe; - - const fixture = async () => { - testEnv = await loadFixture(testEnvFixture); - - const { - ape, - users: [user1, depositor], - protocolDataProvider, - pool, - } = testEnv; - - const {xTokenAddress: pSApeCoinAddress} = - await protocolDataProvider.getReserveTokensAddresses(sApeAddress); - pSApeCoin = await getPTokenSApe(pSApeCoinAddress); - - await waitForTx( - await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - - await supplyAndValidate(ape, "20000", depositor, true); - - return testEnv; - }; - - it("supply sApe is not allowed", async () => { - const { - users: [user1], - pool, - } = await loadFixture(fixture); - - await expect( - pool.connect(user1.signer).supply(sApeAddress, 111, user1.address, 0, { - gasLimit: 12_450_000, - }) - ).to.be.revertedWith(ProtocolErrors.SAPE_NOT_ALLOWED); - }); - - it("withdraw sApe 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}], - [] - ) - ); - - const balance = await pSApeCoin.balanceOf(user1.address); - - // HF = (51 * 0.7 + 5000 * 0.0036906841286 * 0.7) / (5000 * 0.0036906841286 ) = 2.6346006732655392383 - - await expect( - pool.connect(user1.signer).withdraw(sApeAddress, balance, user1.address, { - gasLimit: 12_450_000, - }) - ).to.be.revertedWith(ProtocolErrors.SAPE_NOT_ALLOWED); - }); - - it("borrow sApe is not allowed", async () => { - const { - users: [user1], - pool, - } = await loadFixture(fixture); - - await expect( - pool.connect(user1.signer).borrow(sApeAddress, 111, 0, user1.address, { - gasLimit: 12_450_000, - }) - ).to.be.revertedWith(ProtocolErrors.BORROWING_NOT_ENABLED); - }); - - it("liquidate sApe is not allowed", async () => { - const { - users: [user1, liquidator], - ape, - mayc, - pool, - weth, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "5000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount}], - [] - ) - ); - - await supplyAndValidate(weth, "100", liquidator, true, "200000"); - - // BorrowLimit: (51 * 0.325 + 5000 * 0.0036906841286 * 0.2 - 5000 * 0.0036906841286) = 1.8122634856 - const borrowAmount = await convertToCurrencyDecimals(weth.address, "1"); - expect( - await pool - .connect(user1.signer) - .borrow(weth.address, borrowAmount, 0, user1.address) - ); - - // drop HF and ERC-721_HF below 1 - await changePriceAndValidate(mayc, "5"); - - await expect( - pool - .connect(liquidator.signer) - .liquidateERC20( - weth.address, - sApeAddress, - user1.address, - amount, - false, - {gasLimit: 5000000} - ) - ).to.be.revertedWith(ProtocolErrors.SAPE_NOT_ALLOWED); - }); - - it("set sApe not as collateral is not allowed", async () => { - const { - users: [user1], - ape, - mayc, - pool, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "5000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [{tokenId: 0, amount: amount}], - [] - ) - ); - - await expect( - pool - .connect(user1.signer) - .setUserUseERC20AsCollateral(sApeAddress, false, { - gasLimit: 12_450_000, - }) - ).to.be.revertedWith(ProtocolErrors.SAPE_NOT_ALLOWED); - }); -}); diff --git a/test/auto_compound_ape.spec.ts b/test/auto_compound_ape.spec.ts index 36cbd766f..0cc047dbf 100644 --- a/test/auto_compound_ape.spec.ts +++ b/test/auto_compound_ape.spec.ts @@ -1,33 +1,20 @@ import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; import {expect} from "chai"; -import {AutoCompoundApe, PToken, PTokenSApe, VariableDebtToken} from "../types"; +import {AutoCompoundApe, PToken, VariableDebtToken} from "../types"; import {TestEnv} from "./helpers/make-suite"; import {testEnvFixture} from "./helpers/setup-env"; import {mintAndValidate} from "./helpers/validated-steps"; import {parseEther, solidityKeccak256} from "ethers/lib/utils"; -import { - almostEqual, - approveTo, - createNewPool, - fund, - mintNewPosition, -} from "./helpers/uniswapv3-helper"; +import {almostEqual} from "./helpers/uniswapv3-helper"; import { getAutoCompoundApe, getPToken, - getPTokenSApe, getVariableDebtToken, } from "../helpers/contracts-getters"; -import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; -import { - advanceTimeAndBlock, - getParaSpaceConfig, - waitForTx, -} from "../helpers/misc-utils"; +import {MAX_UINT_AMOUNT} from "../helpers/constants"; +import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; import {deployMockedDelegateRegistry} from "../helpers/contracts-deployments"; import {ETHERSCAN_VERIFICATION} from "../helpers/hardhat-constants"; -import {convertToCurrencyDecimals} from "../helpers/contracts-helpers"; -import {encodeSqrtRatioX96} from "@uniswap/v3-sdk"; import {ProtocolErrors} from "../helpers/types"; describe("Auto Compound Ape Test", () => { @@ -35,8 +22,6 @@ describe("Auto Compound Ape Test", () => { let cApe: AutoCompoundApe; let pCApe: PToken; let variableDebtCAPE: VariableDebtToken; - let pSApeCoin: PTokenSApe; - const sApeAddress = ONE_ADDRESS; let user1Amount; let user2Amount; let user3Amount; @@ -48,14 +33,11 @@ describe("Auto Compound Ape Test", () => { testEnv = await loadFixture(testEnvFixture); const { ape, - usdc, - weth, - users: [user1, user2, , , user3, user4, user5], + users: [user1, user2, , , user3, user4], apeCoinStaking, pool, protocolDataProvider, poolAdmin, - nftPositionManager, } = testEnv; cApe = await getAutoCompoundApe(); @@ -67,9 +49,6 @@ describe("Auto Compound Ape Test", () => { } = await protocolDataProvider.getReserveTokensAddresses(cApe.address); pCApe = await getPToken(pCApeAddress); variableDebtCAPE = await getVariableDebtToken(variableDebtPsApeAddress); - const {xTokenAddress: pSApeCoinAddress} = - await protocolDataProvider.getReserveTokensAddresses(sApeAddress); - pSApeCoin = await getPTokenSApe(pSApeCoinAddress); await mintAndValidate(ape, "1000", user1); await mintAndValidate(ape, "2000", user2); @@ -129,98 +108,6 @@ describe("Auto Compound Ape Test", () => { await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) ); - //////////////////////////////////////////////////////////////////////////////// - // Uniswap APE/WETH/USDC - //////////////////////////////////////////////////////////////////////////////// - const userApeAmount = await convertToCurrencyDecimals( - ape.address, - "200000" - ); - const userUsdcAmount = await convertToCurrencyDecimals( - usdc.address, - "800000" - ); - const userWethAmount = await convertToCurrencyDecimals( - weth.address, - "732.76177" - ); - await fund({token: ape, user: user5, amount: userApeAmount}); - await fund({token: weth, user: user5, amount: userWethAmount.mul(2)}); - await fund({token: usdc, user: user5, amount: userUsdcAmount}); - const nft = nftPositionManager.connect(user5.signer); - await approveTo({ - target: nftPositionManager.address, - token: ape, - user: user5, - }); - await approveTo({ - target: nftPositionManager.address, - token: usdc, - user: user5, - }); - await approveTo({ - target: nftPositionManager.address, - token: weth, - user: user5, - }); - const apeWethFee = 3000; - const usdcWethFee = 500; - const apeWethTickSpacing = apeWethFee / 50; - const usdcWethTickSpacing = usdcWethFee / 50; - const apeWethInitialPrice = encodeSqrtRatioX96(1091760000, 4000000); - const apeWethLowerPrice = encodeSqrtRatioX96(109176000, 4000000); - const apeWethUpperPrice = encodeSqrtRatioX96(10917600000, 4000000); - const usdcWethInitialPrice = encodeSqrtRatioX96( - 1000000000000000000, - 1091760000 - ); - const usdcWethLowerPrice = encodeSqrtRatioX96( - 100000000000000000, - 1091760000 - ); - const usdcWethUpperPrice = encodeSqrtRatioX96( - 10000000000000000000, - 1091760000 - ); - await createNewPool({ - positionManager: nft, - token0: weth, - token1: ape, - fee: apeWethFee, - initialSqrtPrice: apeWethInitialPrice.toString(), - }); - await createNewPool({ - positionManager: nft, - token0: usdc, - token1: weth, - fee: usdcWethFee, - initialSqrtPrice: usdcWethInitialPrice.toString(), - }); - await mintNewPosition({ - nft: nft, - token0: weth, - token1: ape, - fee: apeWethFee, - user: user5, - tickSpacing: apeWethTickSpacing, - lowerPrice: apeWethLowerPrice, - upperPrice: apeWethUpperPrice, - token0Amount: userWethAmount, - token1Amount: userApeAmount, - }); - await mintNewPosition({ - nft: nft, - token0: usdc, - token1: weth, - fee: usdcWethFee, - user: user5, - tickSpacing: usdcWethTickSpacing, - lowerPrice: usdcWethLowerPrice, - upperPrice: usdcWethUpperPrice, - token0Amount: userUsdcAmount, - token1Amount: userWethAmount, - }); - return testEnv; }; diff --git a/test/helper_contract.spec.ts b/test/helper_contract.spec.ts deleted file mode 100644 index 0f50561fa..000000000 --- a/test/helper_contract.spec.ts +++ /dev/null @@ -1,132 +0,0 @@ -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; -import {AutoCompoundApe, HelperContract, PToken} from "../types"; -import {TestEnv} from "./helpers/make-suite"; -import {testEnvFixture} from "./helpers/setup-env"; -import {mintAndValidate} from "./helpers/validated-steps"; -import { - getAutoCompoundApe, - getHelperContract, - getPToken, -} from "../helpers/contracts-getters"; -import {MAX_UINT_AMOUNT} from "../helpers/constants"; -import {parseEther} from "ethers/lib/utils"; -import {almostEqual} from "./helpers/uniswapv3-helper"; -import {waitForTx} from "../helpers/misc-utils"; -import {expect} from "chai"; - -describe("Helper contract Test", () => { - let testEnv: TestEnv; - let helperContract: HelperContract; - let cApe: AutoCompoundApe; - let pcApe: PToken; - let MINIMUM_LIQUIDITY; - - const fixture = async () => { - testEnv = await loadFixture(testEnvFixture); - const {ape, users, apeCoinStaking, protocolDataProvider} = testEnv; - - const user1 = users[0]; - const user4 = users[5]; - - helperContract = await getHelperContract(); - - cApe = await getAutoCompoundApe(); - MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); - const {xTokenAddress: pCApeAddress} = - await protocolDataProvider.getReserveTokensAddresses(cApe.address); - pcApe = await getPToken(pCApeAddress); - - // send extra tokens to the apestaking contract for rewards - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"]( - apeCoinStaking.address, - parseEther("100000000000") - ) - ); - - // user4 deposit MINIMUM_LIQUIDITY to make test case easy - await mintAndValidate(ape, "1", user4); - await waitForTx( - await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) - ); - - return testEnv; - }; - - it("test convertApeCoinToPCApe and convertPCApeToApeCoin", async () => { - const { - users: [user1], - ape, - } = await loadFixture(fixture); - - await mintAndValidate(ape, "10000", user1); - await waitForTx( - await ape - .connect(user1.signer) - .approve(helperContract.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await helperContract - .connect(user1.signer) - .convertApeCoinToPCApe(parseEther("10000")) - ); - const pcApeBalance = await pcApe.balanceOf(user1.address); - almostEqual(pcApeBalance, parseEther("10000")); - - await waitForTx( - await pcApe - .connect(user1.signer) - .approve(helperContract.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await helperContract - .connect(user1.signer) - .convertPCApeToApeCoin(pcApeBalance) - ); - const apeBalance = await ape.balanceOf(user1.address); - almostEqual(apeBalance, parseEther("10000")); - }); - - it("cApeMigration", async () => { - const { - users: [user1, user2], - ape, - } = await loadFixture(fixture); - - await mintAndValidate(ape, "10000", user1); - await waitForTx( - await ape.connect(user1.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - - await waitForTx( - await cApe - .connect(user1.signer) - .deposit(user1.address, parseEther("10000")) - ); - expect(await cApe.balanceOf(user1.address)).to.be.eq(parseEther("10000")); - - await waitForTx( - await cApe - .connect(user1.signer) - .approve(helperContract.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await helperContract - .connect(user1.signer) - .cApeMigration(parseEther("5000"), user2.address) - ); - expect(await cApe.balanceOf(user2.address)).to.be.eq(parseEther("5000")); - - await waitForTx( - await helperContract - .connect(user1.signer) - .cApeMigration("0", user2.address) - ); - expect(await cApe.balanceOf(user2.address)).to.be.eq(parseEther("10000")); - }); -}); diff --git a/test/p2p_pair_staking.spec.ts b/test/p2p_pair_staking.spec.ts deleted file mode 100644 index aee0665a1..000000000 --- a/test/p2p_pair_staking.spec.ts +++ /dev/null @@ -1,1299 +0,0 @@ -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; -import {expect} from "chai"; -import {AutoCompoundApe, P2PPairStaking} from "../types"; -import {TestEnv} from "./helpers/make-suite"; -import {testEnvFixture} from "./helpers/setup-env"; -import {mintAndValidate, supplyAndValidate} from "./helpers/validated-steps"; -import { - getAutoCompoundApe, - getP2PPairStaking, -} from "../helpers/contracts-getters"; -import {MAX_UINT_AMOUNT} from "../helpers/constants"; -import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; -import {getSignedListingOrder} from "./helpers/p2ppairstaking-helper"; -import {parseEther} from "ethers/lib/utils"; -import {almostEqual} from "./helpers/uniswapv3-helper"; - -describe("P2P Pair Staking Test", () => { - let testEnv: TestEnv; - let p2pPairStaking: P2PPairStaking; - let cApe: AutoCompoundApe; - let MINIMUM_LIQUIDITY; - - const fixture = async () => { - testEnv = await loadFixture(testEnvFixture); - const {ape, users, apeCoinStaking} = testEnv; - - const user1 = users[0]; - const user2 = users[1]; - const user4 = users[5]; - - p2pPairStaking = await getP2PPairStaking(); - - cApe = await getAutoCompoundApe(); - MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); - - // send extra tokens to the apestaking contract for rewards - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"]( - apeCoinStaking.address, - parseEther("100000000000") - ) - ); - - // user4 deposit MINIMUM_LIQUIDITY to make test case easy - await mintAndValidate(ape, "1", user4); - await waitForTx( - await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) - ); - - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) - ); - - return testEnv; - }; - - it("test BAYC pair with ApeCoin Staking", async () => { - const { - users: [user1, user2], - ape, - bayc, - nBAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); - - 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, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - cApe, - 0, - 8000, - user2 - ); - - 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(user1.signer) - .claimForMatchedOrderAndCompound([orderHash]) - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2880") - ); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) - ); - await waitForTx( - await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) - ); - - almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); - almostEqual(await cApe.balanceOf(user2.address), parseEther("2880")); - await waitForTx( - await cApe - .connect(user2.signer) - .transfer(user1.address, await cApe.balanceOf(user2.address)) - ); - - await advanceTimeAndBlock(parseInt("3600")); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) - ); - - expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); - almostEqual(await cApe.balanceOf(user2.address), apeAmount); - almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2880") - ); - }); - - it("test MAYC pair with ApeCoin Staking", async () => { - const { - users: [user1, user2], - ape, - mayc, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await mayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(1); - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 1, - mayc, - 0, - 2000, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 1, - cApe, - 0, - 8000, - user2 - ); - - 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(user1.signer) - .claimForMatchedOrderAndCompound([orderHash]) - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2880") - ); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) - ); - await waitForTx( - await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) - ); - - almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); - almostEqual(await cApe.balanceOf(user2.address), parseEther("2880")); - await waitForTx( - await cApe - .connect(user2.signer) - .transfer(user1.address, await cApe.balanceOf(user2.address)) - ); - - await advanceTimeAndBlock(parseInt("3600")); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) - ); - - expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); - almostEqual(await cApe.balanceOf(user2.address), apeAmount); - almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2880") - ); - }); - - it("test BAYC pair with BAKC and ApeCoin Staking", async () => { - const { - users: [user1, user2, user3], - ape, - bayc, - bakc, - nBAYC, - nBAKC, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bayc, - 0, - 2000, - user1 - ); - const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bakc, - 0, - 2000, - user3 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - cApe, - 0, - 6000, - user2 - ); - - const txReceipt = await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .matchBAKCPairStakingList( - user1SignedOrder, - user3SignedOrder, - user2SignedOrder - ) - ); - - const logLength = txReceipt.logs.length; - const orderHash = txReceipt.logs[logLength - 1].data; - - await advanceTimeAndBlock(parseInt("3600")); - - await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .claimForMatchedOrderAndCompound([orderHash]) - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2160") - ); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) - ); - await waitForTx( - await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) - ); - await waitForTx( - await p2pPairStaking.connect(user3.signer).claimCApeReward(user3.address) - ); - - almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); - almostEqual(await cApe.balanceOf(user3.address), parseEther("720")); - almostEqual(await cApe.balanceOf(user2.address), parseEther("2160")); - await waitForTx( - await cApe - .connect(user2.signer) - .transfer(user1.address, await cApe.balanceOf(user2.address)) - ); - - await advanceTimeAndBlock(parseInt("3600")); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) - ); - - expect(await bayc.balanceOf(nBAYC.address)).to.be.equal(1); - expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(1); - almostEqual(await cApe.balanceOf(user2.address), apeAmount); - - almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2160") - ); - }); - - it("test MAYC pair with BAKC and ApeCoin Staking", async () => { - const { - users: [user1, user2, user3], - ape, - mayc, - bakc, - nMAYC, - nBAKC, - } = await loadFixture(fixture); - - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user3, true); - 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) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - mayc, - 0, - 2000, - user1 - ); - const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bakc, - 0, - 2000, - user3 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - cApe, - 0, - 6000, - user2 - ); - - const txReceipt = await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .matchBAKCPairStakingList( - user1SignedOrder, - user3SignedOrder, - user2SignedOrder - ) - ); - - const logLength = txReceipt.logs.length; - const orderHash = txReceipt.logs[logLength - 1].data; - - await advanceTimeAndBlock(parseInt("3600")); - - await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .claimForMatchedOrderAndCompound([orderHash]) - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2160") - ); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).claimCApeReward(user1.address) - ); - await waitForTx( - await p2pPairStaking.connect(user2.signer).claimCApeReward(user2.address) - ); - await waitForTx( - await p2pPairStaking.connect(user3.signer).claimCApeReward(user3.address) - ); - - almostEqual(await cApe.balanceOf(user1.address), parseEther("720")); - almostEqual(await cApe.balanceOf(user3.address), parseEther("720")); - almostEqual(await cApe.balanceOf(user2.address), parseEther("2160")); - await waitForTx( - await cApe - .connect(user2.signer) - .transfer(user1.address, await cApe.balanceOf(user2.address)) - ); - - await advanceTimeAndBlock(parseInt("3600")); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash) - ); - - expect(await mayc.balanceOf(nMAYC.address)).to.be.equal(1); - expect(await bakc.balanceOf(nBAKC.address)).to.be.equal(1); - almostEqual(await cApe.balanceOf(user2.address), apeAmount); - almostEqual( - await p2pPairStaking.pendingCApeReward(user1.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), - parseEther("720") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2160") - ); - }); - - it("claimForMatchedOrderAndCompound for multi user work as expected", async () => { - const { - users: [user1, user2, user3], - ape, - bayc, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "10", user1, true); - await supplyAndValidate(bakc, "10", user3, true); - await mintAndValidate(ape, "10000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("1000000")) - ); - - const txArray: string[] = []; - for (let i = 0; i < 10; i++) { - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bayc, - i, - 2000, - user1 - ); - const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bakc, - i, - 2000, - user3 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - cApe, - 0, - 6000, - user2 - ); - - const txReceipt = await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .matchBAKCPairStakingList( - user1SignedOrder, - user3SignedOrder, - user2SignedOrder - ) - ); - const logLength = txReceipt.logs.length; - const orderHash = txReceipt.logs[logLength - 1].data; - - txArray.push(orderHash); - } - - for (let i = 0; i < 2; i++) { - await advanceTimeAndBlock(parseInt("3600")); - - await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .claimForMatchedOrderAndCompound(txArray) - ); - } - }); - - it("match failed when order was canceled 0", async () => { - const { - users: [user1, user2], - ape, - bayc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await ape - .connect(user2.signer) - .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - bayc, - 0, - 2000, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - ape, - 0, - 8000, - user2 - ); - - await waitForTx( - await p2pPairStaking.connect(user2.signer).cancelListing(user2SignedOrder) - ); - - await expect( - p2pPairStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith("order already cancelled"); - }); - - it("match failed when order was canceled 1", async () => { - const { - users: [user1, user2, user3], - ape, - bayc, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user2, true); - await mintAndValidate(ape, "1000000", user3); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user2.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await ape - .connect(user3.signer) - .approve(p2pPairStaking.address, MAX_UINT_AMOUNT) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bayc, - 0, - 2000, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bakc, - 0, - 2000, - user2 - ); - const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - ape, - 0, - 6000, - user3 - ); - - await waitForTx( - await p2pPairStaking.connect(user3.signer).cancelListing(user3SignedOrder) - ); - - await expect( - p2pPairStaking - .connect(user1.signer) - .matchBAKCPairStakingList( - user1SignedOrder, - user2SignedOrder, - user3SignedOrder - ) - ).to.be.revertedWith("order already cancelled"); - }); - - it("match failed when orders type match failed 0", async () => { - const { - users: [user1, user2], - ape, - bayc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - bayc, - 0, - 2000, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 1, - cApe, - 0, - 8000, - user2 - ); - - await expect( - p2pPairStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith("orders type match failed"); - }); - - it("match failed when orders type match failed 1", async () => { - const { - users: [user1, user2, user3], - ape, - bayc, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bayc, - 0, - 2000, - user1 - ); - const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 1, - bakc, - 0, - 2000, - user3 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - cApe, - 0, - 6000, - user2 - ); - - await expect( - p2pPairStaking - .connect(user1.signer) - .matchBAKCPairStakingList( - user1SignedOrder, - user3SignedOrder, - user2SignedOrder - ) - ).to.be.revertedWith("orders type match failed"); - }); - - it("match failed when share match failed 0", async () => { - const { - users: [user1, user2], - ape, - bayc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - bayc, - 0, - 2000, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - cApe, - 0, - 7000, - user2 - ); - - await expect( - p2pPairStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ).to.be.revertedWith("share match failed"); - }); - - it("match failed when share match failed 1", async () => { - const { - users: [user1, user2, user3], - ape, - bayc, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(2); - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bayc, - 0, - 2000, - user1 - ); - const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bakc, - 0, - 2000, - user3 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - cApe, - 0, - 7000, - user2 - ); - - await expect( - p2pPairStaking - .connect(user1.signer) - .matchBAKCPairStakingList( - user1SignedOrder, - user3SignedOrder, - user2SignedOrder - ) - ).to.be.revertedWith("share match failed"); - }); - - it("listing order can only be canceled by offerer", async () => { - const { - users: [user1, user2], - bayc, - } = await loadFixture(fixture); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - bayc, - 0, - 2000, - user1 - ); - - await expect( - p2pPairStaking.connect(user2.signer).cancelListing(user1SignedOrder) - ).to.be.revertedWith("not order offerer"); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).cancelListing(user1SignedOrder) - ); - }); - - it("matching operator work as expected", async () => { - const { - users: [user1, user2, user3, , user5], - gatewayAdmin, - bayc, - nBAYC, - ape, - } = await loadFixture(fixture); - - await expect( - p2pPairStaking.connect(user2.signer).setMatchingOperator(user5.address) - ).to.be.revertedWith("Ownable: caller is not the owner"); - - await waitForTx( - await p2pPairStaking - .connect(gatewayAdmin.signer) - .setMatchingOperator(user5.address) - ); - - await supplyAndValidate(bayc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - const apeAmount = await p2pPairStaking.getApeCoinStakingCap(0); - await waitForTx( - await cApe.connect(user2.signer).deposit(user2.address, apeAmount) - ); - - const user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - bayc, - 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], - gatewayAdmin, - bayc, - ape, - } = await loadFixture(fixture); - - await waitForTx( - await p2pPairStaking.connect(gatewayAdmin.signer).setCompoundFee(50) - ); - - await supplyAndValidate(bayc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - //deposit cApe for user3 to let exchangeRate > 1 - await waitForTx( - await ape.connect(user2.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("1000")) - ); - - await waitForTx( - await bayc - .connect(user3.signer) - .setApprovalForAll(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 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - cApe, - 0, - 8000, - user2 - ); - - 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(user1.signer) - .claimForMatchedOrderAndCompound([orderHash]) - ); - - almostEqual( - await p2pPairStaking.pendingCApeReward(p2pPairStaking.address), - parseEther("18") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user3.address), - parseEther("716.4") - ); - almostEqual( - await p2pPairStaking.pendingCApeReward(user2.address), - parseEther("2865.6") - ); - - await waitForTx( - await p2pPairStaking - .connect(gatewayAdmin.signer) - .claimCompoundFee(gatewayAdmin.address) - ); - - almostEqual(await cApe.balanceOf(gatewayAdmin.address), parseEther("18")); - }); - - it("check ape token can be matched twice", async () => { - const { - users: [user1, user2, user3], - bayc, - ape, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user3, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await bakc - .connect(user3.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("500000")) - ); - - //match bayc + ApeCoin - let user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - bayc, - 0, - 2000, - user1 - ); - let user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - cApe, - 0, - 8000, - user2 - ); - - let txReceipt = await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder, user2SignedOrder) - ); - let logLength = txReceipt.logs.length; - const orderHash0 = txReceipt.logs[logLength - 1].data; - - //match bayc + bakc + ApeCoin - user1SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bayc, - 0, - 2000, - user1 - ); - const user3SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - bakc, - 0, - 2000, - user3 - ); - user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 2, - cApe, - 0, - 6000, - user2 - ); - - txReceipt = await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .matchBAKCPairStakingList( - user1SignedOrder, - user3SignedOrder, - user2SignedOrder - ) - ); - logLength = txReceipt.logs.length; - const orderHash1 = txReceipt.logs[logLength - 1].data; - - await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) - ); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash1) - ); - }); - - it("check ape coin listing order can not be matched twice", async () => { - const { - users: [user1, user2], - bayc, - ape, - } = await loadFixture(fixture); - - await supplyAndValidate(bayc, "2", user1, true); - await mintAndValidate(ape, "1000000", user2); - - await waitForTx( - await bayc - .connect(user1.signer) - .setApprovalForAll(p2pPairStaking.address, true) - ); - await waitForTx( - await cApe - .connect(user2.signer) - .deposit(user2.address, parseEther("500000")) - ); - - const user1SignedOrder0 = await getSignedListingOrder( - p2pPairStaking, - 0, - bayc, - 0, - 2000, - user1 - ); - const user1SignedOrder1 = await getSignedListingOrder( - p2pPairStaking, - 0, - bayc, - 1, - 2000, - user1 - ); - const user2SignedOrder = await getSignedListingOrder( - p2pPairStaking, - 0, - cApe, - 0, - 8000, - user2 - ); - - const txReceipt = await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder0, user2SignedOrder) - ); - const logLength = txReceipt.logs.length; - const orderHash0 = txReceipt.logs[logLength - 1].data; - - await expect( - p2pPairStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder1, user2SignedOrder) - ).to.be.revertedWith("ape coin order already matched"); - - await waitForTx( - await p2pPairStaking.connect(user1.signer).breakUpMatchedOrder(orderHash0) - ); - - await waitForTx( - await p2pPairStaking - .connect(user1.signer) - .matchPairStakingList(user1SignedOrder1, user2SignedOrder) - ); - }); -}); diff --git a/test/xtoken_ntoken_bakc.spec.ts b/test/xtoken_ntoken_bakc.spec.ts deleted file mode 100644 index c38c4cf51..000000000 --- a/test/xtoken_ntoken_bakc.spec.ts +++ /dev/null @@ -1,404 +0,0 @@ -import {loadFixture} from "@nomicfoundation/hardhat-network-helpers"; -import {expect} from "chai"; -import {MAX_UINT_AMOUNT, ONE_ADDRESS} from "../helpers/constants"; -import {convertToCurrencyDecimals} from "../helpers/contracts-helpers"; -import {advanceTimeAndBlock, waitForTx} from "../helpers/misc-utils"; -import {TestEnv} from "./helpers/make-suite"; -import {testEnvFixture} from "./helpers/setup-env"; -import { - changePriceAndValidate, - changeSApePriceAndValidate, - mintAndValidate, - supplyAndValidate, -} from "./helpers/validated-steps"; -import {parseEther} from "ethers/lib/utils"; -import {getAutoCompoundApe} from "../helpers/contracts-getters"; - -describe("APE Coin Staking Test", () => { - let testEnv: TestEnv; - const sApeAddress = ONE_ADDRESS; - - const fixture = async () => { - testEnv = await loadFixture(testEnvFixture); - const { - ape, - mayc, - bayc, - users: [user1, depositor, , , , user4], - pool, - apeCoinStaking, - bakc, - } = testEnv; - - const cApe = await getAutoCompoundApe(); - - await supplyAndValidate(ape, "20000", depositor, true); - await changePriceAndValidate(ape, "0.001"); - await changeSApePriceAndValidate(sApeAddress, "0.001"); - - await changePriceAndValidate(mayc, "50"); - await changePriceAndValidate(bayc, "50"); - - await waitForTx( - await ape.connect(user1.signer).approve(pool.address, MAX_UINT_AMOUNT) - ); - await waitForTx( - await bakc.connect(user1.signer).setApprovalForAll(pool.address, true) - ); - - // send extra tokens to the apestaking contract for rewards - await waitForTx( - await ape - .connect(user1.signer) - ["mint(address,uint256)"]( - apeCoinStaking.address, - parseEther("100000000000") - ) - ); - - // user4 deposit MINIMUM_LIQUIDITY to make test case easy - await mintAndValidate(ape, "1", user4); - await waitForTx( - await ape.connect(user4.signer).approve(cApe.address, MAX_UINT_AMOUNT) - ); - const MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); - await waitForTx( - await cApe.connect(user4.signer).deposit(user4.address, MINIMUM_LIQUIDITY) - ); - - return testEnv; - }; - - it("user can supply bakc first and stake paired nft", async () => { - const { - users: [user1], - ape, - mayc, - pool, - bakc, - nMAYC, - nBAKC, - } = await loadFixture(fixture); - await supplyAndValidate(bakc, "1", user1, true); - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - - const amount = await convertToCurrencyDecimals(ape.address, "10000"); - const halfAmount = await convertToCurrencyDecimals(ape.address, "5000"); - - await waitForTx( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] - ) - ); - - const totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // advance in time - await advanceTimeAndBlock(parseInt("3600")); - await waitForTx( - await pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 0}]) - ); - let apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).to.be.equal(parseEther("3600")); - - await advanceTimeAndBlock(parseInt("3600")); - await waitForTx( - await pool - .connect(user1.signer) - .claimBAKC(mayc.address, [{mainTokenId: 0, bakcTokenId: 0}]) - ); - apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).to.be.equal(parseEther("7200")); - - await waitForTx( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: halfAmount, - isUncommit: false, - }, - ]) - ); - - apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).to.be.equal(parseEther("12200")); - - await waitForTx( - await pool.connect(user1.signer).withdrawBAKC(mayc.address, [ - { - mainTokenId: 0, - bakcTokenId: 0, - amount: halfAmount, - isUncommit: true, - }, - ]) - ); - - apeBalance = await ape.balanceOf(user1.address); - expect(apeBalance).to.be.equal(parseEther("17200")); - - await waitForTx( - await pool - .connect(user1.signer) - .withdrawERC721(bakc.address, [0], user1.address) - ); - expect(await nBAKC.balanceOf(user1.address)).to.be.equal(0); - expect(await bakc.balanceOf(user1.address)).to.be.equal(1); - }); - - it("unstakeApePositionAndRepay when bakc in user wallet: bakc reward should transfer to user wallet", async () => { - const { - users: [user1], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await waitForTx(await bakc["mint(uint256,address)"]("1", user1.address)); - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - const amount = await convertToCurrencyDecimals(ape.address, "10000"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] - ) - ); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const userBalance = await ape.balanceOf(user1.address); - expect(userBalance).to.be.eq(parseEther("86400")); - }); - - it("unstakeApePositionAndRepay when bakc has been supplied: bakc reward should transfer to user wallet", async () => { - const { - users: [user1], - ape, - mayc, - pool, - bakc, - } = await loadFixture(fixture); - - await supplyAndValidate(bakc, "1", user1, true); - await supplyAndValidate(mayc, "1", user1, true); - await mintAndValidate(ape, "10000", user1); - const amount = await convertToCurrencyDecimals(ape.address, "10000"); - - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] - ) - ); - - // advance in time - await advanceTimeAndBlock(parseInt("86400")); - - expect( - await pool - .connect(user1.signer) - .unstakeApePositionAndRepay(mayc.address, 0) - ); - - const userBalance = await ape.balanceOf(user1.address); - expect(userBalance).to.be.eq(parseEther("86400")); - }); - - it("liquidate bakc will unstake user ape staking position", async () => { - const { - users: [user1, liquidator], - ape, - mayc, - pool, - weth, - bakc, - nMAYC, - } = await loadFixture(fixture); - await supplyAndValidate(ape, "20000", liquidator, true); - await changePriceAndValidate(ape, "0.001"); - await changeSApePriceAndValidate(sApeAddress, "0.001"); - await changePriceAndValidate(mayc, "50"); - await changePriceAndValidate(bakc, "5"); - - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user1, true); - - const amount = parseEther("10000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] - ) - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - await supplyAndValidate(weth, "91", liquidator, true, "200000"); - - await advanceTimeAndBlock(parseInt("3600")); - - // drop HF and ERC-721_HF below 1 - await changePriceAndValidate(ape, "1"); - await changeSApePriceAndValidate(sApeAddress, "1"); - await changePriceAndValidate(mayc, "1"); - await changePriceAndValidate(bakc, "1"); - - // start auction - await waitForTx( - await pool - .connect(liquidator.signer) - .startAuction(user1.address, bakc.address, 0) - ); - - expect(await ape.balanceOf(user1.address)).to.be.equal(0); - // try to liquidate the NFT - expect( - await pool - .connect(liquidator.signer) - .liquidateERC721( - bakc.address, - user1.address, - 0, - parseEther("10"), - false, - {gasLimit: 5000000} - ) - ); - expect(await bakc.ownerOf("0")).to.be.eq(liquidator.address); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - expect(await ape.balanceOf(user1.address)).to.be.equal(parseEther("3600")); - }); - - it("transfer nbakc will unstake user ape staking position", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - bakc, - nBAKC, - nMAYC, - } = await loadFixture(fixture); - await mintAndValidate(ape, "10000", user1); - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user1, true); - - const amount = parseEther("10000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: 0, - cashAmount: amount, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] - ) - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - await advanceTimeAndBlock(parseInt("3600")); - - expect(await nBAKC.ownerOf("0")).to.be.eq(user1.address); - expect(await ape.balanceOf(user1.address)).to.be.equal(0); - expect( - await nBAKC - .connect(user1.signer) - ["safeTransferFrom(address,address,uint256)"]( - user1.address, - user2.address, - 0, - {gasLimit: 5000000} - ) - ); - expect(await nBAKC.ownerOf("0")).to.be.eq(user2.address); - expect(await ape.balanceOf(user1.address)).to.be.equal(parseEther("3600")); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(0); - }); - - it("withdraw bakc will not unstake user ape staking position", async () => { - const { - users: [user1, user2], - ape, - mayc, - pool, - bakc, - nMAYC, - } = await loadFixture(fixture); - - await supplyAndValidate(ape, "20000", user2, true); - await supplyAndValidate(mayc, "1", user1, true); - await supplyAndValidate(bakc, "1", user1, true); - - const amount = parseEther("10000"); - expect( - await pool.connect(user1.signer).borrowApeAndStake( - { - nftAsset: mayc.address, - borrowAsset: ape.address, - borrowAmount: amount, - cashAmount: 0, - }, - [], - [{mainTokenId: 0, bakcTokenId: 0, amount: amount}] - ) - ); - let totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - - // start auction - await waitForTx( - await pool - .connect(user1.signer) - .withdrawERC721(bakc.address, [0], user1.address) - ); - - expect(await bakc.ownerOf("0")).to.be.eq(user1.address); - totalStake = await nMAYC.getUserApeStakingAmount(user1.address); - expect(totalStake).equal(amount); - }); -}); From 11a94c1e830033f593491f9b74dd294bff718552 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Mon, 25 Dec 2023 18:15:23 +0800 Subject: [PATCH 7/9] chore: update apestaking doc and delegation implementation --- contracts/cross-chain/BridgeDefine.sol | 13 -- .../cross-chain/L1/IParaxBridgeNFTVault.sol | 12 - .../cross-chain/L1/IParaxL1MessageHandler.sol | 6 +- contracts/cross-chain/L1/IVaultApeStaking.sol | 56 ++++- contracts/cross-chain/L1/IVaultParaX.sol | 11 + .../cross-chain/L1/ParaxBridgeNFTVault.sol | 90 ------- .../cross-chain/L1/ParaxL1MessageHandler.sol | 42 ++-- contracts/cross-chain/L1/VaultApeStaking.sol | 16 +- contracts/cross-chain/L1/VaultParaX.sol | 50 ++++ contracts/cross-chain/L2/BridgeERC721.sol | 47 ---- .../cross-chain/L2/BridgeERC721Handler.sol | 34 --- contracts/cross-chain/L2/IBridgeERC721.sol | 8 - .../cross-chain/L2/IParaxL2MessageHandler.sol | 9 +- .../cross-chain/L2/ParaxL2MessageHandler.sol | 45 ++-- .../cross-chain/socket/IExecutionManager.sol | 191 +++++++++++++++ contracts/cross-chain/socket/ISocket.sol | 219 ++++++++++++++++++ .../cross-chain/socket/ITransmitManager.sol | 43 ++++ contracts/protocol/pool/PoolCrossChain.sol | 63 +++++ contracts/protocol/pool/PoolParameters.sol | 1 - .../base/MintableIncentivizedERC721.sol | 4 - 20 files changed, 685 insertions(+), 275 deletions(-) delete mode 100644 contracts/cross-chain/L1/IParaxBridgeNFTVault.sol create mode 100644 contracts/cross-chain/L1/IVaultParaX.sol delete mode 100644 contracts/cross-chain/L1/ParaxBridgeNFTVault.sol create mode 100644 contracts/cross-chain/L1/VaultParaX.sol delete mode 100644 contracts/cross-chain/L2/BridgeERC721.sol delete mode 100644 contracts/cross-chain/L2/BridgeERC721Handler.sol delete mode 100644 contracts/cross-chain/L2/IBridgeERC721.sol create mode 100644 contracts/cross-chain/socket/IExecutionManager.sol create mode 100644 contracts/cross-chain/socket/ISocket.sol create mode 100644 contracts/cross-chain/socket/ITransmitManager.sol create mode 100644 contracts/protocol/pool/PoolCrossChain.sol diff --git a/contracts/cross-chain/BridgeDefine.sol b/contracts/cross-chain/BridgeDefine.sol index cdf4ff6b5..d0567de94 100644 --- a/contracts/cross-chain/BridgeDefine.sol +++ b/contracts/cross-chain/BridgeDefine.sol @@ -2,8 +2,6 @@ pragma solidity ^0.8.0; enum MessageType { - AddNewCrossChainERC721, - BridgeERC721, ERC721DELEGATION } @@ -12,20 +10,9 @@ struct BridgeMessage { bytes data; } -struct BridgeERC721Message { - address asset; - uint256[] tokenIds; - address receiver; -} - struct ERC721DelegationMessage { address asset; address delegateTo; uint256[] tokenIds; bool value; } - -//library BridgeDefine { -// -// -//} diff --git a/contracts/cross-chain/L1/IParaxBridgeNFTVault.sol b/contracts/cross-chain/L1/IParaxBridgeNFTVault.sol deleted file mode 100644 index e1bde7b26..000000000 --- a/contracts/cross-chain/L1/IParaxBridgeNFTVault.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; - -interface IParaxBridgeNFTVault { - function releaseNFT(BridgeERC721Message calldata message) external; - - function updateTokenDelegation( - ERC721DelegationMessage calldata delegationInfo - ) external; -} diff --git a/contracts/cross-chain/L1/IParaxL1MessageHandler.sol b/contracts/cross-chain/L1/IParaxL1MessageHandler.sol index a1d2d70c0..d25d3edfe 100644 --- a/contracts/cross-chain/L1/IParaxL1MessageHandler.sol +++ b/contracts/cross-chain/L1/IParaxL1MessageHandler.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {MessageType, BridgeMessage, BridgeERC721Message} from "../BridgeDefine.sol"; - interface IParaxL1MessageHandler { - function addBridgeAsset(address asset) external; - - function bridgeAsset(BridgeERC721Message calldata message) external; + function migration(address asset) external; } diff --git a/contracts/cross-chain/L1/IVaultApeStaking.sol b/contracts/cross-chain/L1/IVaultApeStaking.sol index cb7e3c7c5..8e2c137ea 100644 --- a/contracts/cross-chain/L1/IVaultApeStaking.sol +++ b/contracts/cross-chain/L1/IVaultApeStaking.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; - interface IVaultApeStaking { struct TokenStatus { //record tokenId reward debt position @@ -152,38 +150,88 @@ interface IVaultApeStaking { */ function compoundBAKC(BAKCPairActionInfo calldata actionInfo) external; + /** + * @notice enter ape staking pool when bayc/mayc/bakc transferred to vault contract. + * It's an interceptor call, can only be called by vault self. + * @param nft Identify pool + * @param tokenId The tokenId of the nft + * @param beneficiary The reward beneficiary for the pool position + */ function onboardCheckApeStakingPosition( address nft, uint32 tokenId, address beneficiary ) external; + /** + * @notice exit ape staking pool when bayc/mayc/bakc transferred out from vault contract. + * It's an interceptor call, can only be called by vault self. + * @param nft Identify pool + * @param tokenId The tokenId of the nft + */ function offboardCheckApeStakingPosition( address nft, uint32 tokenId ) external; + /** + * @notice unstake the ape coin position on the ape. can only be called by the bot. + * @param isBAYC if Ape is BAYC + * @param tokenIds Ape token ids + */ function unstakeApe(bool isBAYC, uint32[] calldata tokenIds) external; + /** + * @notice set ape coin staking bot address. can only be called by pool admin. + */ function setApeStakingBot(address _apeStakingBot) external; + /** + * @notice set compound fee rate. can only be called by pool admin. + */ function setCompoundFeeRate(uint32 _compoundFeeRate) external; + /** + * @notice set cApe income rate. can only be called by pool admin. + * @param nft Identify pool + * @param rate new cApe income rate + */ + function setCApeIncomeRate(address nft, uint32 rate) external; + + /** + * @notice claim compound fee. can only be called by bot. + */ function claimCompoundFee(address receiver) external; + /** + * @notice update the position reward beneficiary. can only be called by bridge. + *Nft owner can launch the cross-chain calling from L2 + * @param nft Identify pool + * @param tokenIds The tokenIds of the nft + */ function updateBeneficiary( address nft, uint32[] calldata tokenIds, address newBenificiary ) external; + /** + * @notice Pauses the contract. Only pool admin or emergency admin can call this function + **/ function pause() external; + /** + * @notice Unpause the contract. Only pool admin can call this function + **/ function unpause() external; + /** + * @notice initialization operation for the vault + **/ function initialize() external; + /** + * @notice fetch accumulated compound fee. + **/ function compoundFee() external view returns (uint256); - - function setCApeIncomeRate(address nft, uint32 rate) external; } diff --git a/contracts/cross-chain/L1/IVaultParaX.sol b/contracts/cross-chain/L1/IVaultParaX.sol new file mode 100644 index 000000000..34028e6ad --- /dev/null +++ b/contracts/cross-chain/L1/IVaultParaX.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IVaultParaX { + function updateTokenDelegation( + address delegateTo, + address asset, + uint256[] calldata tokenIds, + bool value + ) external; +} diff --git a/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol b/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol deleted file mode 100644 index e58284776..000000000 --- a/contracts/cross-chain/L1/ParaxBridgeNFTVault.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; -import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; -import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; -import "../../dependencies/openzeppelin/upgradeability/Initializable.sol"; -import "../../dependencies/openzeppelin/upgradeability/OwnableUpgradeable.sol"; -import "./IParaxL1MessageHandler.sol"; -import {IDelegateRegistry} from "../../dependencies/delegation/IDelegateRegistry.sol"; - -contract ParaxBridgeNFTVault is Initializable, OwnableUpgradeable { - IParaxL1MessageHandler internal immutable l1MsgHander; - - IDelegateRegistry internal immutable delegationRegistry; - - mapping(address => bool) supportAsset; - - constructor( - IParaxL1MessageHandler msgHandler, - address _delegationRegistry - ) { - l1MsgHander = msgHandler; - delegationRegistry = IDelegateRegistry(_delegationRegistry); - } - - modifier onlyMsgHandler() { - require(msg.sender == address(l1MsgHander), Errors.ONLY_MSG_HANDLER); - _; - } - - function addBridgeAsset(address asset) external { - require(supportAsset[asset] == false, "asset already added"); - supportAsset[asset] = true; - l1MsgHander.addBridgeAsset(asset); - } - - function bridgeAsset( - address asset, - uint256[] calldata tokenIds, - address receiver - ) external { - require(supportAsset[asset] == true, "asset already added"); - //lock asset - uint256 length = tokenIds.length; - for (uint256 index = 0; index < length; index++) { - uint256 tokenId = tokenIds[index]; - IERC721(asset).safeTransferFrom(msg.sender, address(this), tokenId); - } - - //send cross chain msg - l1MsgHander.bridgeAsset( - BridgeERC721Message({ - asset: asset, - tokenIds: tokenIds, - receiver: receiver - }) - ); - } - - function releaseNFT( - BridgeERC721Message calldata message - ) external onlyMsgHandler { - uint256 length = message.tokenIds.length; - for (uint256 index = 0; index < length; index++) { - uint256 tokenId = message.tokenIds[index]; - IERC721(message.asset).safeTransferFrom( - address(this), - message.receiver, - tokenId - ); - } - } - - function updateTokenDelegation( - ERC721DelegationMessage calldata delegationInfo - ) external onlyMsgHandler { - uint256 length = delegationInfo.tokenIds.length; - for (uint256 index = 0; index < length; index++) { - uint256 tokenId = delegationInfo.tokenIds[index]; - delegationRegistry.delegateERC721( - delegationInfo.delegateTo, - delegationInfo.asset, - tokenId, - "", - delegationInfo.value - ); - } - } -} diff --git a/contracts/cross-chain/L1/ParaxL1MessageHandler.sol b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol index f7b622134..e9805eb13 100644 --- a/contracts/cross-chain/L1/ParaxL1MessageHandler.sol +++ b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol @@ -2,48 +2,38 @@ pragma solidity ^0.8.0; pragma abicoder v2; -import {MessageType, BridgeMessage, BridgeERC721Message} from "../BridgeDefine.sol"; +import {MessageType, BridgeMessage, ERC721DelegationMessage} from "../BridgeDefine.sol"; import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; -import "./IParaxBridgeNFTVault.sol"; +import "./IVaultParaX.sol"; contract ParaxL1MessageHandler { - IParaxBridgeNFTVault internal immutable nftVault; - address immutable bridgeImpl; + address internal immutable vault; + address public immutable socket; - constructor(IParaxBridgeNFTVault vault, address bridge) { - nftVault = vault; - bridgeImpl = bridge; - } - - modifier onlyVault() { - require(msg.sender == address(nftVault), Errors.ONLY_VAULT); - _; + constructor(address vault_, address bridge) { + vault = vault_; + socket = bridge; } modifier onlyBridge() { - require(msg.sender == address(bridgeImpl), Errors.ONLY_BRIDGE); + require(msg.sender == socket, Errors.ONLY_BRIDGE); _; } - function addBridgeAsset(address asset) external onlyVault {} - - function bridgeAsset( - BridgeERC721Message calldata message - ) external onlyVault {} + function migration(address asset) external {} function bridgeReceive(BridgeMessage calldata message) external onlyBridge { - if (message.msgType == MessageType.BridgeERC721) { - BridgeERC721Message memory erc721Message = abi.decode( - message.data, - (BridgeERC721Message) - ); - nftVault.releaseNFT(erc721Message); - } else if (message.msgType == MessageType.ERC721DELEGATION) { + if (message.msgType == MessageType.ERC721DELEGATION) { ERC721DelegationMessage memory delegationMsg = abi.decode( message.data, (ERC721DelegationMessage) ); - nftVault.updateTokenDelegation(delegationMsg); + IVaultParaX(vault).updateTokenDelegation( + delegationMsg.delegateTo, + delegationMsg.asset, + delegationMsg.tokenIds, + delegationMsg.value + ); } } } diff --git a/contracts/cross-chain/L1/VaultApeStaking.sol b/contracts/cross-chain/L1/VaultApeStaking.sol index 39d79828e..7492f1738 100644 --- a/contracts/cross-chain/L1/VaultApeStaking.sol +++ b/contracts/cross-chain/L1/VaultApeStaking.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; import "../../dependencies/openzeppelin/contracts/ReentrancyGuard.sol"; import "../../dependencies/openzeppelin/contracts//Pausable.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; @@ -387,6 +386,7 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } } + /// @inheritdoc IVaultApeStaking function offboardCheckApeStakingPosition( address nft, uint32 tokenId @@ -664,6 +664,7 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { ); } + /// @inheritdoc IVaultApeStaking function setApeStakingBot(address _apeStakingBot) external onlyPoolAdmin { ApeStakingStorage storage ds = apeStakingStorage(); address oldValue = ds.apeStakingBot; @@ -673,6 +674,7 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } } + /// @inheritdoc IVaultApeStaking function setCompoundFeeRate( uint32 _compoundFeeRate ) external onlyPoolAdmin { @@ -686,6 +688,7 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } } + /// @inheritdoc IVaultApeStaking function setCApeIncomeRate( address nft, uint32 rate @@ -700,6 +703,7 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } } + /// @inheritdoc IVaultApeStaking function claimCompoundFee(address receiver) external { ApeStakingStorage storage ds = apeStakingStorage(); require(ds.apeStakingBot == msg.sender, Errors.NOT_APE_STAKING_BOT); @@ -713,11 +717,13 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } } + /// @inheritdoc IVaultApeStaking function compoundFee() external view returns (uint256) { ApeStakingStorage storage ds = apeStakingStorage(); return ds.accuCompoundFee; } + /// @inheritdoc IVaultApeStaking function updateBeneficiary( address nft, uint32[] calldata tokenIds, @@ -753,16 +759,12 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } } - /** - * @notice Pauses the contract. Only pool admin or emergency admin can call this function - **/ + /// @inheritdoc IVaultApeStaking function pause() external onlyEmergencyOrPoolAdmin { _pause(); } - /** - * @notice Unpause the contract. Only pool admin can call this function - **/ + /// @inheritdoc IVaultApeStaking function unpause() external onlyPoolAdmin { _unpause(); } diff --git a/contracts/cross-chain/L1/VaultParaX.sol b/contracts/cross-chain/L1/VaultParaX.sol new file mode 100644 index 000000000..962c6cc86 --- /dev/null +++ b/contracts/cross-chain/L1/VaultParaX.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; +import "../../dependencies/openzeppelin/contracts/ReentrancyGuard.sol"; +import "../../dependencies/openzeppelin/contracts//Pausable.sol"; +import "./IParaxL1MessageHandler.sol"; +import "./IVaultParaX.sol"; +import {IDelegateRegistry} from "../../dependencies/delegation/IDelegateRegistry.sol"; + +contract VaultParaX is ReentrancyGuard, Pausable, IVaultParaX { + IParaxL1MessageHandler internal immutable l1MsgHander; + + IDelegateRegistry internal immutable delegationRegistry; + + mapping(address => bool) supportAsset; + + constructor( + IParaxL1MessageHandler msgHandler, + address _delegationRegistry + ) { + l1MsgHander = msgHandler; + delegationRegistry = IDelegateRegistry(_delegationRegistry); + } + + modifier onlyMsgHandler() { + require(msg.sender == address(l1MsgHander), Errors.ONLY_MSG_HANDLER); + _; + } + + function updateTokenDelegation( + address delegateTo, + address asset, + uint256[] calldata tokenIds, + bool value + ) external onlyMsgHandler { + uint256 length = tokenIds.length; + for (uint256 index = 0; index < length; index++) { + uint256 tokenId = tokenIds[index]; + delegationRegistry.delegateERC721( + delegateTo, + asset, + tokenId, + "", + value + ); + } + } +} diff --git a/contracts/cross-chain/L2/BridgeERC721.sol b/contracts/cross-chain/L2/BridgeERC721.sol deleted file mode 100644 index f8db4385b..000000000 --- a/contracts/cross-chain/L2/BridgeERC721.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {ERC721} from "../../dependencies/openzeppelin/contracts/ERC721.sol"; -import {ERC721Enumerable} from "../../dependencies/openzeppelin/contracts/ERC721Enumerable.sol"; -import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; - -contract BridgeERC21 is ERC721Enumerable { - address internal immutable handler; - - modifier onlyHandler() { - require(msg.sender == handler, Errors.ONLY_VAULT); - _; - } - - constructor( - string memory name, - string memory symbol, - address _handler - ) ERC721(name, symbol) { - handler = _handler; - } - - function mint( - address to, - uint256[] calldata tokenIds - ) external onlyHandler { - uint256 length = tokenIds.length; - for (uint256 index = 0; index < length; index++) { - uint256 tokenId = tokenIds[index]; - _mint(to, tokenId); - } - } - - function burn( - address from, - uint256[] calldata tokenIds - ) external onlyHandler { - uint256 length = tokenIds.length; - for (uint256 index = 0; index < length; index++) { - uint256 tokenId = tokenIds[index]; - address owner = ownerOf(tokenId); - require(owner == from, "invalid"); - _burn(tokenId); - } - } -} diff --git a/contracts/cross-chain/L2/BridgeERC721Handler.sol b/contracts/cross-chain/L2/BridgeERC721Handler.sol deleted file mode 100644 index 3f2627b64..000000000 --- a/contracts/cross-chain/L2/BridgeERC721Handler.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {MessageType, BridgeMessage, BridgeERC721Message} from "../BridgeDefine.sol"; -import {ERC721} from "../../dependencies/openzeppelin/contracts/ERC721.sol"; -import "./IParaxL2MessageHandler.sol"; -import "./IBridgeERC721.sol"; -import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; - -contract BridgeERC21Handler { - IParaxL2MessageHandler internal immutable l2MsgHandler; - - //origin asset -> bridge asset - mapping(address => address) getBridgeAsset; - mapping(address => address) getOriginAsset; - - constructor(IParaxL2MessageHandler msgHandler) { - l2MsgHandler = msgHandler; - } - - modifier onlyMsgHandler() { - require(msg.sender == address(l2MsgHandler), Errors.ONLY_HANDLER); - _; - } - - function bridgeAsset( - BridgeERC721Message calldata message - ) external onlyMsgHandler { - address asset = getBridgeAsset[message.asset]; - require(asset != address(0), "invalid"); - - IBridgeERC721(asset).mint(message.receiver, message.tokenIds); - } -} diff --git a/contracts/cross-chain/L2/IBridgeERC721.sol b/contracts/cross-chain/L2/IBridgeERC721.sol deleted file mode 100644 index 327eb7b51..000000000 --- a/contracts/cross-chain/L2/IBridgeERC721.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IBridgeERC721 { - function mint(address to, uint256[] calldata tokenId) external; - - function burn(address from, uint256[] calldata tokenId) external; -} diff --git a/contracts/cross-chain/L2/IParaxL2MessageHandler.sol b/contracts/cross-chain/L2/IParaxL2MessageHandler.sol index d2f4a1904..5c954566c 100644 --- a/contracts/cross-chain/L2/IParaxL2MessageHandler.sol +++ b/contracts/cross-chain/L2/IParaxL2MessageHandler.sol @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {MessageType, BridgeMessage, BridgeERC721Message, ERC721DelegationMessage} from "../BridgeDefine.sol"; - interface IParaxL2MessageHandler { - //function bridgeAsset(BridgeERC721Message calldata message) external; - function updateTokenDelegation( - ERC721DelegationMessage calldata delegationInfo + address delegateTo, + address underlyingAsset, + uint256[] calldata tokenIds, + bool value ) external; } diff --git a/contracts/cross-chain/L2/ParaxL2MessageHandler.sol b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol index bd41aa215..ea1e2e8db 100644 --- a/contracts/cross-chain/L2/ParaxL2MessageHandler.sol +++ b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol @@ -1,38 +1,45 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {MessageType, BridgeMessage} from "../BridgeDefine.sol"; -import "./BridgeERC721Handler.sol"; +import {MessageType, BridgeMessage, ERC721DelegationMessage} from "../BridgeDefine.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; import "./IParaxL2MessageHandler.sol"; +import "../socket/ISocket.sol"; contract ParaxL2MessageHandler is IParaxL2MessageHandler { - BridgeERC21Handler internal immutable erc712Handler; - address immutable bridgeImpl; - address immutable paraX; + uint32 public immutable siblingChainSlug; + address public immutable socket; + address public immutable paraX; - constructor(BridgeERC21Handler handler) { - erc712Handler = handler; - } - - function bridgeReceive(BridgeMessage calldata message) external { - require(msg.sender == bridgeImpl, ""); - if (message.msgType == MessageType.BridgeERC721) { - BridgeERC721Message memory erc721Message = abi.decode( - message.data, - (BridgeERC721Message) - ); - erc712Handler.bridgeAsset(erc721Message); - } else {} + constructor(address bridge, address paraX_, uint32 siblingChainSlug_) { + socket = bridge; + paraX = paraX_; + siblingChainSlug = siblingChainSlug_; } function updateTokenDelegation( - ERC721DelegationMessage calldata delegationInfo + address delegateTo, + address underlyingAsset, + uint256[] calldata tokenIds, + bool value ) external { require(msg.sender == paraX, Errors.ONLY_PARAX); + ERC721DelegationMessage memory delegationInfo; + delegationInfo.asset = underlyingAsset; + delegationInfo.delegateTo = delegateTo; + delegationInfo.tokenIds = tokenIds; + delegationInfo.value = value; BridgeMessage memory message; message.msgType = MessageType.ERC721DELEGATION; message.data = abi.encode(delegationInfo); //send msg + ISocket(socket).outbound( + siblingChainSlug, + 150000, + "", + "", + abi.encode(message) + ); } } diff --git a/contracts/cross-chain/socket/IExecutionManager.sol b/contracts/cross-chain/socket/IExecutionManager.sol new file mode 100644 index 000000000..96de96783 --- /dev/null +++ b/contracts/cross-chain/socket/IExecutionManager.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title Execution Manager Interface + * @dev This interface defines the functions for managing and executing transactions on external chains + * @dev It is also responsible for collecting all the socket fees, which can then be pulled by others + */ +interface IExecutionManager { + /** + * @notice Returns the executor of the packed message and whether the executor is authorized + * @param packedMessage The message packed with payload, fees and config + * @param sig The signature of the message + * @return The address of the executor and a boolean indicating if the executor is authorized + */ + function isExecutor( + bytes32 packedMessage, + bytes memory sig + ) external view returns (address, bool); + + /** + * @notice Pays the fees for executing a transaction on the external chain + * @dev This function is payable and assumes the socket is going to send correct amount of fees. + * @param minMsgGasLimit_ The minimum gas limit for the transaction + * @param payloadSize_ The payload size in bytes + * @param executionParams_ Extra params for execution + * @param transmissionParams_ Extra params for transmission + * @param siblingChainSlug_ Sibling chain identifier + * @param switchboardFees_ fee charged by switchboard for processing transaction + * @param verificationOverheadFees_ fee charged for verifying transaction + * @param transmitManager_ The transmitManager address + * @param switchboard_ The switchboard address + * @param maxPacketLength_ The maxPacketLength for the capacitor + */ + function payAndCheckFees( + uint256 minMsgGasLimit_, + uint256 payloadSize_, + bytes32 executionParams_, + bytes32 transmissionParams_, + uint32 siblingChainSlug_, + uint128 switchboardFees_, + uint128 verificationOverheadFees_, + address transmitManager_, + address switchboard_, + uint256 maxPacketLength_ + ) external payable returns (uint128, uint128); + + /** + * @notice Returns the minimum fees required for executing a transaction on the external chain + * @param minMsgGasLimit_ minMsgGasLimit_ + * @param siblingChainSlug_ The destination slug + * @return The minimum fees required for executing the transaction + */ + function getMinFees( + uint256 minMsgGasLimit_, + uint256 payloadSize_, + bytes32 executionParams_, + uint32 siblingChainSlug_ + ) external view returns (uint128); + + /** + * @notice function for getting the minimum fees required for executing and transmitting a cross-chain transaction + * @dev this function is called at source to calculate the execution cost. + * @param payloadSize_ byte length of payload. Currently only used to check max length, later on will be used for fees calculation. + * @param executionParams_ Can be used for providing extra information. Currently used for msgValue + * @param siblingChainSlug_ Sibling chain identifier + * @return minExecutionFee : Minimum fees required for executing the transaction + */ + function getExecutionTransmissionMinFees( + uint256 minMsgGasLimit_, + uint256 payloadSize_, + bytes32 executionParams_, + bytes32 transmissionParams_, + uint32 siblingChainSlug_, + address transmitManager_ + ) external view returns (uint128, uint128); + + /** + * @notice Updates the execution fees for an executor and message ID + * @param executor The executor address + * @param executionFees The execution fees to update + * @param msgId The ID of the message + */ + function updateExecutionFees( + address executor, + uint128 executionFees, + bytes32 msgId + ) external; + + /** + * @notice updates the transmission fee + * @param remoteChainSlug_ sibling chain identifier + * @param transmitMinFees_ transmission fees collected + */ + function setTransmissionMinFees( + uint32 remoteChainSlug_, + uint128 transmitMinFees_ + ) external; + + /** + * @notice sets the minimum execution fees required for executing at `siblingChainSlug_` + * @dev this function currently sets the price for a constant msg gas limit and payload size + * @param nonce_ incremental id to prevent signature replay + * @param siblingChainSlug_ sibling chain identifier + * @param executionFees_ total fees where price in destination native token is converted to source native tokens + * @param signature_ signature of fee updater + */ + function setExecutionFees( + uint256 nonce_, + uint32 siblingChainSlug_, + uint128 executionFees_, + bytes calldata signature_ + ) external; + + /** + * @notice sets the min limit for msg value for `siblingChainSlug_` + * @param nonce_ incremental id to prevent signature replay + * @param siblingChainSlug_ sibling chain identifier + * @param msgValueMinThreshold_ min msg value + * @param signature_ signature of fee updater + */ + function setMsgValueMinThreshold( + uint256 nonce_, + uint32 siblingChainSlug_, + uint256 msgValueMinThreshold_, + bytes calldata signature_ + ) external; + + /** + * @notice sets the max limit for msg value for `siblingChainSlug_` + * @param nonce_ incremental id to prevent signature replay + * @param siblingChainSlug_ sibling chain identifier + * @param msgValueMaxThreshold_ max msg value + * @param signature_ signature of fee updater + */ + function setMsgValueMaxThreshold( + uint256 nonce_, + uint32 siblingChainSlug_, + uint256 msgValueMaxThreshold_, + bytes calldata signature_ + ) external; + + /** + * @notice sets the relative token price for `siblingChainSlug_` + * @dev this function is expected to be called frequently to match the original prices + * @param nonce_ incremental id to prevent signature replay + * @param siblingChainSlug_ sibling chain identifier + * @param relativeNativeTokenPrice_ relative price + * @param signature_ signature of fee updater + */ + function setRelativeNativeTokenPrice( + uint256 nonce_, + uint32 siblingChainSlug_, + uint256 relativeNativeTokenPrice_, + bytes calldata signature_ + ) external; + + /** + * @notice called by socket while executing message to validate if the msg value provided is enough + * @param executionParams_ a bytes32 string where first byte gives param type (if value is 0 or not) + * and remaining bytes give the msg value needed + * @param msgValue_ msg.value to be sent with inbound + */ + function verifyParams( + bytes32 executionParams_, + uint256 msgValue_ + ) external view; + + /** + * @notice withdraws switchboard fees from contract + * @param siblingChainSlug_ withdraw fees corresponding to this slug + * @param amount_ withdraw amount + */ + function withdrawSwitchboardFees( + uint32 siblingChainSlug_, + address switchboard_, + uint128 amount_ + ) external; + + /** + * @dev this function gets the transmitManager address from the socket contract. If it is ever upgraded in socket, + * @dev remove the fees from executionManager first, and then upgrade address at socket. + * @notice withdraws transmission fees from contract + * @param siblingChainSlug_ withdraw fees corresponding to this slug + * @param amount_ withdraw amount + */ + function withdrawTransmissionFees( + uint32 siblingChainSlug_, + uint128 amount_ + ) external; +} diff --git a/contracts/cross-chain/socket/ISocket.sol b/contracts/cross-chain/socket/ISocket.sol new file mode 100644 index 000000000..150eecc43 --- /dev/null +++ b/contracts/cross-chain/socket/ISocket.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.0; + +import "./ITransmitManager.sol"; +import "./IExecutionManager.sol"; + +/** + * @title ISocket + * @notice An interface for a cross-chain communication contract + * @dev This interface provides methods for transmitting and executing messages between chains, + * connecting a plug to a remote chain and setting up switchboards for the message transmission + * This interface also emits events for important operations such as message transmission, execution status, + * and plug connection + */ +interface ISocket { + /** + * @notice A struct containing fees required for message transmission and execution + * @param transmissionFees fees needed for transmission + * @param switchboardFees fees needed by switchboard + * @param executionFee fees needed for execution + */ + struct Fees { + uint128 transmissionFees; + uint128 executionFee; + uint128 switchboardFees; + } + + /** + * @title MessageDetails + * @dev This struct defines the details of a message to be executed in a Decapacitor contract. + */ + struct MessageDetails { + // A unique identifier for the message. + bytes32 msgId; + // The fee to be paid for executing the message. + uint256 executionFee; + // The min amount of gas that can be used to execute the message. + uint256 minMsgGasLimit; + // The extra params which might provide msg value and additional info needed for message exec + bytes32 executionParams; + // The payload data to be executed in the message. + bytes payload; + } + + /** + * @title ExecutionDetails + * @dev This struct defines the execution details + */ + struct ExecutionDetails { + // packet id + bytes32 packetId; + // proposal count + uint256 proposalCount; + // gas limit needed to execute inbound + uint256 executionGasLimit; + // proof data required by the Decapacitor contract to verify the message's authenticity + bytes decapacitorProof; + // signature of executor + bytes signature; + } + + /** + * @notice emits the status of message after inbound call + * @param msgId msg id which is executed + */ + event ExecutionSuccess(bytes32 msgId); + + /** + * @notice emits the config set by a plug for a remoteChainSlug + * @param plug address of plug on current chain + * @param siblingChainSlug sibling chain slug + * @param siblingPlug address of plug on sibling chain + * @param inboundSwitchboard inbound switchboard (select from registered options) + * @param outboundSwitchboard outbound switchboard (select from registered options) + * @param capacitor capacitor selected based on outbound switchboard + * @param decapacitor decapacitor selected based on inbound switchboard + */ + event PlugConnected( + address plug, + uint32 siblingChainSlug, + address siblingPlug, + address inboundSwitchboard, + address outboundSwitchboard, + address capacitor, + address decapacitor + ); + + /** + * @notice registers a message + * @dev Packs the message and includes it in a packet with capacitor + * @param remoteChainSlug_ the remote chain slug + * @param minMsgGasLimit_ the gas limit needed to execute the payload on remote + * @param payload_ the data which is needed by plug at inbound call on remote + */ + function outbound( + uint32 remoteChainSlug_, + uint256 minMsgGasLimit_, + bytes32 executionParams_, + bytes32 transmissionParams_, + bytes calldata payload_ + ) external payable returns (bytes32 msgId); + + /** + * @notice executes a message + * @param executionDetails_ the packet details, proof and signature needed for message execution + * @param messageDetails_ the message details + */ + function execute( + ISocket.ExecutionDetails calldata executionDetails_, + ISocket.MessageDetails calldata messageDetails_ + ) external payable; + + /** + * @notice seals data in capacitor for specific batchSize + * @param batchSize_ size of batch to be sealed + * @param capacitorAddress_ address of capacitor + * @param signature_ signed Data needed for verification + */ + function seal( + uint256 batchSize_, + address capacitorAddress_, + bytes calldata signature_ + ) external payable; + + /** + * @notice proposes a packet + * @param packetId_ packet id + * @param root_ root data + * @param switchboard_ The address of switchboard for which this packet is proposed + * @param signature_ signed Data needed for verification + */ + function proposeForSwitchboard( + bytes32 packetId_, + bytes32 root_, + address switchboard_, + bytes calldata signature_ + ) external payable; + + /** + * @notice sets the config specific to the plug + * @param siblingChainSlug_ the sibling chain slug + * @param siblingPlug_ address of plug present at sibling chain to call inbound + * @param inboundSwitchboard_ the address of switchboard to use for receiving messages + * @param outboundSwitchboard_ the address of switchboard to use for sending messages + */ + function connect( + uint32 siblingChainSlug_, + address siblingPlug_, + address inboundSwitchboard_, + address outboundSwitchboard_ + ) external; + + /** + * @notice deploy capacitor and decapacitor for a switchboard with a specified max packet length, sibling chain slug, and capacitor type. + * @param siblingChainSlug_ The slug of the sibling chain that the switchboard is registered with. + * @param maxPacketLength_ The maximum length of a packet allowed by the switchboard. + * @param capacitorType_ The type of capacitor that the switchboard uses. + * @param siblingSwitchboard_ The switchboard address deployed on `siblingChainSlug_` + */ + function registerSwitchboardForSibling( + uint32 siblingChainSlug_, + uint256 maxPacketLength_, + uint256 capacitorType_, + address siblingSwitchboard_ + ) external returns (address capacitor, address decapacitor); + + /** + * @notice Emits the sibling switchboard for given `siblingChainSlug_`. + * @dev This function is expected to be only called by switchboard. + * @dev the event emitted is tracked by transmitters to decide which switchboard a packet should be proposed on + * @param siblingChainSlug_ The slug of the sibling chain + * @param siblingSwitchboard_ The switchboard address deployed on `siblingChainSlug_` + */ + function useSiblingSwitchboard( + uint32 siblingChainSlug_, + address siblingSwitchboard_ + ) external; + + /** + * @notice Retrieves the packet id roots for a specified packet id. + * @param packetId_ The packet id for which to retrieve the root. + * @param proposalCount_ The proposal id for packetId_ for which to retrieve the root. + * @param switchboard_ The address of switchboard for which this packet is proposed + * @return The packet id roots for the specified packet id. + */ + function packetIdRoots( + bytes32 packetId_, + uint256 proposalCount_, + address switchboard_ + ) external view returns (bytes32); + + /** + * @notice Retrieves the latest proposalCount for a packet id. + * @return The proposal count for the specified packet id. + */ + function proposalCount(bytes32 packetId_) external view returns (uint256); + + /** + * @notice Retrieves the minimum fees required for a message with a specified gas limit and destination chain. + * @param minMsgGasLimit_ The gas limit of the message. + * @param remoteChainSlug_ The slug of the destination chain for the message. + * @param plug_ The address of the plug through which the message is sent. + * @return totalFees The minimum fees required for the specified message. + */ + function getMinFees( + uint256 minMsgGasLimit_, + uint256 payloadSize_, + bytes32 executionParams_, + bytes32 transmissionParams_, + uint32 remoteChainSlug_, + address plug_ + ) external view returns (uint256 totalFees); + + /// return instance of transmit manager + function transmitManager__() external view returns (ITransmitManager); + + /// return instance of execution manager + function executionManager__() external view returns (IExecutionManager); +} diff --git a/contracts/cross-chain/socket/ITransmitManager.sol b/contracts/cross-chain/socket/ITransmitManager.sol new file mode 100644 index 000000000..46cb454c6 --- /dev/null +++ b/contracts/cross-chain/socket/ITransmitManager.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.0; + +/** + * @title ITransmitManager + * @dev The interface for a transmit manager contract + */ +interface ITransmitManager { + /** + * @notice Checks if a given transmitter is authorized to send transactions to the destination chain. + * @param siblingSlug The unique identifier for the sibling chain. + * @param digest The digest of the message being signed. + * @param signature The signature of the message being signed. + * @return The address of the transmitter and a boolean indicating whether the transmitter is authorized or not. + */ + function checkTransmitter( + uint32 siblingSlug, + bytes32 digest, + bytes calldata signature + ) external view returns (address, bool); + + /** + * @notice sets the transmission fee needed to transmit message to given `siblingSlug_` + * @dev recovered address should add have feeUpdater role for `siblingSlug_` + * @param nonce_ The incremental nonce to prevent signature replay + * @param siblingSlug_ sibling id for which fee updater is registered + * @param transmissionFees_ digest which is signed by transmitter + * @param signature_ signature + */ + function setTransmissionFees( + uint256 nonce_, + uint32 siblingSlug_, + uint128 transmissionFees_, + bytes calldata signature_ + ) external; + + /** + * @notice receives fees from Execution manager + * @dev this function can be used to keep track of fees received for each slug + * @param siblingSlug_ sibling id for which fee updater is registered + */ + function receiveFees(uint32 siblingSlug_) external payable; +} diff --git a/contracts/protocol/pool/PoolCrossChain.sol b/contracts/protocol/pool/PoolCrossChain.sol new file mode 100644 index 000000000..90f13ae0f --- /dev/null +++ b/contracts/protocol/pool/PoolCrossChain.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.10; + +import {ParaVersionedInitializable} from "../libraries/paraspace-upgradeability/ParaVersionedInitializable.sol"; +import {Errors} from "../libraries/helpers/Errors.sol"; +import {DataTypes} from "../libraries/types/DataTypes.sol"; +import {IPoolAddressesProvider} from "../../interfaces/IPoolAddressesProvider.sol"; +import {IPoolCrossChain} from "../../interfaces/IPoolCrossChain.sol"; +import {PoolStorage} from "./PoolStorage.sol"; +import {Address} from "../../dependencies/openzeppelin/contracts/Address.sol"; +import {ParaReentrancyGuard} from "../libraries/paraspace-upgradeability/ParaReentrancyGuard.sol"; +import "../../cross-chain/L2/IParaxL2MessageHandler.sol"; + +/** + * @title Pool Parameters contract + * + * @notice Main point of interaction with an ParaSpace protocol's market + **/ +contract PoolCrossChain is + ParaVersionedInitializable, + ParaReentrancyGuard, + PoolStorage, + IPoolCrossChain +{ + uint256 public constant POOL_REVISION = 200; + IPoolAddressesProvider public immutable ADDRESSES_PROVIDER; + address public immutable CROSS_CHAIN_MSG_HANDLER; + + function getRevision() internal pure virtual override returns (uint256) { + return POOL_REVISION; + } + + /** + * @dev Constructor. + * @param provider The address of the PoolAddressesProvider contract + * @param msgHandler The address of the L2 message handler contract + */ + constructor(IPoolAddressesProvider provider, address msgHandler) { + ADDRESSES_PROVIDER = provider; + CROSS_CHAIN_MSG_HANDLER = msgHandler; + } + + function updateTokenDelegation( + address delegateTo, + address underlyingAsset, + uint256[] calldata tokenIds, + bool value + ) external { + DataTypes.PoolStorage storage ps = poolStorage(); + + require( + msg.sender == ps._reserves[underlyingAsset].xTokenAddress, + Errors.CALLER_NOT_XTOKEN + ); + + IParaxL2MessageHandler(CROSS_CHAIN_MSG_HANDLER).updateTokenDelegation( + delegateTo, + underlyingAsset, + tokenIds, + value + ); + } +} diff --git a/contracts/protocol/pool/PoolParameters.sol b/contracts/protocol/pool/PoolParameters.sol index 4a8d53cbd..7366d1bbb 100644 --- a/contracts/protocol/pool/PoolParameters.sol +++ b/contracts/protocol/pool/PoolParameters.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.10; import {ParaVersionedInitializable} from "../libraries/paraspace-upgradeability/ParaVersionedInitializable.sol"; -import {Errors} from "../libraries/helpers/Errors.sol"; import {ReserveConfiguration} from "../libraries/configuration/ReserveConfiguration.sol"; import {PoolLogic} from "../libraries/logic/PoolLogic.sol"; import {ReserveLogic} from "../libraries/logic/ReserveLogic.sol"; diff --git a/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol b/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol index d47964476..ae3881756 100644 --- a/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol +++ b/contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol @@ -397,10 +397,6 @@ abstract contract MintableIncentivizedERC721 is ); } - function revokeDelegation(address v1Registry) external onlyPoolAdmin { - IDelegationRegistry(v1Registry).revokeAllDelegates(); - } - function delegateForToken( address delegate, uint256[] calldata tokenIds, From f9b85ee44bc58744bd8ccd9e33e2aea599191e85 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Fri, 29 Dec 2023 15:01:55 +0800 Subject: [PATCH 8/9] chore: v1 --- .../cross-chain/L1/IParaxL1MessageHandler.sol | 13 + contracts/cross-chain/L1/IVault.sol | 6 +- contracts/cross-chain/L1/IVaultApeStaking.sol | 10 - contracts/cross-chain/L1/IVaultCommon.sol | 13 + .../cross-chain/L1/IVaultEarlyAccess.sol | 10 + contracts/cross-chain/L1/IVaultTemplate.sol | 12 - .../cross-chain/L1/ParaxL1MessageHandler.sol | 35 +- contracts/cross-chain/L1/VaultApeStaking.sol | 26 -- contracts/cross-chain/L1/VaultCommon.sol | 53 ++- contracts/cross-chain/L1/VaultEarlyAccess.sol | 351 ++++++++++++++++++ contracts/cross-chain/L1/VaultParaX.sol | 2 - contracts/cross-chain/L1/VaultTemplate.sol | 42 --- .../cross-chain/L2/ParaxL2MessageHandler.sol | 39 +- contracts/interfaces/IAAVEPool.sol | 76 ++++ contracts/interfaces/ICApe.sol | 2 + .../protocol/libraries/helpers/Errors.sol | 5 + 16 files changed, 574 insertions(+), 121 deletions(-) create mode 100644 contracts/cross-chain/L1/IVaultEarlyAccess.sol delete mode 100644 contracts/cross-chain/L1/IVaultTemplate.sol create mode 100644 contracts/cross-chain/L1/VaultEarlyAccess.sol delete mode 100644 contracts/cross-chain/L1/VaultTemplate.sol create mode 100644 contracts/interfaces/IAAVEPool.sol diff --git a/contracts/cross-chain/L1/IParaxL1MessageHandler.sol b/contracts/cross-chain/L1/IParaxL1MessageHandler.sol index d25d3edfe..77072e548 100644 --- a/contracts/cross-chain/L1/IParaxL1MessageHandler.sol +++ b/contracts/cross-chain/L1/IParaxL1MessageHandler.sol @@ -3,4 +3,17 @@ pragma solidity ^0.8.0; interface IParaxL1MessageHandler { function migration(address asset) external; + + function updateTokenDelegation( + address delegateTo, + address underlyingAsset, + uint256[] calldata tokenIds, + bool value + ) external; + + function updateApeStakingBeneficiary( + address nft, + uint32[] calldata tokenIds, + address newBenificiary + ) external; } diff --git a/contracts/cross-chain/L1/IVault.sol b/contracts/cross-chain/L1/IVault.sol index 2d87bdaa9..57767f2a9 100644 --- a/contracts/cross-chain/L1/IVault.sol +++ b/contracts/cross-chain/L1/IVault.sol @@ -2,13 +2,15 @@ pragma solidity ^0.8.0; import "./IVaultApeStaking.sol"; -import "./IVaultTemplate.sol"; +import "./IVaultEarlyAccess.sol"; import "./IVaultCommon.sol"; +import "./IVaultParaX.sol"; import "../../interfaces/IParaProxyInterfaces.sol"; interface IVault is IVaultApeStaking, - IVaultTemplate, + IVaultEarlyAccess, IVaultCommon, + IVaultParaX, IParaProxyInterfaces {} diff --git a/contracts/cross-chain/L1/IVaultApeStaking.sol b/contracts/cross-chain/L1/IVaultApeStaking.sol index 8e2c137ea..0badbf1f0 100644 --- a/contracts/cross-chain/L1/IVaultApeStaking.sol +++ b/contracts/cross-chain/L1/IVaultApeStaking.sol @@ -215,16 +215,6 @@ interface IVaultApeStaking { address newBenificiary ) external; - /** - * @notice Pauses the contract. Only pool admin or emergency admin can call this function - **/ - function pause() external; - - /** - * @notice Unpause the contract. Only pool admin can call this function - **/ - function unpause() external; - /** * @notice initialization operation for the vault **/ diff --git a/contracts/cross-chain/L1/IVaultCommon.sol b/contracts/cross-chain/L1/IVaultCommon.sol index d711c1571..bc933b9b9 100644 --- a/contracts/cross-chain/L1/IVaultCommon.sol +++ b/contracts/cross-chain/L1/IVaultCommon.sol @@ -2,6 +2,19 @@ pragma solidity ^0.8.0; interface IVaultCommon { + /** + * @notice Pauses the contract. Only pool admin or emergency admin can call this function + **/ + function pause() external; + + /** + * @notice Unpause the contract. Only pool admin can call this function + **/ + function unpause() external; + + /** + * @dev Receives and executes a batch of function calls on this contract. + */ function multicall( bytes[] calldata data ) external returns (bytes[] memory results); diff --git a/contracts/cross-chain/L1/IVaultEarlyAccess.sol b/contracts/cross-chain/L1/IVaultEarlyAccess.sol new file mode 100644 index 000000000..b55a6dca6 --- /dev/null +++ b/contracts/cross-chain/L1/IVaultEarlyAccess.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IVaultEarlyAccess { + function depositERC721(address nft, uint32[] calldata tokenIds) external; + + // function offboardNFTs(address nft, uint32[] calldata tokenIds) external; + // + // function offboardNFT(address nft, uint32 tokenId) external; +} diff --git a/contracts/cross-chain/L1/IVaultTemplate.sol b/contracts/cross-chain/L1/IVaultTemplate.sol deleted file mode 100644 index f5c066fc0..000000000 --- a/contracts/cross-chain/L1/IVaultTemplate.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IVaultTemplate { - function onboardNFTs(address nft, uint32[] calldata tokenIds) external; - - function onboardNFT(address nft, uint32 tokenId) external; - - function offboardNFTs(address nft, uint32[] calldata tokenIds) external; - - function offboardNFT(address nft, uint32 tokenId) external; -} diff --git a/contracts/cross-chain/L1/ParaxL1MessageHandler.sol b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol index e9805eb13..ed3eccd2e 100644 --- a/contracts/cross-chain/L1/ParaxL1MessageHandler.sol +++ b/contracts/cross-chain/L1/ParaxL1MessageHandler.sol @@ -4,7 +4,7 @@ pragma abicoder v2; import {MessageType, BridgeMessage, ERC721DelegationMessage} from "../BridgeDefine.sol"; import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; -import "./IVaultParaX.sol"; +import "./IVault.sol"; contract ParaxL1MessageHandler { address internal immutable vault; @@ -22,18 +22,25 @@ contract ParaxL1MessageHandler { function migration(address asset) external {} - function bridgeReceive(BridgeMessage calldata message) external onlyBridge { - if (message.msgType == MessageType.ERC721DELEGATION) { - ERC721DelegationMessage memory delegationMsg = abi.decode( - message.data, - (ERC721DelegationMessage) - ); - IVaultParaX(vault).updateTokenDelegation( - delegationMsg.delegateTo, - delegationMsg.asset, - delegationMsg.tokenIds, - delegationMsg.value - ); - } + function updateTokenDelegation( + address delegateTo, + address underlyingAsset, + uint256[] calldata tokenIds, + bool value + ) external onlyBridge { + IVault(vault).updateTokenDelegation( + delegateTo, + underlyingAsset, + tokenIds, + value + ); + } + + function updateApeStakingBeneficiary( + address nft, + uint32[] calldata tokenIds, + address newBenificiary + ) external onlyBridge { + IVault(vault).updateBeneficiary(nft, tokenIds, newBenificiary); } } diff --git a/contracts/cross-chain/L1/VaultApeStaking.sol b/contracts/cross-chain/L1/VaultApeStaking.sol index 7492f1738..6b5123a5d 100644 --- a/contracts/cross-chain/L1/VaultApeStaking.sol +++ b/contracts/cross-chain/L1/VaultApeStaking.sol @@ -759,16 +759,6 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { } } - /// @inheritdoc IVaultApeStaking - function pause() external onlyEmergencyOrPoolAdmin { - _pause(); - } - - /// @inheritdoc IVaultApeStaking - function unpause() external onlyPoolAdmin { - _unpause(); - } - modifier onlyMsgHandler() { require(msg.sender == address(l1MsgHander), Errors.ONLY_MSG_HANDLER); _; @@ -782,14 +772,6 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { _; } - /** - * @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), @@ -797,14 +779,6 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { ); } - function _onlyPoolOrEmergencyAdmin() internal view { - require( - aclManager.isPoolAdmin(msg.sender) || - aclManager.isEmergencyAdmin(msg.sender), - Errors.CALLER_NOT_POOL_OR_EMERGENCY_ADMIN - ); - } - function apeStakingStorage() internal pure diff --git a/contracts/cross-chain/L1/VaultCommon.sol b/contracts/cross-chain/L1/VaultCommon.sol index 08a6be926..229797891 100644 --- a/contracts/cross-chain/L1/VaultCommon.sol +++ b/contracts/cross-chain/L1/VaultCommon.sol @@ -4,12 +4,28 @@ pragma solidity ^0.8.0; import "../../dependencies/openzeppelin/contracts/ReentrancyGuard.sol"; import "../../dependencies/openzeppelin/contracts//Pausable.sol"; import "../../dependencies/openzeppelin/contracts/Address.sol"; +import "../../interfaces/IACLManager.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; import "./IVaultCommon.sol"; contract VaultCommon is ReentrancyGuard, Pausable, IVaultCommon { - /** - * @dev Receives and executes a batch of function calls on this contract. - */ + IACLManager private immutable aclManager; + + constructor(address _aclManager) { + aclManager = IACLManager(_aclManager); + } + + /// @inheritdoc IVaultCommon + function pause() external onlyEmergencyOrPoolAdmin { + _pause(); + } + + /// @inheritdoc IVaultCommon + function unpause() external onlyPoolAdmin { + _unpause(); + } + + /// @inheritdoc IVaultCommon function multicall( bytes[] calldata data ) external virtual returns (bytes[] memory results) { @@ -28,4 +44,35 @@ contract VaultCommon is ReentrancyGuard, Pausable, IVaultCommon { ) external pure returns (bytes4) { return this.onERC721Received.selector; } + + /** + * @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/cross-chain/L1/VaultEarlyAccess.sol b/contracts/cross-chain/L1/VaultEarlyAccess.sol new file mode 100644 index 000000000..092eedb5d --- /dev/null +++ b/contracts/cross-chain/L1/VaultEarlyAccess.sol @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../dependencies/openzeppelin/contracts/ReentrancyGuard.sol"; +import "../../dependencies/openzeppelin/contracts//Pausable.sol"; +import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; +import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; +import {SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; +import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; +import "../../interfaces/IACLManager.sol"; +import "../../interfaces/IAAVEPool.sol"; +import "../../misc/interfaces/IWETH.sol"; +import "../../interfaces/ILido.sol"; +import "../../interfaces/ICApe.sol"; +import "../../interfaces/ICurve.sol"; +import "./IVaultEarlyAccess.sol"; +import "./IVaultApeStaking.sol"; + +contract VaultEarlyAccess is ReentrancyGuard, Pausable, IVaultEarlyAccess { + using SafeERC20 for IERC20; + using SafeCast for uint256; + + address public constant ETH = address(0x1); + address public constant USD = address(0x2); + address internal immutable weth; + address internal immutable usdt; + address internal immutable usdc; + address internal immutable apecoin; + address internal immutable cApe; + address internal immutable aavePool; + address internal immutable LIDO; + IACLManager private immutable aclManager; + + bytes32 constant EARLY_ACCESS_STORAGE_POSITION = + bytes32( + uint256(keccak256("vault.early.access.implementation.storage")) - 1 + ); + + enum AssetType { + NONE, + CollectionAsset, + SingleAsset + } + + enum StrategyType { + NONE, + NOYIELD, + AAVE, + LIDO, + APESTAKING, + CAPE + } + + struct AssetInfo { + StrategyType strategyType; + //total share for ERC20, total balance for ERC721 + uint184 totalShare; + // user => shareBalance + mapping(address => uint256) shareBalance; + // tokenId => owner, only for ERC721 + mapping(uint256 => address) erc721Owner; + } + + struct EarlyAccessStorage { + address bridge; + address yieldBot; + uint256 crossChainETH; + //single asset status + mapping(address => bool) assetStatus; + //asset => assetInfo + mapping(address => AssetInfo) assetInfo; + } + + constructor( + address _weth, + address _cApe, + address _usdt, + address _usdc, + address _aavePool, + address _LIDO, + address _aclManager + ) { + weth = _weth; + cApe = _cApe; + apecoin = address(ICApe(cApe).apeCoin()); + usdt = _usdt; + usdc = _usdc; + aavePool = _aavePool; + LIDO = _LIDO; + aclManager = IACLManager(_aclManager); + } + + function setBridge(address _bridge) external onlyPoolAdmin { + EarlyAccessStorage storage ds = earlyAccessStorage(); + ds.bridge = _bridge; + } + + function setYieldBot(address _yieldBot) external onlyPoolAdmin { + EarlyAccessStorage storage ds = earlyAccessStorage(); + ds.yieldBot = _yieldBot; + } + + function updateAccessListStatus( + address[] calldata assets, + bool[] calldata statuses + ) external onlyPoolAdmin { + uint256 arrayLength = assets.length; + EarlyAccessStorage storage ds = earlyAccessStorage(); + for (uint256 index = 0; index < arrayLength; index++) { + address asset = assets[index]; + bool status = statuses[index]; + require(ds.assetStatus[asset].isAllow != status, Errors.INVALID_STATUS); + ds.assetStatus[asset].isAllow = status; + } + } + + function setAssetStrategy( + address[] calldata assets, + StrategyType[] calldata strategies + ) external onlyPoolAdmin { + uint256 arrayLength = assets.length; + require(strategies.length == arrayLength, Errors.INVALID_PARAMETER); + EarlyAccessStorage storage ds = earlyAccessStorage(); + for (uint256 index = 0; index < arrayLength; index++) { + address asset = assets[index]; + AssetInfo storage assetInfo = ds.assetInfo[asset]; + //change strategy is not allowed currently + require( + assetInfo.strategyType == StrategyType.NONE, + Errors.ASSET_STRATEGY_ALREADY_SET + ); + assetInfo.strategyType = strategies[index]; + } + } + + function depositERC721(address asset, uint32[] calldata tokenIds) external { + uint256 arrayLength = tokenIds.length; + EarlyAccessStorage storage ds = earlyAccessStorage(); + AssetInfo storage assetInfo = ds.assetInfo[asset]; + require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); + bool isApeStaking = assetInfo.strategyType == StrategyType.APESTAKING; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + IERC721(asset).safeTransferFrom(msg.sender, address(this), tokenId); + if (isApeStaking) { + IVaultApeStaking(address(this)).onboardCheckApeStakingPosition( + asset, + tokenId, + msg.sender + ); + } + assetInfo.erc721Owner[tokenId] = msg.sender; + } + assetInfo.shareBalance[msg.sender] += arrayLength; + assetInfo.totalShare += arrayLength.toUint184(); + } + + receive() external payable { + revert("not allow yet"); + } + + //ETH + wETH + stETH + cbETH + rETH + function depositETHCollection(address asset, uint256 amount) public payable { + if (asset == ETH) { + require(msg.value > 0, Errors.INVALID_PARAMETER); + } else { + require(msg.value == 0, Errors.INVALID_PARAMETER); + } + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(ds.assetStatus, Errors.NOT_IN_ACCESS_LIST); + _depositLidoStrategy(ETH, msg.value, true); + } + + function depositLidoStrategy(address asset, uint256 amount) external { + _depositLidoStrategy(asset, amount, false); + } + + function _depositLidoStrategy( + address asset, + uint256 amount, + bool transferred + ) internal { + require(amount > 0, Errors.INVALID_PARAMETER); + + EarlyAccessStorage storage ds = earlyAccessStorage(); + AssetInfo storage assetInfo = ds.assetInfo[asset]; + require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); + require( + assetInfo.strategyType == StrategyType.LIDO, + Errors.STRATEGY_NOT_MATCH + ); + + uint256 totalBalance = totalLIDOBalance(); + if (transferred) { + totalBalance -= amount; + } + uint256 share = (amount * assetInfo.totalShare) / totalBalance; + assetInfo.totalShare += share.toUint184(); + ds.shareBalance[LIDO][msg.sender] += share; + + if (!transferred) { + IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); + } + } + + function lidoStaking() external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + uint256 balance = IERC20(weth).balanceOf(address(this)); + if (balance > 0) { + IWETH(weth).withdraw(balance); + } + balance = address(this).balance; + if (balance > 0) { + ILido(LIDO).submit{value: balance}(address(0)); + } + } + + function totalETHBalance() internal view returns (uint256) { + return + address(this).balance + + IERC20(LIDO).balanceOf(address(this)) + + IERC20(weth).balanceOf(address(this)); + } + + function depositAAVEStrategy(address asset, uint256 amount) external { + require(amount > 0, Errors.INVALID_PARAMETER); + + EarlyAccessStorage storage ds = earlyAccessStorage(); + AssetInfo storage assetInfo = ds.assetInfo[asset]; + require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); + require( + assetInfo.strategyType == StrategyType.AAVE, + Errors.STRATEGY_NOT_MATCH + ); + + uint256 share = (amount * assetInfo.totalShare) / + _totalBalanceForAAVE(asset); + assetInfo.totalShare += share.toUint184(); + ds.shareBalance[asset][msg.sender] += share; + + IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); + } + + function aaveStaking(address asset) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + uint256 balance = IERC20(asset).balanceOf(address(this)); + IERC20(asset).safeApprove(aavePool, balance); + IAAVEPool(aavePool).supply(asset, balance, address(this), 0); + } + + function _totalBalanceForAAVE(address asset) internal view returns (uint256) { + address aToken = IAAVEPool(aavePool) + .getReserveData(asset) + .aTokenAddress; + return + IERC20(asset).balanceOf(address(this)) + + IERC20(aToken).balanceOf(address(this)); + } + + function depositCApeStrategy(address asset, uint256 amount) external { + require(amount > 0, Errors.INVALID_PARAMETER); + + EarlyAccessStorage storage ds = earlyAccessStorage(); + AssetInfo storage assetInfo = ds.assetInfo[asset]; + require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); + require( + assetInfo.strategyType == StrategyType.CAPE, + Errors.STRATEGY_NOT_MATCH + ); + + uint256 share = (amount * assetInfo.totalShare) / + _totalBalanceForCApe(); + assetInfo.totalShare += share.toUint184(); + ds.shareBalance[cApe][msg.sender] += share; + + IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); + } + + function capeStaking() external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + uint256 balance = IERC20(apecoin).balanceOf(address(this)); + IERC20(apecoin).safeApprove(cApe, balance); + ICApe(cApe).deposit(address(this), balance); + } + + function _totalBalanceForCApe() internal view returns (uint256) { + return + IERC20(apecoin).balanceOf(address(this)) + + IERC20(cApe).balanceOf(address(this)); + } + + function depositERC20(address asset, uint256 amount) external { + require(amount > 0, Errors.INVALID_PARAMETER); + + EarlyAccessStorage storage ds = earlyAccessStorage(); + AssetInfo storage assetInfo = ds.assetInfo[asset]; + require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); + require( + assetInfo.strategyType == StrategyType.NOYIELD, + Errors.STRATEGY_NOT_MATCH + ); + + uint256 share = (amount * assetInfo.totalShare) / _totalBalance(asset); + assetInfo.totalShare += share.toUint184(); + ds.shareBalance[asset][msg.sender] += share; + + IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); + } + + function _totalBalance(address asset) internal view returns (uint256) { + return IERC20(asset).balanceOf(address(this)); + } + + function depositUSDT() external {} + + function depositUSDC() external {} + + function crossChain(address asset) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(ds.bridge != address(0), Errors.NOT_ENABLE); + + //TODO cross chain implementation + } + + modifier onlyPoolAdmin() { + _onlyPoolAdmin(); + _; + } + + function _onlyPoolAdmin() internal view { + require( + aclManager.isPoolAdmin(msg.sender), + Errors.CALLER_NOT_POOL_ADMIN + ); + } + + function earlyAccessStorage() + internal + pure + returns (EarlyAccessStorage storage ds) + { + bytes32 position = EARLY_ACCESS_STORAGE_POSITION; + assembly { + ds.slot := position + } + } +} diff --git a/contracts/cross-chain/L1/VaultParaX.sol b/contracts/cross-chain/L1/VaultParaX.sol index 962c6cc86..1290e34fe 100644 --- a/contracts/cross-chain/L1/VaultParaX.sol +++ b/contracts/cross-chain/L1/VaultParaX.sol @@ -14,8 +14,6 @@ contract VaultParaX is ReentrancyGuard, Pausable, IVaultParaX { IDelegateRegistry internal immutable delegationRegistry; - mapping(address => bool) supportAsset; - constructor( IParaxL1MessageHandler msgHandler, address _delegationRegistry diff --git a/contracts/cross-chain/L1/VaultTemplate.sol b/contracts/cross-chain/L1/VaultTemplate.sol deleted file mode 100644 index 113590e45..000000000 --- a/contracts/cross-chain/L1/VaultTemplate.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "../../dependencies/openzeppelin/contracts/ReentrancyGuard.sol"; -import "../../dependencies/openzeppelin/contracts//Pausable.sol"; -import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; -import "./IVaultTemplate.sol"; -import "./IVaultApeStaking.sol"; - -//Mock Socket Vault Implementation, Just for Testing -contract VaultTemplate is ReentrancyGuard, Pausable, IVaultTemplate { - function onboardNFTs(address nft, uint32[] calldata tokenIds) external { - for (uint256 index = 0; index < tokenIds.length; index++) { - onboardNFT(nft, tokenIds[index]); - } - } - - function onboardNFT(address nft, uint32 tokenId) public { - IERC721(nft).safeTransferFrom(msg.sender, address(this), tokenId); - IVaultApeStaking(address(this)).onboardCheckApeStakingPosition( - nft, - tokenId, - msg.sender - ); - } - - //didn't check ownership here, just for testing - function offboardNFTs(address nft, uint32[] calldata tokenIds) external { - for (uint256 index = 0; index < tokenIds.length; index++) { - offboardNFT(nft, tokenIds[index]); - } - } - - //didn't check ownership here, just for testing - function offboardNFT(address nft, uint32 tokenId) public { - IVaultApeStaking(address(this)).offboardCheckApeStakingPosition( - nft, - tokenId - ); - IERC721(nft).safeTransferFrom(address(this), msg.sender, tokenId); - } -} diff --git a/contracts/cross-chain/L2/ParaxL2MessageHandler.sol b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol index ea1e2e8db..808424475 100644 --- a/contracts/cross-chain/L2/ParaxL2MessageHandler.sol +++ b/contracts/cross-chain/L2/ParaxL2MessageHandler.sol @@ -5,6 +5,7 @@ import {MessageType, BridgeMessage, ERC721DelegationMessage} from "../BridgeDefi import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; import "./IParaxL2MessageHandler.sol"; import "../socket/ISocket.sol"; +import "../L1/IParaxL1MessageHandler.sol"; contract ParaxL2MessageHandler is IParaxL2MessageHandler { uint32 public immutable siblingChainSlug; @@ -25,21 +26,39 @@ contract ParaxL2MessageHandler is IParaxL2MessageHandler { ) external { require(msg.sender == paraX, Errors.ONLY_PARAX); - ERC721DelegationMessage memory delegationInfo; - delegationInfo.asset = underlyingAsset; - delegationInfo.delegateTo = delegateTo; - delegationInfo.tokenIds = tokenIds; - delegationInfo.value = value; - BridgeMessage memory message; - message.msgType = MessageType.ERC721DELEGATION; - message.data = abi.encode(delegationInfo); - //send msg ISocket(socket).outbound( siblingChainSlug, 150000, "", "", - abi.encode(message) + abi.encodeWithSelector( + IParaxL1MessageHandler.updateTokenDelegation.selector, + delegateTo, + underlyingAsset, + tokenIds, + value + ) + ); + } + + function updateApeStakingBeneficiary( + address nft, + uint32[] calldata tokenIds, + address newBenificiary + ) external { + require(msg.sender == paraX, Errors.ONLY_PARAX); + + ISocket(socket).outbound( + siblingChainSlug, + 150000, + "", + "", + abi.encodeWithSelector( + IParaxL1MessageHandler.updateApeStakingBeneficiary.selector, + nft, + tokenIds, + newBenificiary + ) ); } } diff --git a/contracts/interfaces/IAAVEPool.sol b/contracts/interfaces/IAAVEPool.sol new file mode 100644 index 000000000..6dfcfd90c --- /dev/null +++ b/contracts/interfaces/IAAVEPool.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; + +interface IAAVEPool { + struct ReserveData { + //stores the reserve configuration + ReserveConfigurationMap configuration; + //the liquidity index. Expressed in ray + uint128 liquidityIndex; + //the current supply rate. Expressed in ray + uint128 currentLiquidityRate; + //variable borrow index. Expressed in ray + uint128 variableBorrowIndex; + //the current variable borrow rate. Expressed in ray + uint128 currentVariableBorrowRate; + //the current stable borrow rate. Expressed in ray + uint128 currentStableBorrowRate; + //timestamp of last update + uint40 lastUpdateTimestamp; + //the id of the reserve. Represents the position in the list of the active reserves + uint16 id; + //aToken address + address aTokenAddress; + //stableDebtToken address + address stableDebtTokenAddress; + //variableDebtToken address + address variableDebtTokenAddress; + //address of the interest rate strategy + address interestRateStrategyAddress; + //the current treasury balance, scaled + uint128 accruedToTreasury; + //the outstanding unbacked aTokens minted through the bridging feature + uint128 unbacked; + //the outstanding debt borrowed against this asset in isolation mode + uint128 isolationModeTotalDebt; + } + + struct ReserveConfigurationMap { + //bit 0-15: LTV + //bit 16-31: Liq. threshold + //bit 32-47: Liq. bonus + //bit 48-55: Decimals + //bit 56: reserve is active + //bit 57: reserve is frozen + //bit 58: borrowing is enabled + //bit 59: stable rate borrowing enabled + //bit 60: asset is paused + //bit 61: borrowing in isolation mode is enabled + //bit 62-63: reserved + //bit 64-79: reserve factor + //bit 80-115 borrow cap in whole tokens, borrowCap == 0 => no cap + //bit 116-151 supply cap in whole tokens, supplyCap == 0 => no cap + //bit 152-167 liquidation protocol fee + //bit 168-175 eMode category + //bit 176-211 unbacked mint cap in whole tokens, unbackedMintCap == 0 => minting disabled + //bit 212-251 debt ceiling for isolation mode with (ReserveConfiguration::DEBT_CEILING_DECIMALS) decimals + //bit 252-255 unused + + uint256 data; + } + + function getReserveData(address asset) external view returns (ReserveData memory); + + function supply( + address asset, + uint256 amount, + address onBehalfOf, + uint16 referralCode + ) external; + + function withdraw( + address asset, + uint256 amount, + address to + ) external returns (uint256); +} diff --git a/contracts/interfaces/ICApe.sol b/contracts/interfaces/ICApe.sol index 40c9b6ddd..3bc0f5f72 100644 --- a/contracts/interfaces/ICApe.sol +++ b/contracts/interfaces/ICApe.sol @@ -83,4 +83,6 @@ interface ICApe is IERC20 { * @notice collect ape reward in ApeCoinStaking and deposit to earn compound interest. **/ function harvestAndCompound() external; + + function apeCoin() external view returns(IERC20); } diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index f966d4361..cdae0b753 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -146,4 +146,9 @@ library Errors { string public constant NFT_NOT_IN_POOL = "207"; //nft not in the pool string public constant ALREADY_STAKING = "208"; //already staking string public constant INVALID_STATUS = "209"; //invalid status + string public constant NOT_IN_ACCESS_LIST = "210"; //not in access list + string public constant STRATEGY_NOT_MATCH = "211"; //strategy not match + string public constant ASSET_STRATEGY_ALREADY_SET = "212"; //asset strategy already set + string public constant NOT_ENABLE = "213"; //cross chain not enable + string public constant NOT_STAKING_BOT = "214"; //not staking bot } From ce07cbdd52093f28a46f0e91816498d4096dd2e0 Mon Sep 17 00:00:00 2001 From: zhoujia6139 Date: Tue, 2 Jan 2024 13:30:41 +0800 Subject: [PATCH 9/9] chore: early access implementation --- contracts/cross-chain/L1/IVaultApeStaking.sol | 8 +- .../cross-chain/L1/IVaultEarlyAccess.sol | 54 +- contracts/cross-chain/L1/VaultApeStaking.sol | 138 +++-- contracts/cross-chain/L1/VaultEarlyAccess.sol | 506 ++++++++++++------ .../openzeppelin/contracts/EnumerableSet.sol | 165 +++--- contracts/interfaces/IAAVEPool.sol | 4 +- contracts/interfaces/ICApe.sol | 2 +- contracts/interfaces/IcbETH.sol | 8 + contracts/interfaces/IrETH.sol | 8 + contracts/interfaces/IwstETH.sol | 10 + .../protocol/libraries/helpers/Errors.sol | 11 +- helpers/contracts-deployments.ts | 117 ++-- helpers/contracts-getters.ts | 36 -- helpers/contracts-helpers.ts | 2 - helpers/types.ts | 1 + scripts/upgrade/P2PPairStaking.ts | 35 -- test/vault_ape_staking.spec.ts | 87 +-- 17 files changed, 715 insertions(+), 477 deletions(-) create mode 100644 contracts/interfaces/IcbETH.sol create mode 100644 contracts/interfaces/IrETH.sol create mode 100644 contracts/interfaces/IwstETH.sol delete mode 100644 scripts/upgrade/P2PPairStaking.ts diff --git a/contracts/cross-chain/L1/IVaultApeStaking.sol b/contracts/cross-chain/L1/IVaultApeStaking.sol index 0badbf1f0..3a05942be 100644 --- a/contracts/cross-chain/L1/IVaultApeStaking.sol +++ b/contracts/cross-chain/L1/IVaultApeStaking.sol @@ -154,12 +154,12 @@ interface IVaultApeStaking { * @notice enter ape staking pool when bayc/mayc/bakc transferred to vault contract. * It's an interceptor call, can only be called by vault self. * @param nft Identify pool - * @param tokenId The tokenId of the nft + * @param tokenIds The tokenId array of the nft * @param beneficiary The reward beneficiary for the pool position */ function onboardCheckApeStakingPosition( address nft, - uint32 tokenId, + uint32[] calldata tokenIds, address beneficiary ) external; @@ -167,11 +167,11 @@ interface IVaultApeStaking { * @notice exit ape staking pool when bayc/mayc/bakc transferred out from vault contract. * It's an interceptor call, can only be called by vault self. * @param nft Identify pool - * @param tokenId The tokenId of the nft + * @param tokenIds The tokenId array of the nft */ function offboardCheckApeStakingPosition( address nft, - uint32 tokenId + uint32[] calldata tokenIds ) external; /** diff --git a/contracts/cross-chain/L1/IVaultEarlyAccess.sol b/contracts/cross-chain/L1/IVaultEarlyAccess.sol index b55a6dca6..6d64b23b3 100644 --- a/contracts/cross-chain/L1/IVaultEarlyAccess.sol +++ b/contracts/cross-chain/L1/IVaultEarlyAccess.sol @@ -2,9 +2,55 @@ pragma solidity ^0.8.0; interface IVaultEarlyAccess { - function depositERC721(address nft, uint32[] calldata tokenIds) external; + enum StrategyType { + NONE, + NORMAL, //no yield or AAVE + CAPE, + APESTAKING + } - // function offboardNFTs(address nft, uint32[] calldata tokenIds) external; - // - // function offboardNFT(address nft, uint32 tokenId) external; + function updateAccessListStatus( + address[] calldata assets, + bool[] calldata statuses + ) external; + + function setCollectionStrategy( + address[] calldata assets, + StrategyType[] calldata strategies + ) external; + + function addETHCollection(address asset) external; + + function ethCollection() external view returns (address[] memory); + + function isInETHList(address asset) external view returns (bool); + + function depositETHCollection( + address asset, + uint256 amount + ) external payable; + + function totalETHValue() external view returns (uint256); + + function totalETHShare() external view returns (uint256); + + function addUSDCollection(address asset) external; + + function usdCollection() external view returns (address[] memory); + + function isInUSDList(address asset) external view returns (bool); + + function depositUSDCollection(address asset, uint256 amount) external; + + function totalUSDValue() external view returns (uint256); + + function totalUSDShare() external view returns (uint256); + + function cApeCollection() external view returns (address[] memory); + + function depositCApeCollection(address asset, uint256 amount) external; + + function depositERC20(address asset, uint256 amount) external; + + function depositERC721(address asset, uint32[] calldata tokenIds) external; } diff --git a/contracts/cross-chain/L1/VaultApeStaking.sol b/contracts/cross-chain/L1/VaultApeStaking.sol index 6b5123a5d..8f71c93d0 100644 --- a/contracts/cross-chain/L1/VaultApeStaking.sol +++ b/contracts/cross-chain/L1/VaultApeStaking.sol @@ -353,7 +353,7 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { /// @inheritdoc IVaultApeStaking function onboardCheckApeStakingPosition( address nft, - uint32 tokenId, + uint32[] calldata tokenIds, address beneficiary ) external override { require(msg.sender == address(this), Errors.INVALID_CALLER); @@ -361,80 +361,104 @@ contract VaultApeStaking is ReentrancyGuard, Pausable, IVaultApeStaking { if (nft == bayc || nft == mayc || nft == bakc) { //ensure no ape position uint256 poolId = (nft == bayc) ? 1 : ((nft == mayc) ? 2 : 3); - (uint256 stakedAmount, ) = apeCoinStaking.nftPosition( - poolId, - tokenId - ); - require(stakedAmount == 0, Errors.ALREADY_STAKING); - if (nft == bayc || nft == mayc) { - (, bool isPaired) = apeCoinStaking.mainToBakc(poolId, tokenId); - require(!isPaired, Errors.ALREADY_STAKING); - } - PoolState storage poolState = apeStakingStorage().poolStates[nft]; - TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; - require( - tokenStatus.beneficiary == address(0), - Errors.INVALID_STATUS - ); - tokenStatus.beneficiary = beneficiary; - tokenStatus.rewardsDebt = poolState.accumulatedRewardsPerNft; - poolState.tokenStatus[tokenId] = tokenStatus; + uint256 arrayLength = tokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + (uint256 stakedAmount, ) = apeCoinStaking.nftPosition( + poolId, + tokenId + ); + require(stakedAmount == 0, Errors.ALREADY_STAKING); + if (nft == bayc || nft == mayc) { + (, bool isPaired) = apeCoinStaking.mainToBakc( + poolId, + tokenId + ); + require(!isPaired, Errors.ALREADY_STAKING); + } - poolState.totalPosition += 1; + TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; + require( + tokenStatus.beneficiary == address(0), + Errors.INVALID_STATUS + ); + + tokenStatus.beneficiary = beneficiary; + tokenStatus.rewardsDebt = poolState.accumulatedRewardsPerNft; + poolState.tokenStatus[tokenId] = tokenStatus; + } + + poolState.totalPosition += arrayLength.toUint32(); } } /// @inheritdoc IVaultApeStaking function offboardCheckApeStakingPosition( address nft, - uint32 tokenId + uint32[] calldata tokenIds ) external override { + ApeStakingStorage storage ds = apeStakingStorage(); //ensure ownership by bridge, don't validate ownership here - require(msg.sender == address(this), Errors.INVALID_CALLER); + require( + msg.sender == address(this) || msg.sender == ds.apeStakingBot, + Errors.INVALID_CALLER + ); - PoolState storage poolState = apeStakingStorage().poolStates[nft]; + PoolState storage poolState = ds.poolStates[nft]; if (poolState.totalPosition > 0) { - if (poolState.stakingPosition > 0) { - TokenStatus memory tokenStatus = poolState.tokenStatus[tokenId]; - if (nft == bakc) { - if (tokenStatus.isStaking) { - uint32[] memory tokenIds = new uint32[](1); - tokenIds[0] = tokenStatus.pairTokenId; - _unstakeApe(tokenStatus.isPairedWithBayc, tokenIds); - } - } else { - if (tokenStatus.isStaking || tokenStatus.isPairedStaking) { - bool isBAYC = (nft == bayc); - uint32[] memory tokenIds = new uint32[](1); - tokenIds[0] = tokenId; - _unstakeApe(isBAYC, tokenIds); + uint256 arrayLength = tokenIds.length; + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + + if (poolState.stakingPosition > 0) { + TokenStatus memory tokenStatus = poolState.tokenStatus[ + tokenId + ]; + if (nft == bakc) { + if (tokenStatus.isStaking) { + uint32[] memory ids = new uint32[](1); + ids[0] = tokenStatus.pairTokenId; + _unstakeApe(tokenStatus.isPairedWithBayc, ids); + } + } else { + if ( + tokenStatus.isStaking || tokenStatus.isPairedStaking + ) { + bool isBAYC = (nft == bayc); + uint32[] memory ids = new uint32[](1); + ids[0] = tokenId; + _unstakeApe(isBAYC, ids); + } } } - } - //claim pending reward - uint256 cApeExchangeRate = cApe.getPooledApeByShares( - WadRayMath.RAY - ); - uint256 rewardShare = _claimPendingReward( - poolState.tokenStatus[tokenId], - poolState.accumulatedRewardsPerNft, - nft, - tokenId, - cApeExchangeRate - ); - if (rewardShare > 0) { - uint256 pendingReward = rewardShare.rayMul(cApeExchangeRate); - cApe.transfer( - poolState.tokenStatus[tokenId].beneficiary, - pendingReward + //claim one by one, since every token id may have a different beneficiary + uint256 cApeExchangeRate = cApe.getPooledApeByShares( + WadRayMath.RAY ); - } + uint256 rewardShare = _claimPendingReward( + poolState.tokenStatus[tokenId], + poolState.accumulatedRewardsPerNft, + nft, + tokenId, + cApeExchangeRate + ); + if (rewardShare > 0) { + uint256 pendingReward = rewardShare.rayMul( + cApeExchangeRate + ); + cApe.transfer( + poolState.tokenStatus[tokenId].beneficiary, + pendingReward + ); + } - poolState.totalPosition -= 1; - delete poolState.tokenStatus[tokenId]; + // we also reduce totalPosition one by one for accuracy + poolState.totalPosition -= 1; + delete poolState.tokenStatus[tokenId]; + } } } diff --git a/contracts/cross-chain/L1/VaultEarlyAccess.sol b/contracts/cross-chain/L1/VaultEarlyAccess.sol index 092eedb5d..d8297f773 100644 --- a/contracts/cross-chain/L1/VaultEarlyAccess.sol +++ b/contracts/cross-chain/L1/VaultEarlyAccess.sol @@ -4,32 +4,37 @@ pragma solidity ^0.8.0; import "../../dependencies/openzeppelin/contracts/ReentrancyGuard.sol"; import "../../dependencies/openzeppelin/contracts//Pausable.sol"; import "../../dependencies/openzeppelin/contracts/SafeCast.sol"; +import "../../dependencies/openzeppelin/contracts/EnumerableSet.sol"; import {IERC721} from "../../dependencies/openzeppelin/contracts/IERC721.sol"; -import {IERC20} from "../../dependencies/openzeppelin/contracts/IERC20.sol"; +import {IERC20, IERC20Detailed} from "../../dependencies/openzeppelin/contracts/IERC20Detailed.sol"; import {SafeERC20} from "../../dependencies/openzeppelin/contracts/SafeERC20.sol"; import {Errors} from "../../protocol/libraries/helpers/Errors.sol"; import "../../interfaces/IACLManager.sol"; import "../../interfaces/IAAVEPool.sol"; import "../../misc/interfaces/IWETH.sol"; import "../../interfaces/ILido.sol"; +import "../../interfaces/IcbETH.sol"; +import "../../interfaces/IrETH.sol"; +import "../../interfaces/IwstETH.sol"; import "../../interfaces/ICApe.sol"; -import "../../interfaces/ICurve.sol"; import "./IVaultEarlyAccess.sol"; import "./IVaultApeStaking.sol"; contract VaultEarlyAccess is ReentrancyGuard, Pausable, IVaultEarlyAccess { using SafeERC20 for IERC20; using SafeCast for uint256; + using EnumerableSet for EnumerableSet.AddressSet; - address public constant ETH = address(0x1); - address public constant USD = address(0x2); + address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address public constant ETHCollection = address(0x1); + address public constant USDCollection = address(0x2); address internal immutable weth; - address internal immutable usdt; - address internal immutable usdc; - address internal immutable apecoin; + address internal immutable stETH; + address internal immutable wstETH; + address internal immutable cbETH; + address internal immutable rETH; address internal immutable cApe; address internal immutable aavePool; - address internal immutable LIDO; IACLManager private immutable aclManager; bytes32 constant EARLY_ACCESS_STORAGE_POSITION = @@ -37,25 +42,26 @@ contract VaultEarlyAccess is ReentrancyGuard, Pausable, IVaultEarlyAccess { uint256(keccak256("vault.early.access.implementation.storage")) - 1 ); - enum AssetType { - NONE, - CollectionAsset, - SingleAsset - } - - enum StrategyType { - NONE, - NOYIELD, - AAVE, - LIDO, - APESTAKING, - CAPE - } + /** + * @dev Emitted during deposit asset in early access + * @param assetCollection asset collection address tag, 0x1 for ETH, 0x2 for USD + * @param share share for asset collection + * @param asset deposited asset address + * @param amount deposited asset amount + **/ + event Deposit( + address assetCollection, + uint256 share, + address asset, + uint256 amount + ); - struct AssetInfo { + struct CollectionInfo { StrategyType strategyType; - //total share for ERC20, total balance for ERC721 - uint184 totalShare; + //exchange rate for ERC20 + uint184 exchangeRate; + // total share on current chain + uint256 totalShare; // user => shareBalance mapping(address => uint256) shareBalance; // tokenId => owner, only for ERC721 @@ -66,28 +72,37 @@ contract VaultEarlyAccess is ReentrancyGuard, Pausable, IVaultEarlyAccess { address bridge; address yieldBot; uint256 crossChainETH; + EnumerableSet.AddressSet ethCollection; + EnumerableSet.AddressSet usdCollection; //single asset status mapping(address => bool) assetStatus; - //asset => assetInfo - mapping(address => AssetInfo) assetInfo; + //collection => CollectionInfo + mapping(address => CollectionInfo) collectionInfo; } + //1. asset address might be zero address on some chain + //2. big amount share attach + //3. is strategy still useful constructor( address _weth, + address _wstETH, + address _cbETH, + address _rETH, address _cApe, - address _usdt, - address _usdc, address _aavePool, - address _LIDO, address _aclManager ) { weth = _weth; + wstETH = _wstETH; + if (wstETH == address(0)) { + stETH = address(0); + } else { + stETH = IwstETH(wstETH).stETH(); + } + cbETH = _cbETH; + rETH = _rETH; cApe = _cApe; - apecoin = address(ICApe(cApe).apeCoin()); - usdt = _usdt; - usdc = _usdc; aavePool = _aavePool; - LIDO = _LIDO; aclManager = IACLManager(_aclManager); } @@ -110,12 +125,12 @@ contract VaultEarlyAccess is ReentrancyGuard, Pausable, IVaultEarlyAccess { for (uint256 index = 0; index < arrayLength; index++) { address asset = assets[index]; bool status = statuses[index]; - require(ds.assetStatus[asset].isAllow != status, Errors.INVALID_STATUS); - ds.assetStatus[asset].isAllow = status; + require(ds.assetStatus[asset] != status, Errors.INVALID_STATUS); + ds.assetStatus[asset] = status; } } - function setAssetStrategy( + function setCollectionStrategy( address[] calldata assets, StrategyType[] calldata strategies ) external onlyPoolAdmin { @@ -124,202 +139,381 @@ contract VaultEarlyAccess is ReentrancyGuard, Pausable, IVaultEarlyAccess { EarlyAccessStorage storage ds = earlyAccessStorage(); for (uint256 index = 0; index < arrayLength; index++) { address asset = assets[index]; - AssetInfo storage assetInfo = ds.assetInfo[asset]; + CollectionInfo storage collectionInfo = ds.collectionInfo[asset]; //change strategy is not allowed currently require( - assetInfo.strategyType == StrategyType.NONE, + collectionInfo.strategyType == StrategyType.NONE, Errors.ASSET_STRATEGY_ALREADY_SET ); - assetInfo.strategyType = strategies[index]; - } - } - - function depositERC721(address asset, uint32[] calldata tokenIds) external { - uint256 arrayLength = tokenIds.length; - EarlyAccessStorage storage ds = earlyAccessStorage(); - AssetInfo storage assetInfo = ds.assetInfo[asset]; - require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); - bool isApeStaking = assetInfo.strategyType == StrategyType.APESTAKING; - for (uint256 index = 0; index < arrayLength; index++) { - uint32 tokenId = tokenIds[index]; - IERC721(asset).safeTransferFrom(msg.sender, address(this), tokenId); - if (isApeStaking) { - IVaultApeStaking(address(this)).onboardCheckApeStakingPosition( - asset, - tokenId, - msg.sender - ); + collectionInfo.strategyType = strategies[index]; + if (collectionInfo.exchangeRate == 0) { + collectionInfo.exchangeRate = 1e18; } - assetInfo.erc721Owner[tokenId] = msg.sender; } - assetInfo.shareBalance[msg.sender] += arrayLength; - assetInfo.totalShare += arrayLength.toUint184(); } receive() external payable { revert("not allow yet"); } - //ETH + wETH + stETH + cbETH + rETH - function depositETHCollection(address asset, uint256 amount) public payable { - if (asset == ETH) { - require(msg.value > 0, Errors.INVALID_PARAMETER); - } else { - require(msg.value == 0, Errors.INVALID_PARAMETER); + /// ETH collection + + function addETHCollection(address asset) external onlyPoolAdmin { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(!isInETHList(asset), Errors.ALREADY_IN_COLLECTION_LIST); + if (asset != ETH) { + require( + IERC20Detailed(asset).decimals() == 18, + Errors.INVALID_PARAMETER + ); } + ds.ethCollection.add(asset); + } + + // eth + weth + stETH + wstETH + cbETH + rETH + function ethCollection() external view returns (address[] memory) { EarlyAccessStorage storage ds = earlyAccessStorage(); - require(ds.assetStatus, Errors.NOT_IN_ACCESS_LIST); - _depositLidoStrategy(ETH, msg.value, true); + return ds.ethCollection.values(); } - function depositLidoStrategy(address asset, uint256 amount) external { - _depositLidoStrategy(asset, amount, false); + function isInETHList(address asset) public view returns (bool) { + EarlyAccessStorage storage ds = earlyAccessStorage(); + return ds.ethCollection.contains(asset); } - function _depositLidoStrategy( + function depositETHCollection( address asset, - uint256 amount, - bool transferred - ) internal { - require(amount > 0, Errors.INVALID_PARAMETER); - + uint256 amount + ) external payable { EarlyAccessStorage storage ds = earlyAccessStorage(); - AssetInfo storage assetInfo = ds.assetInfo[asset]; - require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); + require(ds.assetStatus[asset], Errors.NOT_IN_ACCESS_LIST); + require(isInETHList(asset), Errors.NOT_IN_COLLECTION_LIST); + CollectionInfo storage collectionInfo = ds.collectionInfo[ + ETHCollection + ]; require( - assetInfo.strategyType == StrategyType.LIDO, - Errors.STRATEGY_NOT_MATCH + collectionInfo.strategyType != StrategyType.NONE, + Errors.STRATEGY_NOT_SET ); - uint256 totalBalance = totalLIDOBalance(); - if (transferred) { - totalBalance -= amount; + if (asset == ETH) { + require(msg.value > 0, Errors.INVALID_PARAMETER); + amount = msg.value; + } else { + require(msg.value == 0 && amount > 0, Errors.INVALID_PARAMETER); + IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); } - uint256 share = (amount * assetInfo.totalShare) / totalBalance; - assetInfo.totalShare += share.toUint184(); - ds.shareBalance[LIDO][msg.sender] += share; - if (!transferred) { - IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); + uint256 ethValue = _getETHValue(asset, amount); + uint256 share = _updateUserShare(collectionInfo, msg.sender, ethValue); + + emit Deposit(ETHCollection, share, asset, amount); + } + + function totalETHValue() external view returns (uint256) { + EarlyAccessStorage storage ds = earlyAccessStorage(); + address[] memory ethAssets = ds.ethCollection.values(); + uint256 length = ethAssets.length; + uint256 totalValue; + for (uint256 index = 0; index < length; index++) { + totalValue += _getETHCollectionAssetValue(ethAssets[index]); } + + return totalValue; } - function lidoStaking() external { + function totalETHShare() external view returns (uint256) { EarlyAccessStorage storage ds = earlyAccessStorage(); - require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); - uint256 balance = IERC20(weth).balanceOf(address(this)); - if (balance > 0) { - IWETH(weth).withdraw(balance); + return ds.collectionInfo[ETHCollection].totalShare; + } + + function _getETHCollectionAssetValue( + address asset + ) internal view returns (uint256) { + if (asset == ETH) { + return address(this).balance; } - balance = address(this).balance; - if (balance > 0) { - ILido(LIDO).submit{value: balance}(address(0)); + + uint256 totalBalance = _totalBalanceWithAAVE(asset); + if (totalBalance == 0) { + return 0; } + + return _getETHValue(asset, totalBalance); } - function totalETHBalance() internal view returns (uint256) { - return - address(this).balance + - IERC20(LIDO).balanceOf(address(this)) + - IERC20(weth).balanceOf(address(this)); + function _getETHValue( + address asset, + uint256 amount + ) internal view returns (uint256) { + uint256 exchangeRate = 1e18; + if (asset == cbETH) { + exchangeRate = IcbETH(cbETH).exchangeRate(); + } else if (asset == rETH) { + exchangeRate = IrETH(rETH).getExchangeRate(); + } else if (asset == wstETH) { + exchangeRate = IwstETH(wstETH).stEthPerToken(); + } + + return (amount * exchangeRate) / 1e18; } - function depositAAVEStrategy(address asset, uint256 amount) external { - require(amount > 0, Errors.INVALID_PARAMETER); + /// USD collection + function addUSDCollection(address asset) external onlyPoolAdmin { EarlyAccessStorage storage ds = earlyAccessStorage(); - AssetInfo storage assetInfo = ds.assetInfo[asset]; - require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); + require(!isInUSDList(asset), Errors.ALREADY_IN_COLLECTION_LIST); + ds.usdCollection.add(asset); + } + + //USDT + USDC + DAI + function usdCollection() external view returns (address[] memory) { + EarlyAccessStorage storage ds = earlyAccessStorage(); + return ds.usdCollection.values(); + } + + function isInUSDList(address asset) public view returns (bool) { + EarlyAccessStorage storage ds = earlyAccessStorage(); + return ds.usdCollection.contains(asset); + } + + function depositUSDCollection(address asset, uint256 amount) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(ds.assetStatus[asset], Errors.NOT_IN_ACCESS_LIST); + require(isInUSDList(asset), Errors.NOT_IN_COLLECTION_LIST); + CollectionInfo storage collectionInfo = ds.collectionInfo[ + USDCollection + ]; require( - assetInfo.strategyType == StrategyType.AAVE, - Errors.STRATEGY_NOT_MATCH + collectionInfo.strategyType != StrategyType.NONE, + Errors.STRATEGY_NOT_SET ); - uint256 share = (amount * assetInfo.totalShare) / - _totalBalanceForAAVE(asset); - assetInfo.totalShare += share.toUint184(); - ds.shareBalance[asset][msg.sender] += share; - IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); + + uint256 usdValue = _getUSDValue(asset, amount); + uint256 share = _updateUserShare(collectionInfo, msg.sender, usdValue); + + emit Deposit(USDCollection, share, asset, amount); } - function aaveStaking(address asset) external { + function totalUSDValue() external view returns (uint256) { EarlyAccessStorage storage ds = earlyAccessStorage(); - require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); - uint256 balance = IERC20(asset).balanceOf(address(this)); - IERC20(asset).safeApprove(aavePool, balance); - IAAVEPool(aavePool).supply(asset, balance, address(this), 0); + address[] memory usdAssets = ds.ethCollection.values(); + uint256 length = usdAssets.length; + uint256 totalValue; + for (uint256 index = 0; index < length; index++) { + totalValue += _getUSDCollectionAssetValue(usdAssets[index]); + } + + return totalValue; } - function _totalBalanceForAAVE(address asset) internal view returns (uint256) { - address aToken = IAAVEPool(aavePool) - .getReserveData(asset) - .aTokenAddress; - return - IERC20(asset).balanceOf(address(this)) + - IERC20(aToken).balanceOf(address(this)); + function totalUSDShare() external view returns (uint256) { + EarlyAccessStorage storage ds = earlyAccessStorage(); + return ds.collectionInfo[USDCollection].totalShare; } - function depositCApeStrategy(address asset, uint256 amount) external { + function _getUSDCollectionAssetValue( + address asset + ) internal view returns (uint256) { + uint256 totalBalance = _totalBalanceWithAAVE(asset); + if (totalBalance == 0) { + return 0; + } + + return _getUSDValue(asset, totalBalance); + } + + function _getUSDValue( + address asset, + uint256 amount + ) internal view returns (uint256) { + uint8 decimals = IERC20Detailed(asset).decimals(); + uint256 multiplier = 10 ** (18 - decimals); + return amount * multiplier; + } + + /// ape coin collection + + //ape coin + cApe, only valid on ETH mainnet + function cApeCollection() external view returns (address[] memory) { + if (cApe == address(0)) { + address[] memory list = new address[](0); + return list; + } else { + address[] memory list = new address[](2); + list[0] = cApe; + list[1] = address(ICApe(cApe).apeCoin()); + return list; + } + } + + function depositCApeCollection(address asset, uint256 amount) external { require(amount > 0, Errors.INVALID_PARAMETER); EarlyAccessStorage storage ds = earlyAccessStorage(); - AssetInfo storage assetInfo = ds.assetInfo[asset]; - require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); + require(ds.assetStatus[asset], Errors.NOT_IN_ACCESS_LIST); + CollectionInfo storage collectionInfo = ds.collectionInfo[asset]; require( - assetInfo.strategyType == StrategyType.CAPE, + collectionInfo.strategyType == StrategyType.CAPE, Errors.STRATEGY_NOT_MATCH ); - uint256 share = (amount * assetInfo.totalShare) / - _totalBalanceForCApe(); - assetInfo.totalShare += share.toUint184(); - ds.shareBalance[cApe][msg.sender] += share; + _updateCApeExchange(collectionInfo); + uint256 share = _updateUserShare(collectionInfo, msg.sender, amount); IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); - } - function capeStaking() external { - EarlyAccessStorage storage ds = earlyAccessStorage(); - require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); - uint256 balance = IERC20(apecoin).balanceOf(address(this)); - IERC20(apecoin).safeApprove(cApe, balance); - ICApe(cApe).deposit(address(this), balance); + emit Deposit(cApe, share, asset, amount); } - function _totalBalanceForCApe() internal view returns (uint256) { - return - IERC20(apecoin).balanceOf(address(this)) + + function _updateCApeExchange( + CollectionInfo storage collectionInfo + ) internal { + IERC20 apecoin = ICApe(cApe).apeCoin(); + uint256 totalBalance = apecoin.balanceOf(address(this)) + IERC20(cApe).balanceOf(address(this)); + uint256 exchangeRate = (totalBalance * 1e18) / + collectionInfo.totalShare; + if (exchangeRate == 0) { + exchangeRate = 1e18; + } + collectionInfo.exchangeRate = exchangeRate.toUint184(); } + /// normal ERC20 + function depositERC20(address asset, uint256 amount) external { require(amount > 0, Errors.INVALID_PARAMETER); EarlyAccessStorage storage ds = earlyAccessStorage(); - AssetInfo storage assetInfo = ds.assetInfo[asset]; - require(assetInfo.isAllow, Errors.NOT_IN_ACCESS_LIST); + require(ds.assetStatus[asset], Errors.NOT_IN_ACCESS_LIST); + CollectionInfo storage collectionInfo = ds.collectionInfo[asset]; require( - assetInfo.strategyType == StrategyType.NOYIELD, - Errors.STRATEGY_NOT_MATCH + collectionInfo.strategyType != StrategyType.NONE, + Errors.STRATEGY_NOT_SET ); - uint256 share = (amount * assetInfo.totalShare) / _totalBalance(asset); - assetInfo.totalShare += share.toUint184(); - ds.shareBalance[asset][msg.sender] += share; - IERC20(asset).safeTransferFrom(msg.sender, address(this), amount); + uint256 share = _updateUserShare(collectionInfo, msg.sender, amount); + + emit Deposit(asset, share, asset, amount); } - function _totalBalance(address asset) internal view returns (uint256) { - return IERC20(asset).balanceOf(address(this)); + /// ERC721 + + function depositERC721(address asset, uint32[] calldata tokenIds) external { + uint256 arrayLength = tokenIds.length; + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(ds.assetStatus[asset], Errors.NOT_IN_ACCESS_LIST); + CollectionInfo storage collectionInfo = ds.collectionInfo[asset]; + require( + collectionInfo.strategyType != StrategyType.NONE, + Errors.STRATEGY_NOT_SET + ); + + for (uint256 index = 0; index < arrayLength; index++) { + uint32 tokenId = tokenIds[index]; + IERC721(asset).safeTransferFrom(msg.sender, address(this), tokenId); + collectionInfo.erc721Owner[tokenId] = msg.sender; + } + collectionInfo.shareBalance[msg.sender] += arrayLength; + collectionInfo.totalShare += arrayLength.toUint184(); + + if (collectionInfo.strategyType == StrategyType.APESTAKING) { + IVaultApeStaking(address(this)).onboardCheckApeStakingPosition( + asset, + tokenIds, + msg.sender + ); + } } - function depositUSDT() external {} + function _updateUserShare( + CollectionInfo storage collectionInfo, + address user, + uint256 amount + ) internal returns (uint256) { + uint256 share = (amount * 1e18) / collectionInfo.exchangeRate; + collectionInfo.shareBalance[user] += share; + collectionInfo.totalShare += share; + return share; + } - function depositUSDC() external {} + function _totalBalanceWithAAVE( + address asset + ) internal view returns (uint256) { + address aToken = IAAVEPool(aavePool) + .getReserveData(asset) + .aTokenAddress; + if (aToken == address(0)) { + return IERC20(asset).balanceOf(address(this)); + } else { + return + IERC20(asset).balanceOf(address(this)) + + IERC20(aToken).balanceOf(address(this)); + } + } + + ///yield bot + + //eth -> weth + function depositWETH(uint256 amount) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + IWETH(weth).deposit{value: amount}(); + } + + // weth -> eth + function withdrawWETH(uint256 amount) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + IWETH(weth).withdraw(amount); + } + + // eth -> stETH + function depositLIDO(uint256 amount) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + ILido(stETH).submit{value: amount}(address(0)); + } + + // apecoin -> cApe + function depositCApe(uint256 amount) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + IERC20 apecoin = ICApe(cApe).apeCoin(); + apecoin.safeApprove(cApe, amount); + ICApe(cApe).deposit(address(this), amount); + } + + // cApe -> apecoin + function withdrawCApe(uint256 amount) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + ICApe(cApe).withdraw(amount); + } + + // token -> aToken + function depositAAVE(address asset, uint256 amount) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + //uint256 balance = IERC20(asset).balanceOf(address(this)); + IERC20(asset).safeApprove(aavePool, amount); + IAAVEPool(aavePool).supply(asset, amount, address(this), 0); + } + + // aToken -> token + function withdrawAAVE(address asset, uint256 amount) external { + EarlyAccessStorage storage ds = earlyAccessStorage(); + require(msg.sender == ds.yieldBot, Errors.NOT_STAKING_BOT); + // uint256 balance = IERC20(asset).balanceOf(address(this)); + IERC20(asset).safeApprove(aavePool, amount); + IAAVEPool(aavePool).withdraw(asset, amount, address(this)); + } - function crossChain(address asset) external { + function crossChain(address asset) external view { EarlyAccessStorage storage ds = earlyAccessStorage(); require(ds.bridge != address(0), Errors.NOT_ENABLE); diff --git a/contracts/dependencies/openzeppelin/contracts/EnumerableSet.sol b/contracts/dependencies/openzeppelin/contracts/EnumerableSet.sol index 7004733a1..4c701c26c 100644 --- a/contracts/dependencies/openzeppelin/contracts/EnumerableSet.sol +++ b/contracts/dependencies/openzeppelin/contracts/EnumerableSet.sol @@ -1,4 +1,6 @@ // SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol) +// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. pragma solidity ^0.8.0; @@ -25,6 +27,16 @@ pragma solidity ^0.8.0; * * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) * and `uint256` (`UintSet`) are supported. + * + * [WARNING] + * ==== + * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure + * unusable. + * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. + * + * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an + * array of EnumerableSet. + * ==== */ library EnumerableSet { // To implement this library for multiple types with as little code @@ -82,12 +94,12 @@ library EnumerableSet { uint256 lastIndex = set._values.length - 1; if (lastIndex != toDeleteIndex) { - bytes32 lastvalue = set._values[lastIndex]; + bytes32 lastValue = set._values[lastIndex]; // Move the last value to the index where the value to delete is - set._values[toDeleteIndex] = lastvalue; + set._values[toDeleteIndex] = lastValue; // Update the index for the moved value - set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex + set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex } // Delete the slot where the moved value was stored @@ -105,11 +117,7 @@ library EnumerableSet { /** * @dev Returns true if the value is in the set. O(1). */ - function _contains(Set storage set, bytes32 value) - private - view - returns (bool) - { + function _contains(Set storage set, bytes32 value) private view returns (bool) { return set._indexes[value] != 0; } @@ -130,14 +138,22 @@ library EnumerableSet { * * - `index` must be strictly less than {length}. */ - function _at(Set storage set, uint256 index) - private - view - returns (bytes32) - { + function _at(Set storage set, uint256 index) private view returns (bytes32) { return set._values[index]; } + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function _values(Set storage set) private view returns (bytes32[] memory) { + return set._values; + } + // Bytes32Set struct Bytes32Set { @@ -150,10 +166,7 @@ library EnumerableSet { * Returns true if the value was added to the set, that is if it was not * already present. */ - function add(Bytes32Set storage set, bytes32 value) - internal - returns (bool) - { + function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _add(set._inner, value); } @@ -163,21 +176,14 @@ library EnumerableSet { * Returns true if the value was removed from the set, that is if it was * present. */ - function remove(Bytes32Set storage set, bytes32 value) - internal - returns (bool) - { + function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _remove(set._inner, value); } /** * @dev Returns true if the value is in the set. O(1). */ - function contains(Bytes32Set storage set, bytes32 value) - internal - view - returns (bool) - { + function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { return _contains(set._inner, value); } @@ -198,14 +204,30 @@ library EnumerableSet { * * - `index` must be strictly less than {length}. */ - function at(Bytes32Set storage set, uint256 index) - internal - view - returns (bytes32) - { + function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { return _at(set._inner, index); } + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { + bytes32[] memory store = _values(set._inner); + bytes32[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + // AddressSet struct AddressSet { @@ -218,10 +240,7 @@ library EnumerableSet { * Returns true if the value was added to the set, that is if it was not * already present. */ - function add(AddressSet storage set, address value) - internal - returns (bool) - { + function add(AddressSet storage set, address value) internal returns (bool) { return _add(set._inner, bytes32(uint256(uint160(value)))); } @@ -231,21 +250,14 @@ library EnumerableSet { * Returns true if the value was removed from the set, that is if it was * present. */ - function remove(AddressSet storage set, address value) - internal - returns (bool) - { + function remove(AddressSet storage set, address value) internal returns (bool) { return _remove(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns true if the value is in the set. O(1). */ - function contains(AddressSet storage set, address value) - internal - view - returns (bool) - { + function contains(AddressSet storage set, address value) internal view returns (bool) { return _contains(set._inner, bytes32(uint256(uint160(value)))); } @@ -266,14 +278,30 @@ library EnumerableSet { * * - `index` must be strictly less than {length}. */ - function at(AddressSet storage set, uint256 index) - internal - view - returns (address) - { + function at(AddressSet storage set, uint256 index) internal view returns (address) { return address(uint160(uint256(_at(set._inner, index)))); } + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(AddressSet storage set) internal view returns (address[] memory) { + bytes32[] memory store = _values(set._inner); + address[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } + // UintSet struct UintSet { @@ -296,26 +324,19 @@ library EnumerableSet { * Returns true if the value was removed from the set, that is if it was * present. */ - function remove(UintSet storage set, uint256 value) - internal - returns (bool) - { + function remove(UintSet storage set, uint256 value) internal returns (bool) { return _remove(set._inner, bytes32(value)); } /** * @dev Returns true if the value is in the set. O(1). */ - function contains(UintSet storage set, uint256 value) - internal - view - returns (bool) - { + function contains(UintSet storage set, uint256 value) internal view returns (bool) { return _contains(set._inner, bytes32(value)); } /** - * @dev Returns the number of values on the set. O(1). + * @dev Returns the number of values in the set. O(1). */ function length(UintSet storage set) internal view returns (uint256) { return _length(set._inner); @@ -331,11 +352,27 @@ library EnumerableSet { * * - `index` must be strictly less than {length}. */ - function at(UintSet storage set, uint256 index) - internal - view - returns (uint256) - { + function at(UintSet storage set, uint256 index) internal view returns (uint256) { return uint256(_at(set._inner, index)); } + + /** + * @dev Return the entire set in an array + * + * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed + * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that + * this function has an unbounded cost, and using it as part of a state-changing function may render the function + * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + */ + function values(UintSet storage set) internal view returns (uint256[] memory) { + bytes32[] memory store = _values(set._inner); + uint256[] memory result; + + /// @solidity memory-safe-assembly + assembly { + result := store + } + + return result; + } } diff --git a/contracts/interfaces/IAAVEPool.sol b/contracts/interfaces/IAAVEPool.sol index 6dfcfd90c..a57cb4852 100644 --- a/contracts/interfaces/IAAVEPool.sol +++ b/contracts/interfaces/IAAVEPool.sol @@ -59,7 +59,9 @@ interface IAAVEPool { uint256 data; } - function getReserveData(address asset) external view returns (ReserveData memory); + function getReserveData( + address asset + ) external view returns (ReserveData memory); function supply( address asset, diff --git a/contracts/interfaces/ICApe.sol b/contracts/interfaces/ICApe.sol index 3bc0f5f72..91fa61621 100644 --- a/contracts/interfaces/ICApe.sol +++ b/contracts/interfaces/ICApe.sol @@ -84,5 +84,5 @@ interface ICApe is IERC20 { **/ function harvestAndCompound() external; - function apeCoin() external view returns(IERC20); + function apeCoin() external view returns (IERC20); } diff --git a/contracts/interfaces/IcbETH.sol b/contracts/interfaces/IcbETH.sol new file mode 100644 index 000000000..8d2412fe7 --- /dev/null +++ b/contracts/interfaces/IcbETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.0; + +import {IERC20Detailed} from "../dependencies/openzeppelin/contracts/IERC20Detailed.sol"; + +interface IcbETH is IERC20Detailed { + function exchangeRate() external view returns (uint256 _exchangeRate); +} diff --git a/contracts/interfaces/IrETH.sol b/contracts/interfaces/IrETH.sol new file mode 100644 index 000000000..4a239d9f6 --- /dev/null +++ b/contracts/interfaces/IrETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.0; + +import {IERC20Detailed} from "../dependencies/openzeppelin/contracts/IERC20Detailed.sol"; + +interface IrETH is IERC20Detailed { + function getExchangeRate() external view returns (uint256); +} diff --git a/contracts/interfaces/IwstETH.sol b/contracts/interfaces/IwstETH.sol new file mode 100644 index 000000000..66c1a717c --- /dev/null +++ b/contracts/interfaces/IwstETH.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.0; + +import {IERC20Detailed} from "../dependencies/openzeppelin/contracts/IERC20Detailed.sol"; + +interface IwstETH is IERC20Detailed { + function stETH() external view returns (address); + + function stEthPerToken() external view returns (uint256); +} diff --git a/contracts/protocol/libraries/helpers/Errors.sol b/contracts/protocol/libraries/helpers/Errors.sol index cdae0b753..f2c1e2ec2 100644 --- a/contracts/protocol/libraries/helpers/Errors.sol +++ b/contracts/protocol/libraries/helpers/Errors.sol @@ -147,8 +147,11 @@ library Errors { string public constant ALREADY_STAKING = "208"; //already staking string public constant INVALID_STATUS = "209"; //invalid status string public constant NOT_IN_ACCESS_LIST = "210"; //not in access list - string public constant STRATEGY_NOT_MATCH = "211"; //strategy not match - string public constant ASSET_STRATEGY_ALREADY_SET = "212"; //asset strategy already set - string public constant NOT_ENABLE = "213"; //cross chain not enable - string public constant NOT_STAKING_BOT = "214"; //not staking bot + string public constant NOT_IN_COLLECTION_LIST = "211"; //not in access list + string public constant ALREADY_IN_COLLECTION_LIST = "212"; //already in access list + string public constant STRATEGY_NOT_SET = "213"; //strategy not set + string public constant STRATEGY_NOT_MATCH = "214"; //strategy not match + string public constant ASSET_STRATEGY_ALREADY_SET = "215"; //asset strategy already set + string public constant NOT_ENABLE = "216"; //cross chain not enable + string public constant NOT_STAKING_BOT = "217"; //not staking bot } diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 7e3aac6a0..64177e738 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -80,7 +80,6 @@ import { NTokenMoonBirds, NTokenStakefish, NTokenUniswapV3, - P2PPairStaking, ParaProxy, ParaProxy__factory, ParaProxyInterfaces, @@ -141,8 +140,8 @@ import { VaultApeStaking__factory, VaultCommon, VaultCommon__factory, - VaultTemplate, - VaultTemplate__factory, + VaultEarlyAccess, + VaultEarlyAccess__factory, WalletBalanceProvider, WETH9Mocked, WETHGateway, @@ -161,9 +160,7 @@ import { getContractFactory, getFirstSigner, getInitializableAdminUpgradeabilityProxy, - getP2PPairStaking, getPoolProxy, - getProtocolDataProvider, getPunks, getTimeLockProxy, getUniswapV3SwapRouter, @@ -341,7 +338,9 @@ export const getVaultSignatures = () => { VaultApeStaking__factory.abi ); - const templateSelectors = getFunctionSignatures(VaultTemplate__factory.abi); + const earlyAccessSelectors = getFunctionSignatures( + VaultEarlyAccess__factory.abi + ); const paraProxyInterfacesSelectors = getFunctionSignatures( ParaProxyInterfaces__factory.abi @@ -351,7 +350,7 @@ export const getVaultSignatures = () => { const vaultSelectors = [ ...commonSelectors, ...apeStakingSelectors, - ...templateSelectors, + ...earlyAccessSelectors, ...paraProxyInterfacesSelectors, ]; for (const selector of vaultSelectors) { @@ -369,7 +368,7 @@ export const getVaultSignatures = () => { return { commonSelectors, apeStakingSelectors, - templateSelectors, + earlyAccessSelectors, paraProxyInterfacesSelectors, }; }; @@ -412,10 +411,11 @@ export const deployVaultApeStaking = async (verify?: boolean) => { export const deployVaultCommon = async (verify?: boolean) => { const {commonSelectors} = getVaultSignatures(); + const aclManager = await getACLManager(); const vaultCommon = (await withSaveAndVerify( await getContractFactory("VaultCommon"), eContractid.VaultCommon, - [], + [aclManager.address], verify, false )) as VaultCommon; @@ -426,20 +426,36 @@ export const deployVaultCommon = async (verify?: boolean) => { }; }; -export const deployVaultTemplate = async (verify?: boolean) => { - const {templateSelectors} = getVaultSignatures(); +export const deployVaultEarlyAccess = async (verify?: boolean) => { + const {earlyAccessSelectors} = getVaultSignatures(); - const vaultTemplate = (await withSaveAndVerify( - await getContractFactory("VaultTemplate"), - eContractid.VaultTemplate, - [], + const weth = await getWETH(); + const wstETH = zeroAddress(); + const cbETH = zeroAddress(); + const rETH = zeroAddress(); + const cApe = await getAutoCompoundApe(); + const aavePool = zeroAddress(); + const aclManager = await getACLManager(); + + const vaultEarlyAccess = (await withSaveAndVerify( + await getContractFactory("VaultEarlyAccess"), + eContractid.VaultEarlyAccess, + [ + weth.address, + wstETH, + cbETH, + rETH, + cApe.address, + aavePool, + aclManager.address, + ], verify, false - )) as VaultTemplate; + )) as VaultEarlyAccess; return { - vaultTemplate, - vaultTemplateSelectors: templateSelectors.map((s) => s.signature), + vaultEarlyAccess, + earlyAccessSelectors: earlyAccessSelectors.map((s) => s.signature), }; }; @@ -489,7 +505,7 @@ export const deployVault = async (verify?: boolean) => { verify ); - const {vaultTemplate, vaultTemplateSelectors} = await deployVaultTemplate( + const {vaultEarlyAccess, earlyAccessSelectors} = await deployVaultEarlyAccess( verify ); @@ -509,9 +525,9 @@ export const deployVault = async (verify?: boolean) => { functionSelectors: apeStakingSelectors, }, { - implAddress: vaultTemplate.address, + implAddress: vaultEarlyAccess.address, action: 0, - functionSelectors: vaultTemplateSelectors, + functionSelectors: earlyAccessSelectors, }, { implAddress: vaultParaProxyInterfaces.address, @@ -2587,65 +2603,6 @@ export const deployAutoCompoundApeImplAndAssignItToProxy = async ( ); }; -export const deployP2PPairStakingImpl = async (verify?: boolean) => { - const allTokens = await getAllTokens(); - const protocolDataProvider = await getProtocolDataProvider(); - const nBAYC = ( - await protocolDataProvider.getReserveTokensAddresses(allTokens.BAYC.address) - ).xTokenAddress; - const nMAYC = ( - await protocolDataProvider.getReserveTokensAddresses(allTokens.MAYC.address) - ).xTokenAddress; - const nBAKC = ( - await protocolDataProvider.getReserveTokensAddresses(allTokens.BAKC.address) - ).xTokenAddress; - const apeCoinStaking = - (await getContractAddressInDb(eContractid.ApeCoinStaking)) || - (await deployApeCoinStaking(verify)).address; - const args = [ - allTokens.BAYC.address, - allTokens.MAYC.address, - allTokens.BAKC.address, - nBAYC, - nMAYC, - nBAKC, - allTokens.APE.address, - allTokens.cAPE.address, - apeCoinStaking, - ]; - - return withSaveAndVerify( - await getContractFactory("P2PPairStaking"), - eContractid.P2PPairStakingImpl, - [...args], - verify - ) as Promise; -}; - -export const deployP2PPairStaking = async (verify?: boolean) => { - const p2pImplementation = await deployP2PPairStakingImpl(verify); - - const deployer = await getFirstSigner(); - const deployerAddress = await deployer.getAddress(); - - const initData = p2pImplementation.interface.encodeFunctionData("initialize"); - - const proxyInstance = await withSaveAndVerify( - await getContractFactory("InitializableAdminUpgradeabilityProxy"), - eContractid.P2PPairStaking, - [], - verify - ); - - await waitForTx( - await (proxyInstance as InitializableAdminUpgradeabilityProxy)[ - "initialize(address,address,bytes)" - ](p2pImplementation.address, deployerAddress, initData, GLOBAL_OVERRIDES) - ); - - return await getP2PPairStaking(proxyInstance.address); -}; - export const deployAutoYieldApeImpl = async (verify?: boolean) => { const allTokens = await getAllTokens(); const apeCoinStaking = diff --git a/helpers/contracts-getters.ts b/helpers/contracts-getters.ts index 053106135..ef0df4b2b 100644 --- a/helpers/contracts-getters.ts +++ b/helpers/contracts-getters.ts @@ -71,16 +71,13 @@ import { AutoCompoundApe__factory, InitializableAdminUpgradeabilityProxy__factory, StETHDebtToken__factory, - ApeStakingLogic__factory, MintableERC721Logic__factory, - P2PPairStaking__factory, ExecutorWithTimelock__factory, MultiSendCallOnly__factory, WstETHMocked__factory, BAYCSewerPass__factory, AutoYieldApe__factory, PYieldToken__factory, - HelperContract__factory, MockCToken__factory, TimeLock__factory, HotWalletProxy__factory, @@ -1007,17 +1004,6 @@ export const getApeCoinStaking = async (address?: tEthereumAddress) => await getFirstSigner() ); -export const getApeStakingLogic = async (address?: tEthereumAddress) => - await ApeStakingLogic__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.ApeStakingLogic}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - export const getMintableERC721Logic = async (address?: tEthereumAddress) => await MintableERC721Logic__factory.connect( address || @@ -1080,28 +1066,6 @@ export const getAutoYieldApe = async (address?: tEthereumAddress) => await getFirstSigner() ); -export const getP2PPairStaking = async (address?: tEthereumAddress) => - await P2PPairStaking__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.P2PPairStaking}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - -export const getHelperContract = async (address?: tEthereumAddress) => - await HelperContract__factory.connect( - address || - ( - await getDb() - .get(`${eContractid.HelperContract}.${DRE.network.name}`) - .value() - ).address, - await getFirstSigner() - ); - export const getInitializableAdminUpgradeabilityProxy = async ( address: tEthereumAddress ) => diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index 6be82322a..65e29a17d 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -80,7 +80,6 @@ import { Seaport, Seaport__factory, TimeLock__factory, - P2PPairStaking__factory, NFTFloorOracle__factory, } from "../types"; import { @@ -1082,7 +1081,6 @@ export const decodeInputData = (data: string) => { ...InitializableAdminUpgradeabilityProxy__factory.abi, ...ICurve__factory.abi, ...TimeLock__factory.abi, - ...P2PPairStaking__factory.abi, ...NFTFloorOracle__factory.abi, ]; diff --git a/helpers/types.ts b/helpers/types.ts index f9302d3e6..da7b03660 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -240,6 +240,7 @@ export enum eContractid { VaultCommon = "VaultCommon", VaultApeStaking = "VaultApeStaking", VaultTemplate = "VaultTemplate", + VaultEarlyAccess = "VaultEarlyAccess", VaultProxyInterfacesImpl = "VaultProxyInterfacesImpl", ApeCoinStaking = "ApeCoinStaking", ATokenDebtToken = "ATokenDebtToken", diff --git a/scripts/upgrade/P2PPairStaking.ts b/scripts/upgrade/P2PPairStaking.ts deleted file mode 100644 index 042a37ff9..000000000 --- a/scripts/upgrade/P2PPairStaking.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {deployP2PPairStakingImpl} from "../../helpers/contracts-deployments"; -import { - getInitializableAdminUpgradeabilityProxy, - getP2PPairStaking, -} from "../../helpers/contracts-getters"; -import {dryRunEncodedData} from "../../helpers/contracts-helpers"; -import {DRY_RUN, GLOBAL_OVERRIDES} from "../../helpers/hardhat-constants"; -import {waitForTx} from "../../helpers/misc-utils"; - -export const upgradeP2PPairStaking = async (verify = false) => { - console.time("deploy P2PPairStaking"); - const p2pPairStakingImpl = await deployP2PPairStakingImpl(verify); - const p2pPairStaking = await getP2PPairStaking(); - const p2pPairStakingProxy = await getInitializableAdminUpgradeabilityProxy( - p2pPairStaking.address - ); - console.timeEnd("deploy P2PPairStaking"); - - console.time("upgrade P2PPairStaking"); - if (DRY_RUN) { - const encodedData = p2pPairStakingProxy.interface.encodeFunctionData( - "upgradeTo", - [p2pPairStakingImpl.address] - ); - await dryRunEncodedData(p2pPairStakingProxy.address, encodedData); - } else { - await waitForTx( - await p2pPairStakingProxy.upgradeTo( - p2pPairStakingImpl.address, - GLOBAL_OVERRIDES - ) - ); - } - console.timeEnd("upgrade P2PPairStaking"); -}; diff --git a/test/vault_ape_staking.spec.ts b/test/vault_ape_staking.spec.ts index 39c7782da..711e29bea 100644 --- a/test/vault_ape_staking.spec.ts +++ b/test/vault_ape_staking.spec.ts @@ -21,6 +21,9 @@ describe("Vault Ape staking Test", () => { const fixture = async () => { testEnv = await loadFixture(testEnvFixture); const { + bayc, + mayc, + bakc, ape, users: [user1, , , user4, , user6], apeCoinStaking, @@ -34,6 +37,24 @@ describe("Vault Ape staking Test", () => { await vaultProxy.connect(poolAdmin.signer).setApeStakingBot(user4.address) ); + await waitForTx( + await vaultProxy + .connect(poolAdmin.signer) + .updateAccessListStatus( + [bayc.address, mayc.address, bakc.address], + [true, true, true] + ) + ); + + await waitForTx( + await vaultProxy + .connect(poolAdmin.signer) + .setCollectionStrategy( + [bayc.address, mayc.address, bakc.address], + [3, 3, 3] + ) + ); + cApe = await getAutoCompoundApe(); MINIMUM_LIQUIDITY = await cApe.MINIMUM_LIQUIDITY(); @@ -124,17 +145,17 @@ describe("Vault Ape staking Test", () => { await waitForTx( await vaultProxy .connect(user1.signer) - .onboardNFTs(bayc.address, [0, 1, 2]) + .depositERC721(bayc.address, [0, 1, 2]) ); await waitForTx( await vaultProxy .connect(user2.signer) - .onboardNFTs(mayc.address, [0, 1, 2]) + .depositERC721(mayc.address, [0, 1, 2]) ); await waitForTx( await vaultProxy .connect(user3.signer) - .onboardNFTs(bakc.address, [0, 1, 2]) + .depositERC721(bakc.address, [0, 1, 2]) ); await waitForTx( await vaultProxy.connect(user4.signer).stakingApe(true, [0, 1, 2]) @@ -269,28 +290,28 @@ describe("Vault Ape staking Test", () => { await waitForTx( await vaultProxy - .connect(user1.signer) - .offboardNFTs(bayc.address, [0, 1, 2]) + .connect(user4.signer) + .offboardCheckApeStakingPosition(bayc.address, [0, 1, 2]) ); await waitForTx( await vaultProxy - .connect(user2.signer) - .offboardNFTs(mayc.address, [0, 1, 2]) + .connect(user4.signer) + .offboardCheckApeStakingPosition(mayc.address, [0, 1, 2]) ); await waitForTx( await vaultProxy - .connect(user3.signer) - .offboardNFTs(bakc.address, [0, 1, 2]) - ); - expect(await bayc.ownerOf(0)).to.be.equal(user1.address); - expect(await bayc.ownerOf(1)).to.be.equal(user1.address); - expect(await bayc.ownerOf(2)).to.be.equal(user1.address); - expect(await mayc.ownerOf(0)).to.be.equal(user2.address); - expect(await mayc.ownerOf(1)).to.be.equal(user2.address); - expect(await mayc.ownerOf(2)).to.be.equal(user2.address); - expect(await bakc.ownerOf(0)).to.be.equal(user3.address); - expect(await bakc.ownerOf(1)).to.be.equal(user3.address); - expect(await bakc.ownerOf(2)).to.be.equal(user3.address); + .connect(user4.signer) + .offboardCheckApeStakingPosition(bakc.address, [0, 1, 2]) + ); + // expect(await bayc.ownerOf(0)).to.be.equal(user1.address); + // expect(await bayc.ownerOf(1)).to.be.equal(user1.address); + // expect(await bayc.ownerOf(2)).to.be.equal(user1.address); + // expect(await mayc.ownerOf(0)).to.be.equal(user2.address); + // expect(await mayc.ownerOf(1)).to.be.equal(user2.address); + // expect(await mayc.ownerOf(2)).to.be.equal(user2.address); + // expect(await bakc.ownerOf(0)).to.be.equal(user3.address); + // expect(await bakc.ownerOf(1)).to.be.equal(user3.address); + // expect(await bakc.ownerOf(2)).to.be.equal(user3.address); //1080 + 1080 compoundFee = await vaultProxy.compoundFee(); @@ -373,7 +394,7 @@ describe("Vault Ape staking Test", () => { .setApprovalForAll(vaultProxy.address, true) ); await expect( - vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0]) + vaultProxy.connect(user1.signer).depositERC721(bayc.address, [0]) ).to.be.revertedWith(ProtocolErrors.ALREADY_STAKING); await apeCoinStaking.connect(user1.signer).depositBAKC( @@ -387,7 +408,7 @@ describe("Vault Ape staking Test", () => { [] ); await expect( - vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [1]) + vaultProxy.connect(user1.signer).depositERC721(bayc.address, [1]) ).to.be.revertedWith(ProtocolErrors.ALREADY_STAKING); }); @@ -409,7 +430,7 @@ describe("Vault Ape staking Test", () => { ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( - await vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0]) + await vaultProxy.connect(user1.signer).depositERC721(bayc.address, [0]) ); await waitForTx( @@ -464,15 +485,15 @@ describe("Vault Ape staking Test", () => { ).to.be.revertedWith(ProtocolErrors.NFT_NOT_IN_POOL); await waitForTx( - await vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0]) + await vaultProxy.connect(user1.signer).depositERC721(bayc.address, [0]) ); await waitForTx( - await vaultProxy.connect(user1.signer).onboardNFTs(mayc.address, [0]) + await vaultProxy.connect(user1.signer).depositERC721(mayc.address, [0]) ); await waitForTx( - await vaultProxy.connect(user1.signer).onboardNFTs(bakc.address, [0, 1]) + await vaultProxy.connect(user1.signer).depositERC721(bakc.address, [0, 1]) ); await waitForTx( @@ -518,7 +539,7 @@ describe("Vault Ape staking Test", () => { ); await waitForTx( - await vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0, 1]) + await vaultProxy.connect(user1.signer).depositERC721(bayc.address, [0, 1]) ); await waitForTx( @@ -559,12 +580,12 @@ describe("Vault Ape staking Test", () => { await waitForTx( await vaultProxy .connect(user1.signer) - .onboardNFTs(bayc.address, [0, 1, 2]) + .depositERC721(bayc.address, [0, 1, 2]) ); await waitForTx( await vaultProxy .connect(user1.signer) - .onboardNFTs(bakc.address, [0, 1, 2]) + .depositERC721(bakc.address, [0, 1, 2]) ); await waitForTx( @@ -627,10 +648,10 @@ describe("Vault Ape staking Test", () => { ); await waitForTx( - await vaultProxy.connect(user1.signer).onboardNFTs(bayc.address, [0, 1]) + await vaultProxy.connect(user1.signer).depositERC721(bayc.address, [0, 1]) ); await waitForTx( - await vaultProxy.connect(user1.signer).onboardNFTs(bakc.address, [0, 1]) + await vaultProxy.connect(user1.signer).depositERC721(bakc.address, [0, 1]) ); await waitForTx( @@ -728,15 +749,15 @@ describe("Vault Ape staking Test", () => { .setApprovalForAll(vaultProxy.address, true) ); - let tx0 = vaultProxy.interface.encodeFunctionData("onboardNFTs", [ + let tx0 = vaultProxy.interface.encodeFunctionData("depositERC721", [ bayc.address, [0, 1, 2], ]); - let tx1 = vaultProxy.interface.encodeFunctionData("onboardNFTs", [ + let tx1 = vaultProxy.interface.encodeFunctionData("depositERC721", [ mayc.address, [0, 1, 2], ]); - let tx2 = vaultProxy.interface.encodeFunctionData("onboardNFTs", [ + let tx2 = vaultProxy.interface.encodeFunctionData("depositERC721", [ bakc.address, [0, 1, 2], ]);