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], ]);