diff --git a/contracts/Aelin.sol b/contracts/Aelin.sol index aa7bfdfa..f34e4857 100644 --- a/contracts/Aelin.sol +++ b/contracts/Aelin.sol @@ -8,7 +8,9 @@ error UnauthorizedMinter(); error InvalidAddress(); contract Aelin is ERC20, Ownable { - // Initial supply set to 10M tokens as stated in AELIP-50 + /** + * NOTE Set to 10M tokens as stated in AELIP-50. + */ uint256 public constant INITIAL_SUPPLY = 10 * 1e6 * 1e18; address public authorizedMinter; @@ -17,15 +19,28 @@ contract Aelin is ERC20, Ownable { _mint(_initialHolder, INITIAL_SUPPLY); } + /** + * @notice The mint function for the Aelin token. Only the authorized minted can mint new tokens. + * @param _receiver The recipient of the new tokens. + * @param _amount The amount of tokens that will be recieved. + */ function mint(address _receiver, uint256 _amount) external { if (msg.sender != authorizedMinter) revert UnauthorizedMinter(); _mint(_receiver, _amount); } + /** + * @notice The burn function for the Aelin token. + * @param _amount The amount to be burned. + */ function burn(uint256 _amount) external { _burn(msg.sender, _amount); } + /** + * @notice This functions sets the authorized minter for the Aelin token. + * @param _minter The new authorized minter. + */ function setAuthorizedMinter(address _minter) external onlyOwner { if (_minter == address(0)) revert InvalidAddress(); authorizedMinter = _minter; diff --git a/contracts/AelinDeal.sol b/contracts/AelinDeal.sol index f0563e0e..da0cdacc 100644 --- a/contracts/AelinDeal.sol +++ b/contracts/AelinDeal.sol @@ -38,8 +38,7 @@ contract AelinDeal is AelinVestingToken, MinimalProxyFactory, IAelinDeal { Timeline public proRataRedemption; /** - * @dev the initialize method replaces the constructor setup and can only be called once - * NOTE the deal tokens wrapping the underlying are always 18 decimals + * NOTE The deal tokens wrapping the underlying are always 18 decimals. */ function initialize( string calldata _poolName, @@ -66,22 +65,23 @@ contract AelinDeal is AelinVestingToken, MinimalProxyFactory, IAelinDeal { depositComplete = false; - /** - * calculates the amount of underlying deal tokens you get per wrapped deal token accepted - */ + // Calculates the amount of underlying deal tokens you get per wrapped deal token accepted underlyingPerDealExchangeRate = (_dealData.underlyingDealTokenTotal * 1e18) / maxTotalSupply; emit HolderAccepted(_dealData.holder); } /** - * @dev the holder finalizes the deal for the pool created by the - * sponsor by depositing funds using this method. - * - * NOTE if the deposit was completed with a transfer instead of this method - * the deposit still needs to be finalized by calling this method with - * _underlyingDealTokenAmount set to 0 + * @notice This function allows the holder to deposit any amount of underlying deal tokens to a pool. + * If the cumulative deposited amount is greater than the underlyingDealTokenTotal the deal is finalized. + * @param _underlyingDealTokenAmount The amount of underlying deal tokens deposited. + * @return bool Returns true if the cumulative deposited amount is greater than the underlyingDealTokenTotal, + * meaning that the deal is full funded and finalized. Returns false otherwise. + * NOTE If the deposit was completed with a transfer instead of this method the deposit still needs to + * be finalized by calling this method with _underlyingDealTokenAmount set to 0. */ - function depositUnderlying(uint256 _underlyingDealTokenAmount) external finalizeDeposit nonReentrant returns (bool) { + function depositUnderlying( + uint256 _underlyingDealTokenAmount + ) external finalizeDeposit onlyHolder nonReentrant returns (bool) { if (_underlyingDealTokenAmount > 0) { uint256 currentBalance = IERC20(underlyingDealToken).balanceOf(address(this)); IERC20(underlyingDealToken).safeTransferFrom(msg.sender, address(this), _underlyingDealTokenAmount); @@ -120,8 +120,9 @@ contract AelinDeal is AelinVestingToken, MinimalProxyFactory, IAelinDeal { } /** - * @dev the holder can withdraw any amount accidentally deposited over - * the amount needed to fulfill the deal or all amount if deposit was not completed + * @notice This function allows the holder to withdraw any amount of underlying tokens accidently + * deposited over the amount needed to fulfill the deal, or all of the amount deposited if the deal + * was not completed. */ function withdraw() external onlyHolder { uint256 withdrawAmount; @@ -137,11 +138,9 @@ contract AelinDeal is AelinVestingToken, MinimalProxyFactory, IAelinDeal { } /** - * @dev after the redemption period has ended the holder can withdraw - * the excess funds remaining from purchasers who did not accept the deal - * - * Requirements: - * - both the pro rata and open redemption windows are no longer active + * @notice This function allows the holder to withdraw any excess underlying deal tokens after + * the the redemption period has ended. + * NOTE Both the pro rata and open redemption windows must no longer be active. */ function withdrawExpiry() external onlyHolder { require(proRataRedemption.expiry > 0, "redemption period not started"); @@ -158,9 +157,11 @@ contract AelinDeal is AelinVestingToken, MinimalProxyFactory, IAelinDeal { } /** - * @dev a view showing the number of claimable underlying deal + * @notice This view function returns the number of claimable underlying deal tokens given the token id + * of a user's vesting token. + * @param _tokenId The token id of a user's vesting token. + * @return uint256 The number of underlying deal tokens a user can claim. */ - function claimableUnderlyingTokens(uint256 _tokenId) public view returns (uint256) { VestingDetails memory schedule = vestingDetails[_tokenId]; uint256 precisionAdjustedUnderlyingClaimable; @@ -178,8 +179,11 @@ contract AelinDeal is AelinVestingToken, MinimalProxyFactory, IAelinDeal { : (schedule.share * (maxTime - minTime)) / vestingPeriod; uint256 underlyingClaimable = (underlyingPerDealExchangeRate * claimableAmount) / 1e18; - // This could potentially be the case where the last user claims a slightly smaller amount if there is some precision loss - // although it will generally never happen as solidity rounds down so there should always be a little bit left + /** + * @dev There could potentially be the case where the last user claims a slightly smaller amount + * if there is some precision loss, although it will generally never happen as solidity rounds + * down so there should always be a little bit left. + */ precisionAdjustedUnderlyingClaimable = underlyingClaimable > IERC20(underlyingDealToken).balanceOf(address(this)) ? IERC20(underlyingDealToken).balanceOf(address(this)) @@ -189,6 +193,14 @@ contract AelinDeal is AelinVestingToken, MinimalProxyFactory, IAelinDeal { return precisionAdjustedUnderlyingClaimable; } + /** + * @notice This function allows a user to claim their underlying deal tokens, or a partial amount of their + * underlying tokens, from multiple vesting tokens once they have vested according to the schedule created + * by the sponsor. + * @param _indices The token ids of a user's vesting tokens. + * @return uint256 The number of underlying deal tokens a user claimed. + * NOTE If the vesting of any token ids has been completed the corresponding vesting token will be burned. + */ function claimUnderlyingMultipleEntries(uint256[] memory _indices) external returns (uint256) { uint256 totalClaimed; for (uint256 i = 0; i < _indices.length; i++) { @@ -198,9 +210,11 @@ contract AelinDeal is AelinVestingToken, MinimalProxyFactory, IAelinDeal { } /** - * @dev allows a user to claim their underlying deal tokens or a partial amount - * of their underlying tokens once they have vested according to the schedule - * created by the sponsor + * @notice This function allows a user to claim their underlying deal tokens, or a partial amount of their + * underlying tokens, once they have vested according to the schedule created by the sponsor. + * @param _tokenId The token id of a user's vesting token. + * @return uint256 The number of underlying deal tokens a user claimed. + * NOTE If the vesting has been completed the vesting token will be burned. */ function claimUnderlyingTokens(uint256 _tokenId) external returns (uint256) { return _claimUnderlyingTokens(msg.sender, _tokenId); @@ -224,33 +238,47 @@ contract AelinDeal is AelinVestingToken, MinimalProxyFactory, IAelinDeal { } /** - * @dev allows the purchaser to mint deal tokens. this method is also used - * to send deal tokens to the sponsor. It may only be called from the pool - * contract that created this deal + * @notice This function allows the purchaser to mint deal tokens. It is also used to send deal tokens to + * the sponsor. + * @param _to The recipient of the vesting token. + * @param _amount The number of vesting tokens to be minted. + * NOTE It may only be called from the pool contract that created this deal, and only after the deposit has + * been completed. */ function mintVestingToken(address _to, uint256 _amount) external depositCompleted onlyPool { totalUnderlyingAccepted += _amount; - _mintVestingToken(_to, _amount, vestingCliffExpiry); + /** + * @dev Vesting index hard-coded to zero here. Multiple vesting schedules currently not supported + * for these deals. + */ + _mintVestingToken(_to, _amount, vestingCliffExpiry, 0); } /** - * @dev allows the protocol to handle protocol fees coming in deal tokens. - * It may only be called from the pool contract that created this deal + * @notice This function allows the protocol to handle protocol fees, with deal tokens. + * @param _dealTokenAmount The number of deal tokens reserved for protocol fees. + * NOTE It may only be called from the pool contract that created this deal, and only after the deposit has + * been completed. */ - function transferProtocolFee(uint256 dealTokenAmount) external depositCompleted onlyPool { - uint256 underlyingProtocolFees = (underlyingPerDealExchangeRate * dealTokenAmount) / 1e18; + function transferProtocolFee(uint256 _dealTokenAmount) external depositCompleted onlyPool { + uint256 underlyingProtocolFees = (underlyingPerDealExchangeRate * _dealTokenAmount) / 1e18; IERC20(underlyingDealToken).safeTransfer(address(aelinFeeEscrow), underlyingProtocolFees); } /** - * @dev the holder may change their address + * @notice This function allows the holder to set a future holder address without changing the + * holder address currently. + * @param _futureHolder The future holder address. */ - function setHolder(address _holder) external onlyHolder { - require(_holder != address(0), "holder cant be null"); - futureHolder = _holder; - emit HolderSet(_holder); + function setHolder(address _futureHolder) external onlyHolder { + require(_futureHolder != address(0), "holder cant be null"); + futureHolder = _futureHolder; + emit HolderSet(_futureHolder); } + /** + * @notice This function allows the future holder address to replace the current holder address. + */ function acceptHolder() external { require(msg.sender == futureHolder, "only future holder can access"); holder = futureHolder; diff --git a/contracts/AelinERC20.sol b/contracts/AelinERC20.sol index a35d5bb6..287a6eec 100644 --- a/contracts/AelinERC20.sol +++ b/contracts/AelinERC20.sol @@ -4,15 +4,15 @@ pragma solidity 0.8.19; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; /** - * @dev a standard ERC20 contract that is extended with a few methods - * described in detail below + * @dev A standard ERC20 contract that is extended with a few additional functions and variables. + * Due to the constructor being empty for the MinimalProxy architecture the customName, customSymbol, + * and customDecimals variables are set in the initializer. The MinimalProxy architecture also + * requires custom logic for the name(), symbol(), decimals(), and _setNameSymbolAndDecimals() + * functions. */ contract AelinERC20 is ERC20 { bool private setInfo; - /** - * @dev Due to the constructor being empty for the MinimalProxy architecture we need - * to set the name and symbol in the initializer which requires these custom variables - */ + string private customName; string private customSymbol; uint8 private customDecimals; @@ -27,18 +27,25 @@ contract AelinERC20 is ERC20 { } /** - * @dev Due to the constructor being empty for the MinimalProxy architecture we need - * to set the name, symbol, and decimals in the initializer which requires this - * custom logic for name(), symbol(), decimals(), and _setNameSymbolAndDecimals() + * @notice This view function returns the name of the token. + * @return string The name of the token. */ function name() public view virtual override returns (string memory) { return customName; } + /** + * @notice This view function returns the symbol of the token. + * @return string The symbol of the token. + */ function symbol() public view virtual override returns (string memory) { return customSymbol; } + /** + * @notice This view function returns the number of decimals the token has. + * @return uint8 The number of decimals the token has. + */ function decimals() public view virtual override returns (uint8) { return customDecimals; } diff --git a/contracts/AelinERC721.sol b/contracts/AelinERC721.sol index fcbc0652..755bdc7a 100644 --- a/contracts/AelinERC721.sol +++ b/contracts/AelinERC721.sol @@ -4,11 +4,13 @@ pragma solidity 0.8.19; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +/** + * @dev A standard ERC721 contract that is extended with a few additional functions and variables. + * Due to the constructor being empty for the MinimalProxy architecture the customName and customSymbol + * variables are set in the initializer. The MinimalProxy architecture also requires custom logic for + * the name(), symbol(), and _setNameAndSymbol() functions. + */ contract AelinERC721 is ERC721, ReentrancyGuard { - /** - * @dev Due to the constructor being empty for the MinimalProxy architecture we need - * to set the name and symbol in the initializer which requires these custom variables - */ string private customName; string private customSymbol; @@ -24,14 +26,17 @@ contract AelinERC721 is ERC721, ReentrancyGuard { } /** - * @dev Due to the constructor being empty for the MinimalProxy architecture we need - * to set the name and symbol, and decimals in the initializer which requires this - * custom logic for name(), symbol(), and _setNameAndSymbol() + * @notice This view function returns the name of the token. + * @return string The name of the token. */ function name() public view virtual override returns (string memory) { return customName; } + /** + * @notice This view function returns the symbol of the token. + * @return string The symbol of the token. + */ function symbol() public view virtual override returns (string memory) { return customSymbol; } diff --git a/contracts/AelinFeeEscrow.sol b/contracts/AelinFeeEscrow.sol index c48ce053..dcf9ceac 100644 --- a/contracts/AelinFeeEscrow.sol +++ b/contracts/AelinFeeEscrow.sol @@ -24,26 +24,35 @@ contract AelinFeeEscrow { } /** - * @dev the treasury may change their address + * @notice This function allows the treasury to set a future treasury address without changing the + * treasury address currently. + * @param _futureTreasury The future treasury address. */ function setTreasury(address _futureTreasury) external onlyTreasury { require(_futureTreasury != address(0), "cant pass null treasury address"); futureTreasury = _futureTreasury; } + /** + * @notice This function allows the future treasury address to replace the current treasury address. + */ function acceptTreasury() external { require(msg.sender == futureTreasury, "must be future treasury"); treasury = futureTreasury; emit SetTreasury(futureTreasury); } + /** + * @notice This function allows the treasury to further delay the vesting expiry of escrowed assets + * by the DELAY_PERIOD. + */ function delayEscrow() external onlyTreasury { vestingExpiry = block.timestamp + DELAY_PERIOD; emit DelayEscrow(vestingExpiry); } /** - * @dev transfer all the escrow tokens to the treasury + * @notice This function allows the treasury to transfer all of the escrow tokens to the treasury. */ function withdrawToken() external onlyTreasury { require(block.timestamp > vestingExpiry, "cannot access funds yet"); diff --git a/contracts/AelinPool.sol b/contracts/AelinPool.sol index 3001f32d..6f465385 100644 --- a/contracts/AelinPool.sol +++ b/contracts/AelinPool.sol @@ -50,13 +50,12 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua mapping(address => uint256) public amountWithdrawn; mapping(address => bool) public openPeriodEligible; mapping(address => uint256) public allowList; - // collectionAddress -> NftCollectionRules struct + // CollectionAddress -> NftCollectionRules struct mapping(address => NftCollectionRules) public nftCollectionDetails; /** - * @dev For 721, it is used for blacklisting the tokenId of a collection - * and for 1155, it is used for identifying the eligible tokenIds for - * participating in the pool + * @dev For 721, it is used for blacklisting the tokenId of a collection and for 1155, it is used + * for identifying the eligible tokenIds for participating in the pool. */ mapping(address => mapping(uint256 => bool)) public nftId; bool public hasNftList; @@ -66,12 +65,12 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua string private storedSymbol; /** - * @dev the initialize method replaces the constructor setup and can only be called once + * @dev The initialize method replaces the constructor setup and can only be called once. * * Requirements: - * - max 1 year duration - * - purchase expiry can be set from 30 minutes to 30 days - * - max sponsor fee is 15000 representing 15% + * - Max 1 year duration. + * - Purchase expiry can be set from 30 minutes to 30 days. + * - Max sponsor fee is 15000 representing 15%. */ function initialize( PoolData calldata _poolData, @@ -123,7 +122,7 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua NftCollectionRules[] calldata nftCollectionRules = _poolData.nftCollectionRules; if (nftCollectionRules.length > 0) { - // if the first address supports 721, the entire pool only supports 721 + // If the first address supports 721, the entire pool only supports 721 if (NftCheck.supports721(nftCollectionRules[0].collectionAddress)) { for (uint256 i; i < nftCollectionRules.length; ++i) { require(NftCheck.supports721(nftCollectionRules[i].collectionAddress), "can only contain 721"); @@ -143,7 +142,7 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } hasNftList = true; } - // if the first address supports 1155, the entire pool only supports 1155 + // If the first address supports 1155, the entire pool only supports 1155 else if (NftCheck.supports1155(nftCollectionRules[0].collectionAddress)) { for (uint256 i; i < nftCollectionRules.length; ++i) { require(NftCheck.supports1155(nftCollectionRules[i].collectionAddress), "can only contain 1155"); @@ -170,12 +169,10 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev allows anyone to become a purchaser by sending purchase tokens - * in exchange for pool tokens - * - * Requirements: - * - the deal is in the purchase expiry window - * - the cap has not been exceeded + * @notice This function allows anyone to become a purchaser by sending purchase tokens in exchange + * for pool tokens. + * @param _purchaseTokenAmount The amount of purchase tokens a user will send to the pool. + * NOTE The deal must be within the purchase expiry window, and the cap must not have been exceeded. */ function purchasePoolTokens(uint256 _purchaseTokenAmount) external nonReentrant { require(block.timestamp < purchaseExpiry, "not in purchase window"); @@ -201,14 +198,13 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev allows anyone to become a purchaser with a qualified erc721 - * nft in the pool depending on the scenarios - * - * Scenarios: - * 1. each wallet holding a qualified NFT to deposit an unlimited amount of purchase tokens - * 2. certain amount of Investment tokens per qualified NFT held + * @notice This function allows anyone to become a purchaser with a qualified ERC721 token in the + * pool depending on two scenarios. Either, each account holding a qualified NFT can deposit an + * unlimited amount of purchase tokens, or there exists a certain amount of investment tokens per + * qualified NFT held. + * @param _nftPurchaseList The list of NFTs used to qualify an account for purchasing pool tokens. + * @param _purchaseTokenAmount The amount of purchase tokens a user will send to the pool. */ - function purchasePoolTokensWithNft( NftPurchaseList[] calldata _nftPurchaseList, uint256 _purchaseTokenAmount @@ -225,10 +221,10 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua uint256 tokenIdsLength; NftCollectionRules memory nftCollectionRules; - //The running total for 721 tokens + // The running total for 721 tokens uint256 maxPurchaseTokenAmount; - //Iterate over the collections + // Iterate over the collections for (uint256 i; i < nftPurchaseListLength; ++i) { nftPurchaseList = _nftPurchaseList[i]; collectionAddress = nftPurchaseList.collectionAddress; @@ -239,7 +235,7 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua require(collectionAddress != address(0), "collection should not be null"); require(nftCollectionRules.collectionAddress == collectionAddress, "collection not in the pool"); - //Iterate over the token ids + // Iterate over the token ids for (uint256 j; j < tokenIdsLength; ++j) { if (NftCheck.supports721(collectionAddress)) { require(IERC721(collectionAddress).ownerOf(tokenIds[j]) == msg.sender, "has to be the token owner"); @@ -253,7 +249,7 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua nftId[collectionAddress][tokenIds[j]] = true; emit BlacklistNFT(collectionAddress, tokenIds[j]); } else { - //Must otherwise be an 1155 given initialise function + // Must otherwise be an 1155 given initialise function require(nftId[collectionAddress][tokenIds[j]], "tokenId not in the pool"); require( IERC1155(collectionAddress).balanceOf(msg.sender, tokenIds[j]) >= @@ -266,7 +262,7 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua if (nftCollectionRules.purchaseAmount > 0 && maxPurchaseTokenAmount != type(uint256).max) { unchecked { uint256 collectionAllowance = nftCollectionRules.purchaseAmount * tokenIdsLength; - // if there is an overflow of the previous calculation, allow the max purchase token amount + // If there is an overflow of the previous calculation, allow the max purchase token amount if (collectionAllowance / nftCollectionRules.purchaseAmount != tokenIdsLength) { maxPurchaseTokenAmount = type(uint256).max; } else { @@ -335,24 +331,27 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev the withdraw and partial withdraw methods allow a purchaser to take their - * purchase tokens back in exchange for pool tokens if they do not accept a deal - * - * Requirements: - * - the pool has expired either due to the creation of a deal or the end of the duration + * @notice This function allows any purchaser to take all of their purchase tokens back in exchange + * for pool tokens if they do not accept a deal. + * NOTE The pool must have expired either due to the creation of a deal or the end of the duration. */ function withdrawMaxFromPool() external { _withdraw(balanceOf(msg.sender)); } + /** + * @notice This function allows any purchaser to a portion of their purchase tokens back in exchange + * for pool tokens if they do not accept a deal. + * @param _purchaseTokenAmount The amount of purchase tokens the user wishes to recieve. + * NOTE The pool must have expired either due to the creation of a deal or the end of the duration. + */ function withdrawFromPool(uint256 _purchaseTokenAmount) external { _withdraw(_purchaseTokenAmount); } /** - * @dev purchasers can withdraw at the end of the pool expiry period if - * no deal was presented or they can withdraw after the holder funding period - * if they do not like a deal + * @dev Purchasers can withdraw at the end of the pool expiry period if no deal was presented or they + * can withdraw after the holder funding period if they do not like a deal. */ function _withdraw(uint256 _purchaseTokenAmount) internal { require(_purchaseTokenAmount <= balanceOf(msg.sender), "input larger than balance"); @@ -368,19 +367,30 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev only the sponsor can create a deal. The deal must be funded by the holder - * of the underlying deal token before a purchaser may accept the deal. If the - * holder does not fund the deal before the expiry period is over then the sponsor - * can create a new deal for the pool of capital by calling this method again. - * - * Requirements: - * - The purchase expiry period must be over - * - the holder funding expiry period must be from 30 minutes to 30 days - * - the pro rata redemption period must be from 30 minutes to 30 days - * - the purchase token total for the deal that may be accepted must be <= the funds in the pool - * - if the pro rata conversion ratio (purchase token total for the deal:funds in pool) - * is 1:1 then the open redemption period must be 0, - * otherwise the open period is from 30 minutes to 30 days + * @notice This function allows the sponsor to create a deal. + * @param _underlyingDealToken The address of the underlying deal token. + * @param _purchaseTokenTotalForDeal The total amount of purchase tokens to be distributed for the + * deal. + * @param _underlyingDealTokenTotal The total amount of underlying deal tokens to be used in the deal. + * @param _vestingPeriod The vesting period for the deal. + * @param _vestingCliffPeriod The vesting cliff period for the deal. + * @param _proRataRedemptionPeriod The pro rata redemption period for the deal. + * @param _openRedemptionPeriod The open redemption period for the deal. + * @param _holder The account with the holder role for the deal. + * @param _holderFundingDuration The holder funding duration for the deal. + * @return address The address of the storage proxy contract for the newly created deal. + * NOTE The deal must be fully funded with the underlying deal token before a purchaser may accept + * the deal. If the deal has not been funded before the expiry period is over then the sponsor may + * create a new deal for the pool of capital by calling this function again. Moreover, the following + * requirements must be satisfied: + * - The purchase expiry period must be over. + * - The vestingPeriod must be less than 5 years. + * - The vestingCliffPeriod must be less than 5 years. + * - The holder funding expiry period must be from 30 minutes to 30 days. + * - The pro rata redemption period must be from 30 minutes to 30 days. + * - The purchase token total for the deal that may be accepted must be <= the funds in the pool. + * - If the pro rata conversion ratio (purchase token total for the deal:funds in pool) is 1:1 then + * the open redemption period must be 0, otherwise the open period is from 30 minutes to 30 days. */ function createDeal( address _underlyingDealToken, @@ -459,19 +469,23 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev the 2 methods allow a purchaser to exchange accept all or a - * portion of their pool tokens for deal tokens - * - * Requirements: - * - the redemption period is either in the pro rata or open windows - * - the purchaser cannot accept more than their share for a period - * - if participating in the open period, a purchaser must have maxxed their - * contribution in the pro rata phase + * @notice This function allows a purchaser to exchange all of their pool tokens for deal tokens. + * NOTE The redemption period must be either in the pro rata or open periods, the purchaser cannot + * accept more than their share for a period, and if participating in the open period, a purchaser + * must have maxxed out their contribution in the pro rata phase. */ function acceptMaxDealTokens() external { _acceptDealTokens(msg.sender, 0, true); } + /** + * @notice This function allows a purchaser to exchange a portion of their pool tokens for deal + * tokens. + * @param _poolTokenAmount The amount of pool tokens to be exchanged. + * NOTE The redemption period must be either in the pro rata or open periods, the purchaser cannot + * accept more than their share for a period, and if participating in the open period, a purchaser + * must have maxxed out their contribution in the pro rata phase. + */ function acceptDealTokens(uint256 _poolTokenAmount) external { _acceptDealTokens(msg.sender, _poolTokenAmount, false); } @@ -520,6 +534,10 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua _mintDealTokens(_recipient, acceptAmount); } + /** + * @notice This function allows the sponsor to claim their sponsor fee amount. + * NOTE The redemption period must have expired. + */ function sponsorClaim() external { require(address(aelinDeal) != address(0), "no deal yet"); (, , uint256 proRataRedemptionExpiry) = aelinDeal.proRataRedemption(); @@ -533,8 +551,12 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev the if statement says if you have no balance or if the deal is not funded - * or if the pro rata period is not active, then you have 0 available for this period + * @notice This view function returns the maximum pro rata amount a purchaser can receive at any + * given time. + * @param _purchaser The purchaser's address. + * @return uint256 The max pro rata amount the purchaser can recieve. + * @dev The if statement says if you have no balance or if the deal is not funded or if the pro rata + * period is not active, then you have 0 available for this period. */ function maxProRataAmount(address _purchaser) public view returns (uint256) { (, uint256 proRataRedemptionStart, uint256 proRataRedemptionExpiry) = aelinDeal.proRataRedemption(); @@ -561,8 +583,8 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev the holder will receive less purchase tokens than the amount - * transferred if the purchase token burns or takes a fee during transfer + * @dev The holder will receive less purchase tokens than the amount transferred if the purchase token + * burns or takes a fee during transfer. */ function _mintDealTokens(address _recipient, uint256 _poolTokenAmount) internal { _burn(_recipient, _poolTokenAmount); @@ -580,15 +602,16 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev view to see how much of the deal a purchaser can accept. + * @notice This view function returns the maximum amount of deal tokens a purchaser can recieve. + * @param _purchaser The purchaser's address. + * @return uint256 The max amount the purchaser can recieve. */ function maxDealAccept(address _purchaser) external view returns (uint256) { /** - * The if statement is checking to see if the holder has not funded the deal - * or if the period is outside of a redemption window so nothing is available. - * It then checks if you are in the pro rata period and open period eligibility + * @dev The if statement is checking to see if the holder has not funded the deal or if the + * period is outside of a redemption window so nothing is available. It then checks if you are + * in the pro rata period and open period eligibility. */ - (, uint256 proRataRedemptionStart, uint256 proRataRedemptionExpiry) = aelinDeal.proRataRedemption(); (, uint256 openRedemptionStart, uint256 openRedemptionExpiry) = aelinDeal.openRedemption(); @@ -622,21 +645,26 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev convert pool with varying decimals to deal tokens of 18 decimals - * NOTE that a purchase token must not be greater than 18 decimals + * @dev Convert pool with varying decimals to deal tokens of 18 decimals. + * NOTE A purchase token must not be greater than 18 decimals. */ function _convertPoolToDeal(uint256 _poolTokenAmount, uint256 _poolTokenDecimals) internal pure returns (uint256) { return _poolTokenAmount * 10 ** (18 - _poolTokenDecimals); } /** - * @dev the sponsor may change addresses + * @notice This function allows the sponosor to set a future sponsor address without changing the + * sponsor address currently. + * @param _futureSponsor The future sponsor address. */ - function setSponsor(address _sponsor) external onlySponsor { - require(_sponsor != address(0), "cant pass null sponsor address"); - futureSponsor = _sponsor; + function setSponsor(address _futureSponsor) external onlySponsor { + require(_futureSponsor != address(0), "cant pass null sponsor address"); + futureSponsor = _futureSponsor; } + /** + * @notice This function allows the future sponsor address to replace the current sponsor address. + */ function acceptSponsor() external { require(msg.sender == futureSponsor, "only future sponsor can access"); sponsor = futureSponsor; @@ -644,14 +672,14 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua } /** - * @dev a function that any Ethereum address can call to vouch for a pool's legitimacy + * @notice A function that any address can call to vouch for a pool's legitimacy. */ function vouch() external { emit Vouch(msg.sender); } /** - * @dev a function that any Ethereum address can call to disavow for a pool's legitimacy + * @notice A function that any address can call to disavow a pool's legitimacy. */ function disavow() external { emit Disavow(msg.sender); @@ -662,20 +690,24 @@ contract AelinPool is AelinERC20, MinimalProxyFactory, IAelinPool, ReentrancyGua calledInitialize = true; _; } + modifier onlySponsor() { require(msg.sender == sponsor, "only sponsor can access"); _; } + modifier dealReady() { if (holderFundingExpiry > 0) { require(!aelinDeal.depositComplete() && block.timestamp >= holderFundingExpiry, "cant create new deal"); } _; } + modifier dealFunded() { require(holderFundingExpiry > 0 && aelinDeal.depositComplete(), "deal not yet funded"); _; } + modifier transferWindow() { (, uint256 proRataRedemptionStart, uint256 proRataRedemptionExpiry) = aelinDeal.proRataRedemption(); (, uint256 openRedemptionStart, uint256 openRedemptionExpiry) = aelinDeal.openRedemption(); diff --git a/contracts/AelinPoolFactory.sol b/contracts/AelinPoolFactory.sol index 85eefcf3..ca9b67a2 100644 --- a/contracts/AelinPoolFactory.sol +++ b/contracts/AelinPoolFactory.sol @@ -5,7 +5,7 @@ import {AelinPool, IAelinPool} from "./AelinPool.sol"; import {MinimalProxyFactory} from "./MinimalProxyFactory.sol"; /** - * @dev the factory contract allows an Aelin sponsor to permissionlessly create new pools + * @dev The factory contract allows an Aelin sponsor to permissionlessly create new pools. */ contract AelinPoolFactory is MinimalProxyFactory { address public immutable AELIN_TREASURY; @@ -25,7 +25,9 @@ contract AelinPoolFactory is MinimalProxyFactory { } /** - * @dev the method a sponsor calls to create a pool + * @notice This function is called by a sponsor when creating a pool. + * @param _poolData The pool data for the new pool. + * @return address The address of the newly created pool. */ function createPool(IAelinPool.PoolData calldata _poolData) external returns (address) { require(_poolData.purchaseToken != address(0), "cant pass null token address"); diff --git a/contracts/AelinToken.sol b/contracts/AelinToken.sol index b49c8204..883028bd 100644 --- a/contracts/AelinToken.sol +++ b/contracts/AelinToken.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; /** - * @dev a standard ERC20 contract for the AELIN token + * @dev A standard ERC20 contract for the AELIN token. */ contract AelinToken is ERC20 { diff --git a/contracts/AelinTokenSwapper.sol b/contracts/AelinTokenSwapper.sol index 1b6cfedb..b264736f 100644 --- a/contracts/AelinTokenSwapper.sol +++ b/contracts/AelinTokenSwapper.sol @@ -10,7 +10,9 @@ error AwaitingDeposit(); error AlreadyDeposited(); contract AelinTokenSwapper { - // Initial token supply set to 10M tokens as stated in AELIP-50 + /** + * NOTE Initial token supply set to 10M tokens as stated in AELIP-50. + */ uint256 public constant TOKEN_SUPPLY = 10 * 1e6 * 1e18; uint256 public constant OLD_TOKEN_SUPPLY = 5000 * 1e18; @@ -26,6 +28,10 @@ contract AelinTokenSwapper { aelinTreasury = _aelinTreasury; } + /** + * @notice This function provides a formal method for the Aelin treasury to deposit the new Aelin + * token into this Token Swapper contract. + */ function depositTokens() external { if (deposited == true) revert AlreadyDeposited(); if (msg.sender != aelinTreasury) revert Unauthorized(); @@ -35,6 +41,11 @@ contract AelinTokenSwapper { emit TokenDeposited(msg.sender, address(this), TOKEN_SUPPLY); } + /** + * @notice This function allows any holder of the old Aelin token to redeem their old tokens for a + * commensurate amount of new Aelin tokens. + * @param _amount The amount of old Aelin tokens to be swapped for new Aelin tokens. + */ function swap(uint256 _amount) external { if (deposited == false) revert AwaitingDeposit(); if (_amount == 0) revert AmountTooLow(); diff --git a/contracts/AelinUpFrontDeal.sol b/contracts/AelinUpFrontDeal.sol index c6ef9ba5..36004a54 100644 --- a/contracts/AelinUpFrontDeal.sol +++ b/contracts/AelinUpFrontDeal.sol @@ -17,6 +17,7 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin uint256 public constant BASE = 100 * 10 ** 18; uint256 public constant MAX_SPONSOR_FEE = 15 * 10 ** 18; uint256 public constant AELIN_FEE = 2 * 10 ** 18; + uint256 public constant MAX_VESTING_SCHEDULES = 10; UpFrontDealData public dealData; UpFrontDealConfig public dealConfig; @@ -29,8 +30,10 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin MerkleTree.TrackClaimed private trackClaimed; AelinAllowList.AllowList public allowList; AelinNftGating.NftGatingData public nftGating; - mapping(address => uint256) public purchaseTokensPerUser; - mapping(address => uint256) public poolSharesPerUser; + + // User => VestingIndex => Amount + mapping(address => mapping(uint256 => uint256)) public purchaseTokensPerUser; + mapping(address => mapping(uint256 => uint256)) public poolSharesPerUser; uint256 public totalPurchasingAccepted; uint256 public totalPoolShares; @@ -46,11 +49,12 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin uint256 public dealStart; uint256 public purchaseExpiry; - uint256 public vestingCliffExpiry; - uint256 public vestingExpiry; + uint256[] public vestingCliffExpiries; + uint256[] public vestingExpiries; /** - * @dev initializes the contract configuration, called from the factory contract when creating a new Up Front Deal + * @dev Initializes the contract configuration, called from the factory contract when creating a new Up + * Front Deal. */ function initialize( UpFrontDealData calldata _dealData, @@ -60,7 +64,7 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin address _aelinTreasuryAddress, address _aelinEscrowLogicAddress ) external initOnce { - // pool initialization checks + // Pool initialization checks require(_dealData.purchaseToken != _dealData.underlyingDealToken, "purchase & underlying the same"); require(_dealData.purchaseToken != address(0), "cant pass null purchase address"); require(_dealData.underlyingDealToken != address(0), "cant pass null underlying addr"); @@ -69,31 +73,48 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin require(_dealConfig.purchaseDuration >= 30 minutes && _dealConfig.purchaseDuration <= 30 days, "not within limit"); require(_dealData.sponsorFee <= MAX_SPONSOR_FEE, "exceeds max sponsor fee"); - require(1825 days >= _dealConfig.vestingCliffPeriod, "max 5 year cliff"); - require(1825 days >= _dealConfig.vestingPeriod, "max 5 year vesting"); + uint256 numberOfVestingSchedules = _dealConfig.vestingSchedules.length; + + require(numberOfVestingSchedules > 0, "no vesting schedules"); + require(numberOfVestingSchedules <= MAX_VESTING_SCHEDULES, "too many vesting schedules"); + + // Sets as the first vesting schedule value initially but updates in the loop later + uint256 lowestPrice = _dealConfig.vestingSchedules[0].purchaseTokenPerDealToken; + + for (uint256 i; i < numberOfVestingSchedules; i++) { + require(_dealConfig.vestingSchedules[i].vestingCliffPeriod <= 1825 days, "max 5 year cliff"); + require(_dealConfig.vestingSchedules[i].vestingPeriod <= 1825 days, "max 5 year vesting"); + require(_dealConfig.vestingSchedules[i].purchaseTokenPerDealToken > 0, "invalid deal price"); + + if (_dealConfig.vestingSchedules[i].purchaseTokenPerDealToken < lowestPrice) { + lowestPrice = _dealConfig.vestingSchedules[i].purchaseTokenPerDealToken; + } + } require(_dealConfig.underlyingDealTokenTotal > 0, "must have nonzero deal tokens"); - require(_dealConfig.purchaseTokenPerDealToken > 0, "invalid deal price"); uint8 underlyingTokenDecimals = IERC20Extended(_dealData.underlyingDealToken).decimals(); if (_dealConfig.purchaseRaiseMinimum > 0) { - uint256 _totalIntendedRaise = (_dealConfig.purchaseTokenPerDealToken * _dealConfig.underlyingDealTokenTotal) / - 10 ** underlyingTokenDecimals; - require(_totalIntendedRaise > 0, "intended raise too small"); - require(_dealConfig.purchaseRaiseMinimum <= _totalIntendedRaise, "raise min > deal total"); + uint256 minDealTotal = (lowestPrice * _dealConfig.underlyingDealTokenTotal) / 10 ** underlyingTokenDecimals; + require(minDealTotal >= _dealConfig.purchaseRaiseMinimum, "raise min > deal total"); } - // store pool and deal details as state variables + // Store pool and deal details as state variables dealData = _dealData; dealConfig = _dealConfig; + vestingCliffExpiries = new uint256[](numberOfVestingSchedules); + vestingExpiries = new uint256[](numberOfVestingSchedules); + dealStart = block.timestamp; dealFactory = msg.sender; - // the deal token has the same amount of decimals as the underlying deal token, - // eventually making them 1:1 redeemable + /** + * @dev The deal token has the same amount of decimals as the underlying deal token, eventually making + * them 1:1 redeemable. + */ _setNameAndSymbol( string(abi.encodePacked("aeUpFrontDeal-", _dealData.name)), string(abi.encodePacked("aeUD-", _dealData.symbol)) @@ -102,14 +123,16 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin aelinEscrowLogicAddress = _aelinEscrowLogicAddress; aelinTreasuryAddress = _aelinTreasuryAddress; - // Allow list logic - // check if there's allowlist and amounts, - // if yes, store it to `allowList` and emit a single event with the addresses and amounts + /** + * @dev Allow list logic: Checks if there's an allowlist and amounts, if yes then stores it to + * `allowList` and emit a single event with the addresses and amounts. + */ AelinAllowList.initialize(_allowListInit, allowList); - // NftCollection logic - // check if the deal is nft gated - // if yes, store it in `nftCollectionDetails` and `nftId` and emit respective events for 721 and 1155 + /** + * @dev NftCollection logic: Checks if the deal is NFT-gated, if yes then stores it in + * `nftCollectionDetails` and `nftId` and emit respective events for 721 and 1155 tokens. + */ AelinNftGating.initialize(_nftCollectionRules, nftGating); require(!(allowList.hasAllowList && nftGating.hasNftList), "cant have allow list & nft"); @@ -120,27 +143,24 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin function _startPurchasingPeriod( uint256 _purchaseDuration, - uint256 _vestingCliffPeriod, - uint256 _vestingPeriod + IAelinUpFrontDeal.VestingSchedule[] memory _vestingSchedules ) internal { underlyingDepositComplete = true; purchaseExpiry = block.timestamp + _purchaseDuration; - vestingCliffExpiry = purchaseExpiry + _vestingCliffPeriod; - vestingExpiry = vestingCliffExpiry + _vestingPeriod; - emit DealFullyFunded(address(this), block.timestamp, purchaseExpiry, vestingCliffExpiry, vestingExpiry); - } - modifier initOnce() { - require(!calledInitialize, "can only init once"); - calledInitialize = true; - _; + for (uint256 i; i < _vestingSchedules.length; i++) { + vestingCliffExpiries[i] = purchaseExpiry + _vestingSchedules[i].vestingCliffPeriod; + vestingExpiries[i] = vestingCliffExpiries[i] + _vestingSchedules[i].vestingPeriod; + } + + emit DealFullyFunded(address(this), block.timestamp, purchaseExpiry, vestingCliffExpiries, vestingExpiries); } /** - * @dev method for holder to deposit underlying deal tokens - * all underlying deal tokens must be deposited for the purchasing period to start - * if tokens were deposited directly, this method must still be called to start the purchasing period + * @notice This function allows the holder to deposit underlying deal tokens. * @param _depositUnderlyingAmount how many underlying tokens the holder will transfer to the contract + * NOTE All underlying deal tokens must be deposited for the purchasing period to start. If tokens were + * deposited directly, this method must still be called in order to start the purchasing period. */ function depositUnderlyingTokens(uint256 _depositUnderlyingAmount) external onlyHolder { address _underlyingDealToken = dealData.underlyingDealToken; @@ -154,14 +174,15 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin uint256 underlyingDealTokenAmount = balanceAfterTransfer - balanceBeforeTransfer; if (balanceAfterTransfer >= dealConfig.underlyingDealTokenTotal) { - _startPurchasingPeriod(dealConfig.purchaseDuration, dealConfig.vestingCliffPeriod, dealConfig.vestingPeriod); + _startPurchasingPeriod(dealConfig.purchaseDuration, dealConfig.vestingSchedules); } emit DepositDealToken(_underlyingDealToken, msg.sender, underlyingDealTokenAmount); } /** - * @dev allows holder to withdraw any excess underlying deal tokens deposited to the contract + * @notice This function allows the holder to withdraw any excess underlying deal tokens deposited + * to the contract. */ function withdrawExcess() external onlyHolder { address _underlyingDealToken = dealData.underlyingDealToken; @@ -176,23 +197,28 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin } /** - * @dev accept deal by depositing purchasing tokens which is converted to a mapping which stores the amount of - * underlying purchased. pool shares have the same decimals as the underlying deal token - * @param _nftPurchaseList NFTs to use for accepting the deal if deal is NFT gated - * @param _merkleData Merkle Proof data to prove investors allocation - * @param _purchaseTokenAmount how many purchase tokens will be used to purchase deal token shares + * @notice This function allows anyone to accept a deal by depositing purchasing tokens. + * @dev The deposited purchasing tokens are converted to a mapping that stores the amount of underlying + * tokens purchased. + * @param _nftPurchaseList NFTs to use for accepting the deal if deal is NFT gated. + * @param _merkleData Merkle Proof data to prove investors allocation. + * @param _purchaseTokenAmount How many purchase tokens will be used to purchase deal token shares. + * @param _vestingIndex The vesting schedule index for which to claim from. + * NOTE Pool shares have the same decimals as the underlying deal token. */ function acceptDeal( AelinNftGating.NftPurchaseList[] calldata _nftPurchaseList, MerkleTree.UpFrontMerkleData calldata _merkleData, - uint256 _purchaseTokenAmount + uint256 _purchaseTokenAmount, + uint256 _vestingIndex ) external nonReentrant { require(underlyingDepositComplete, "underlying deposit incomplete"); require(block.timestamp < purchaseExpiry, "not in purchase window"); + require(_vestingIndex < dealConfig.vestingSchedules.length, "index not in bounds"); address _purchaseToken = dealData.purchaseToken; uint256 _underlyingDealTokenTotal = dealConfig.underlyingDealTokenTotal; - uint256 _purchaseTokenPerDealToken = dealConfig.purchaseTokenPerDealToken; + uint256 _purchaseTokenPerDealToken = dealConfig.vestingSchedules[_vestingIndex].purchaseTokenPerDealToken; require(IERC20(_purchaseToken).balanceOf(msg.sender) >= _purchaseTokenAmount, "not enough purchaseToken"); if (nftGating.hasNftList || _nftPurchaseList.length > 0) { @@ -206,23 +232,26 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin uint256 balanceBeforeTransfer = IERC20(_purchaseToken).balanceOf(address(this)); IERC20(_purchaseToken).safeTransferFrom(msg.sender, address(this), _purchaseTokenAmount); - uint256 balanceAfterTransfer = IERC20(_purchaseToken).balanceOf(address(this)); - uint256 purchaseTokenAmount = balanceAfterTransfer - balanceBeforeTransfer; + uint256 purchaseTokenAmount = IERC20(_purchaseToken).balanceOf(address(this)) - balanceBeforeTransfer; totalPurchasingAccepted += purchaseTokenAmount; - purchaseTokensPerUser[msg.sender] += purchaseTokenAmount; + purchaseTokensPerUser[msg.sender][_vestingIndex] += purchaseTokenAmount; uint8 underlyingTokenDecimals = IERC20Extended(dealData.underlyingDealToken).decimals(); - // this takes into account the decimal conversion between purchasing token and underlying deal token - // pool shares having the same amount of decimals as underlying deal tokens + /** + * @dev This takes into account the decimal conversion between purchasing token and underlying deal token + * pool shares having the same amount of decimals as underlying deal tokens. + */ uint256 poolSharesAmount = (purchaseTokenAmount * 10 ** underlyingTokenDecimals) / _purchaseTokenPerDealToken; require(poolSharesAmount > 0, "purchase amount too small"); - // pool shares directly correspond to the amount of deal tokens that can be minted - // pool shares held = deal tokens minted as long as no deallocation takes place + /** + * @dev Pool shares directly correspond to the amount of deal tokens that can be minted. Moreover, + * pool shares held = deal tokens minted as long as no deallocation takes place. + */ totalPoolShares += poolSharesAmount; - poolSharesPerUser[msg.sender] += poolSharesAmount; + poolSharesPerUser[msg.sender][_vestingIndex] += poolSharesAmount; if (!dealConfig.allowDeallocation) { require(totalPoolShares <= _underlyingDealTokenTotal, "purchased amount > total"); @@ -230,18 +259,23 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin emit AcceptDeal( msg.sender, + _vestingIndex, purchaseTokenAmount, - purchaseTokensPerUser[msg.sender], + purchaseTokensPerUser[msg.sender][_vestingIndex], poolSharesAmount, - poolSharesPerUser[msg.sender] + poolSharesPerUser[msg.sender][_vestingIndex] ); } /** - * @dev purchaser calls to claim their deal tokens or refund if the minimum raise does not pass + * @notice This function allows the purchaser to claim their deal tokens or refund if the minimum raise + * does not pass. + * @param _vestingIndex The vesting index that corresponds to the purchase the user made. + * NOTE If a user purchased across multiple vesting schedules they will need to call this function for each + * vesting index to recieve a full refund. */ - function purchaserClaim() external nonReentrant purchasingOver { - require(poolSharesPerUser[msg.sender] > 0, "no pool shares to claim with"); + function purchaserClaim(uint256 _vestingIndex) external nonReentrant purchasingOver { + require(poolSharesPerUser[msg.sender][_vestingIndex] > 0, "no pool shares to claim with"); address _purchaseToken = dealData.purchaseToken; uint256 _purchaseRaiseMinimum = dealConfig.purchaseRaiseMinimum; @@ -254,15 +288,15 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin uint256 precisionAdjustedRefund; if (deallocate) { - // adjust for deallocation and mint deal tokens + // Adjust for deallocation and mint deal tokens adjustedShareAmountForUser = - (((poolSharesPerUser[msg.sender] * _underlyingDealTokenTotal) / totalPoolShares) * + (((poolSharesPerUser[msg.sender][_vestingIndex] * _underlyingDealTokenTotal) / totalPoolShares) * (BASE - AELIN_FEE - dealData.sponsorFee)) / BASE; - // refund any purchase tokens that got deallocated - uint256 purchasingRefund = purchaseTokensPerUser[msg.sender] - - ((purchaseTokensPerUser[msg.sender] * _underlyingDealTokenTotal) / totalPoolShares); + // Refund any purchase tokens that got deallocated + uint256 purchasingRefund = purchaseTokensPerUser[msg.sender][_vestingIndex] - + ((purchaseTokensPerUser[msg.sender][_vestingIndex] * _underlyingDealTokenTotal) / totalPoolShares); precisionAdjustedRefund = purchasingRefund > IERC20(_purchaseToken).balanceOf(address(this)) ? IERC20(_purchaseToken).balanceOf(address(this)) @@ -271,33 +305,36 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin // Transfer purchase token refund IERC20(_purchaseToken).safeTransfer(msg.sender, precisionAdjustedRefund); } else { - // mint deal tokens when there is no deallocation + // Mint deal tokens when there is no deallocation adjustedShareAmountForUser = - ((BASE - AELIN_FEE - dealData.sponsorFee) * poolSharesPerUser[msg.sender]) / + ((BASE - AELIN_FEE - dealData.sponsorFee) * poolSharesPerUser[msg.sender][_vestingIndex]) / BASE; } - poolSharesPerUser[msg.sender] = 0; - purchaseTokensPerUser[msg.sender] = 0; + poolSharesPerUser[msg.sender][_vestingIndex] = 0; + purchaseTokensPerUser[msg.sender][_vestingIndex] = 0; - // mint vesting token and create schedule - _mintVestingToken(msg.sender, adjustedShareAmountForUser, purchaseExpiry); + // Mint vesting token and create schedule + _mintVestingToken(msg.sender, adjustedShareAmountForUser, purchaseExpiry, _vestingIndex); emit ClaimDealTokens(msg.sender, adjustedShareAmountForUser, precisionAdjustedRefund); } else { // Claim Refund - uint256 refundAmount = purchaseTokensPerUser[msg.sender]; - purchaseTokensPerUser[msg.sender] = 0; - poolSharesPerUser[msg.sender] = 0; + uint256 refundAmount = purchaseTokensPerUser[msg.sender][_vestingIndex]; + purchaseTokensPerUser[msg.sender][_vestingIndex] = 0; + poolSharesPerUser[msg.sender][_vestingIndex] = 0; IERC20(_purchaseToken).safeTransfer(msg.sender, refundAmount); emit ClaimDealTokens(msg.sender, 0, refundAmount); } } /** - * @dev sponsor calls once the purchasing period is over if the minimum raise has passed to claim - * their share of deal tokens - * NOTE also calls the claim for the protocol fee + * @notice This function allows the sponsor to claim their share of the deal tokens if the purchasing + * period has passed and the minimum raise has been achieved. + * @dev The sponser sets their own vesting index. + * @param _vestingIndex The vesting index the sponsor elects. + * NOTE This function also calls the claim for the protocol fee. */ - function sponsorClaim() external nonReentrant purchasingOver passMinimumRaise onlySponsor { + function sponsorClaim(uint256 _vestingIndex) external nonReentrant purchasingOver passMinimumRaise onlySponsor { + require(_vestingIndex < dealConfig.vestingSchedules.length, "wrong vesting index"); require(!sponsorClaimed, "sponsor already claimed"); sponsorClaimed = true; @@ -307,8 +344,8 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin uint256 totalSold = totalPoolShares > _underlyingDealTokenTotal ? _underlyingDealTokenTotal : totalPoolShares; uint256 _sponsorFeeAmt = (totalSold * dealData.sponsorFee) / BASE; - // mint vesting token and create schedule - _mintVestingToken(_sponsor, _sponsorFeeAmt, purchaseExpiry); + // Mint vesting token and create schedule + _mintVestingToken(_sponsor, _sponsorFeeAmt, purchaseExpiry, _vestingIndex); emit SponsorClaim(_sponsor, _sponsorFeeAmt); if (!feeEscrowClaimed) { @@ -317,9 +354,9 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin } /** - * @dev holder calls once purchasing period is over to claim their raise or - * underlying deal tokens if the minimum raise has not passed - * NOTE also calls the claim for the protocol fee + * @notice This function allows the holder, purchasing period is over, to claim their raise or the + * underlying deal tokens if the minimum raise was not exceeded. + * NOTE This function also calls the claim for the protocol fee. */ function holderClaim() external nonReentrant purchasingOver onlyHolder { require(!holderClaimed, "holder already claimed"); @@ -334,10 +371,17 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin uint256 _underlyingDealTokenTotal = dealConfig.underlyingDealTokenTotal; bool deallocate = totalPoolShares > _underlyingDealTokenTotal; + if (deallocate) { uint256 _underlyingTokenDecimals = IERC20Extended(_underlyingDealToken).decimals(); - uint256 _totalIntendedRaise = (dealConfig.purchaseTokenPerDealToken * _underlyingDealTokenTotal) / - 10 ** _underlyingTokenDecimals; + + uint256 _totalIntendedRaise; + + for (uint256 i; i < dealConfig.vestingSchedules.length; i++) { + _totalIntendedRaise += + (dealConfig.vestingSchedules[i].purchaseTokenPerDealToken * _underlyingDealTokenTotal) / + 10 ** _underlyingTokenDecimals; + } uint256 precisionAdjustedRaise = _totalIntendedRaise > IERC20(_purchaseToken).balanceOf(address(this)) ? IERC20(_purchaseToken).balanceOf(address(this)) @@ -346,10 +390,10 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin IERC20(_purchaseToken).safeTransfer(_holder, precisionAdjustedRaise); emit HolderClaim(_holder, _purchaseToken, precisionAdjustedRaise, _underlyingDealToken, 0, block.timestamp); } else { - // holder receives raise + // Holder receives raise uint256 _currentBalance = IERC20(_purchaseToken).balanceOf(address(this)); IERC20(_purchaseToken).safeTransfer(_holder, _currentBalance); - // holder receives any leftover underlying deal tokens + // Holder receives any leftover underlying deal tokens uint256 _underlyingRefund = _underlyingDealTokenTotal - totalPoolShares; IERC20(_underlyingDealToken).safeTransfer(_holder, _underlyingRefund); emit HolderClaim( @@ -372,7 +416,8 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin } /** - * @dev transfers protocol fee of underlying deal tokens to the treasury escrow contract + * @notice This function transfers the protocol fee of the underlying deal tokens to the treasury + * escrow contract. */ function feeEscrowClaim() public purchasingOver passMinimumRaise { if (!feeEscrowClaimed) { @@ -397,6 +442,23 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin } } + /** + * @notice This function allows a purchaser to claim the underlying deal tokens after the purchasing period + * is over. The amount recieved is dependent on the vesting schedule associated with the token id. + * @param _tokenId The token id to check the quantity of claimable underlying tokens. + * @return uint256 The amount of underlying deal tokens recieved. + */ + function claimUnderlying(uint256 _tokenId) external returns (uint256) { + return _claimUnderlying(msg.sender, _tokenId); + } + + /** + * @notice This function allows a purchaser to claim across multiple indices in order to retrieve the + * underlying deal tokens after the purchasing period is over. The amount recieved is dependent on the + * vesting schedules associated with the token ids. + * @param _indices An array of token ids to check the quantity of claimable underlying tokens. + * @return uint256 The total amount of underlying deal tokens recieved. + */ function claimUnderlyingMultipleEntries(uint256[] memory _indices) external returns (uint256) { uint256 totalClaimed; for (uint256 i = 0; i < _indices.length; i++) { @@ -405,21 +467,14 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin return totalClaimed; } - /** - * @dev ERC721 deal token holder calls after the purchasing period to claim underlying deal tokens - * amount based on the vesting schedule - */ - function claimUnderlying(uint256 _tokenId) external returns (uint256) { - return _claimUnderlying(msg.sender, _tokenId); - } - function _claimUnderlying(address _owner, uint256 _tokenId) internal returns (uint256) { require(ownerOf(_tokenId) == _owner, "must be owner to claim"); + uint256 vestingIndex = vestingDetails[_tokenId].vestingIndex; uint256 claimableAmount = claimableUnderlyingTokens(_tokenId); if (claimableAmount == 0) { return 0; } - if (block.timestamp >= vestingExpiry) { + if (block.timestamp >= vestingExpiries[vestingIndex]) { _burnVestingToken(_tokenId); } else { vestingDetails[_tokenId].lastClaimedAt = block.timestamp; @@ -432,29 +487,37 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin } /** - * @dev a view showing the amount of the underlying deal token a ERC721 deal token holder can claim - * @param _tokenId the token ID to check the quantity of claimable underlying tokens + * @notice This view function returns the the amount of the underlying deal token a purchaser can claim. + * @param _tokenId The token id to check the quantity of claimable underlying tokens. + * @return uint256 The amount of underlying deal tokens that can be recieved. */ - function claimableUnderlyingTokens(uint256 _tokenId) public view returns (uint256) { - VestingDetails memory schedule = vestingDetails[_tokenId]; + VestingDetails memory details = vestingDetails[_tokenId]; + uint256 vestingIndex = details.vestingIndex; uint256 precisionAdjustedUnderlyingClaimable; - if (schedule.lastClaimedAt > 0) { - uint256 maxTime = block.timestamp > vestingExpiry ? vestingExpiry : block.timestamp; - uint256 minTime = schedule.lastClaimedAt > vestingCliffExpiry ? schedule.lastClaimedAt : vestingCliffExpiry; - uint256 vestingPeriod = dealConfig.vestingPeriod; + if (details.lastClaimedAt > 0) { + uint256 maxTime = block.timestamp > vestingExpiries[vestingIndex] + ? vestingExpiries[vestingIndex] + : block.timestamp; + uint256 minTime = details.lastClaimedAt > vestingCliffExpiries[vestingIndex] + ? details.lastClaimedAt + : vestingCliffExpiries[vestingIndex]; + uint256 vestingPeriod = dealConfig.vestingSchedules[vestingIndex].vestingPeriod; if ( - (maxTime > vestingCliffExpiry && minTime <= vestingExpiry) || - (maxTime == vestingCliffExpiry && vestingPeriod == 0) + (maxTime > vestingCliffExpiries[vestingIndex] && minTime <= vestingExpiries[vestingIndex]) || + (maxTime == vestingCliffExpiries[vestingIndex] && vestingPeriod == 0) ) { uint256 underlyingClaimable = vestingPeriod == 0 - ? schedule.share - : (schedule.share * (maxTime - minTime)) / vestingPeriod; - - // This could potentially be the case where the last user claims a slightly smaller amount if there is some precision loss - // although it will generally never happen as solidity rounds down so there should always be a little bit left + ? details.share + : (details.share * (maxTime - minTime)) / vestingPeriod; + + /** + * @dev There could potentially be the case where the last user claims a slightly smaller amount + * if there is some precision loss, although it will generally never happen as solidity rounds + * down so there should always be a little bit left. + */ address _underlyingDealToken = dealData.underlyingDealToken; precisionAdjustedUnderlyingClaimable = underlyingClaimable > IERC20(_underlyingDealToken).balanceOf(address(this)) @@ -466,17 +529,18 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin } /** - * @dev the holder may change their address - * @param _holder address to swap the holder role + * @notice This function allows the holder to set a future holder address without changing the + * holder address currently. + * @param _futureHolder The future holder address. */ - function setHolder(address _holder) external onlyHolder { - require(_holder != address(0), "holder cant be null"); - futureHolder = _holder; - emit HolderSet(_holder); + function setHolder(address _futureHolder) external onlyHolder { + require(_futureHolder != address(0), "holder cant be null"); + futureHolder = _futureHolder; + emit HolderSet(_futureHolder); } /** - * @dev futurHolder can call to accept the role of holder + * @notice This function allows the future holder address to replace the current holder address. */ function acceptHolder() external { require(msg.sender == futureHolder, "only future holder can access"); @@ -485,26 +549,27 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin } /** - * @dev a function that any Ethereum address can call to vouch for a pool's legitimacy + * @notice A function that any address can call to vouch for a pool's legitimacy. */ function vouch() external { emit Vouch(msg.sender); } /** - * @dev a function that any Ethereum address can call to disavow for a pool's legitimacy + * @notice A function that any address can call to disavow a pool's legitimacy. */ function disavow() external { emit Disavow(msg.sender); } /** - * @dev returns allow list information - * @param _userAddress address to use in returning the amountPerAddress - * @return address[] returns array of addresses included in the allow list - * @return uint256[] returns array of allow list amounts for the address matching the index of allowListAddresses - * @return uint256 allow list amount for _userAddress input - * @return bool true if this deal has an allow list + * @notice This view function the allow list information for this deal. + * @param _userAddress The address to use in returning the amountPerAddress. + * @return address[] Returns array of addresses included in the allow list. + * @return uint256[] Returns array of allow list amounts for the address matching the index of + * allowListAddresses. + * @return uint256 Allow list amount for _userAddress input. + * @return bool Returns true if this deal has an allow list, false otherwise. */ function getAllowList(address _userAddress) public view returns (address[] memory, uint256[] memory, uint256, bool) { return ( @@ -516,13 +581,13 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin } /** - * @dev returns NFT collection details for the input collection address - * @param _collection NFT collection address to get the collection details for - * @return uint256 purchase amount, if 0 then unlimited purchase - * @return address collection address used for configuration - * @return IdRange[] for ERC721, an array of token Id ranges - * @return uint256[] for ERC1155, included token IDs for this collection - * @return uint256[] for ERC1155, min number of tokens required for participating + * @notice This view function returns the NFT collection details for the input collection address. + * @param _collection The NFT collection address to get the collection details for. + * @return uint256 Purchase amount, if 0 then an unlimited purchase. + * @return address Collection address used for configuration. + * @return IdRange[] For ERC721 collections, an array of token Id ranges. + * @return uint256[] for ERC1155 collections, included token IDs for this collection. + * @return uint256[] for ERC1155 collections, min number of tokens required for participating. */ function getNftCollectionDetails( address _collection @@ -537,24 +602,57 @@ contract AelinUpFrontDeal is MinimalProxyFactory, IAelinUpFrontDeal, AelinVestin } /** - * @dev returns various details about the NFT gating storage - * @param _collection NFT collection address to check - * @param _nftId if _collection is ERC721 check if this ID has been used, if ERC1155 check if this ID is included - * @return bool if _collection is ERC721 true if this ID has been used, if ERC1155 true if this ID is included - * @return bool returns hasNftList, true if this deal has a valid NFT gating list + * @notice This view function returns the NFT gating storage details for this deal. + * @param _collection NFT collection address to check. + * @param _nftId If _collection is ERC721 check if this id has been used, if ERC1155 check if this id is + * included. + * @return bool If _collection is an ERC721 collection, then true signifies the id has been used, + * if it is an ERC1155 collection, true signifies that the id is included. + * @return bool Returns true if this deal has a valid NFT gating list. */ function getNftGatingDetails(address _collection, uint256 _nftId) public view returns (bool, bool) { return (nftGating.nftId[_collection][_nftId], nftGating.hasNftList); } /** - * @dev hasPurchasedMerkle - * @param _index index of leaf node/ address to check + * @notice This view function returns the vesting schedule details for a given vesting index. + * @param _vestingIndex The vesting index schedule to retrieve details about. + * @return uint256 The purchaseTokenPerDealToken for the vesting schedule selected. + * @return uint256 The vestingCliffPeriod for the vesting schedule selected. + * @return uint256 The vestingPeriod for the vesting schedule selected. + */ + function getVestingScheduleDetails(uint256 _vestingIndex) public view returns (uint256, uint256, uint256) { + require(_vestingIndex < dealConfig.vestingSchedules.length, "index out of bounds"); + return ( + dealConfig.vestingSchedules[_vestingIndex].purchaseTokenPerDealToken, + dealConfig.vestingSchedules[_vestingIndex].vestingCliffPeriod, + dealConfig.vestingSchedules[_vestingIndex].vestingPeriod + ); + } + + /** + * @notice This view function returns the number of vesting schedules that exist for this deal. + * @return uint256 The length of the vesting schedules array. + */ + function getNumberOfVestingSchedules() public view returns (uint256) { + return dealConfig.vestingSchedules.length; + } + + /** + * @notice This is a view function that returns a boolean specifying whether or not an account has + * has purchased tokens from a deal using a merkle proof. + * @param _index The index of leaf node/address to check. */ function hasPurchasedMerkle(uint256 _index) public view returns (bool) { return MerkleTree.hasPurchasedMerkle(trackClaimed, _index); } + modifier initOnce() { + require(!calledInitialize, "can only init once"); + calledInitialize = true; + _; + } + modifier onlyHolder() { require(msg.sender == dealData.holder, "must be holder"); _; diff --git a/contracts/AelinUpFrontDealFactory.sol b/contracts/AelinUpFrontDealFactory.sol index a16a4c6c..a2ec26f3 100644 --- a/contracts/AelinUpFrontDealFactory.sol +++ b/contracts/AelinUpFrontDealFactory.sol @@ -6,7 +6,7 @@ import {MinimalProxyFactory} from "./MinimalProxyFactory.sol"; import {AelinAllowList} from "./libraries/AelinAllowList.sol"; import {AelinNftGating} from "./libraries/AelinNftGating.sol"; -contract AelinUpFrontDealFactory is MinimalProxyFactory { +contract AelinUpFrontDealFactory is MinimalProxyFactory, IAelinUpFrontDeal { address public immutable UP_FRONT_DEAL_LOGIC; address public immutable AELIN_ESCROW_LOGIC; address public immutable AELIN_TREASURY; @@ -20,6 +20,14 @@ contract AelinUpFrontDealFactory is MinimalProxyFactory { AELIN_TREASURY = _aelinTreasury; } + /** + * @notice This function allows anyone to create an Up Front Deal. + * @param _dealData The deal data for the new deal. + * @param _dealConfig The deal configuration settings for the new deal. + * @param _nftCollectionRules An array of NFT collection rules for the new deal. + * @param _allowListInit The allow list informtation for the new deal. + * @return upFrontDealAddress The address of the newly created Up Front Deal. + */ function createUpFrontDeal( IAelinUpFrontDeal.UpFrontDealData calldata _dealData, IAelinUpFrontDeal.UpFrontDealConfig calldata _dealConfig, @@ -54,36 +62,10 @@ contract AelinUpFrontDealFactory is MinimalProxyFactory { emit CreateUpFrontDealConfig( upFrontDealAddress, _dealConfig.underlyingDealTokenTotal, - _dealConfig.purchaseTokenPerDealToken, _dealConfig.purchaseRaiseMinimum, _dealConfig.purchaseDuration, - _dealConfig.vestingPeriod, - _dealConfig.vestingCliffPeriod, + _dealConfig.vestingSchedules, _dealConfig.allowDeallocation ); } - - event CreateUpFrontDeal( - address indexed dealAddress, - string name, - string symbol, - address purchaseToken, - address underlyingDealToken, - address indexed holder, - address indexed sponsor, - uint256 sponsorFee, - bytes32 merkleRoot, - string ipfsHash - ); - - event CreateUpFrontDealConfig( - address indexed dealAddress, - uint256 underlyingDealTokenTotal, - uint256 purchaseTokenPerDealToken, - uint256 purchaseRaiseMinimum, - uint256 purchaseDuration, - uint256 vestingPeriod, - uint256 vestingCliffPeriod, - bool allowDeallocation - ); } diff --git a/contracts/AelinVestingToken.sol b/contracts/AelinVestingToken.sol index 95172825..87f56b52 100644 --- a/contracts/AelinVestingToken.sol +++ b/contracts/AelinVestingToken.sol @@ -8,10 +8,10 @@ contract AelinVestingToken is AelinERC721, IAelinVestingToken { mapping(uint256 => VestingDetails) public vestingDetails; uint256 public tokenCount; - function _mintVestingToken(address _to, uint256 _amount, uint256 _timestamp) internal { + function _mintVestingToken(address _to, uint256 _amount, uint256 _timestamp, uint256 _vestingIndex) internal { _mint(_to, tokenCount); - vestingDetails[tokenCount] = VestingDetails(_amount, _timestamp); - emit VestingTokenMinted(_to, tokenCount, _amount, _timestamp); + vestingDetails[tokenCount] = VestingDetails(_amount, _timestamp, _vestingIndex); + emit VestingTokenMinted(_to, tokenCount, _amount, _timestamp, _vestingIndex); tokenCount += 1; } @@ -21,13 +21,23 @@ contract AelinVestingToken is AelinERC721, IAelinVestingToken { emit VestingTokenBurned(_tokenId); } + /** + * @notice This function allows anyone to transfer their vesting share to another address. + * @param _to The recipient of the vesting token. + * @param _tokenId The token Id from which the user wants to trasnfer. + * @param _shareAmount The amount of vesting share the user wants to transfer. + */ function transferVestingShare(address _to, uint256 _tokenId, uint256 _shareAmount) public nonReentrant { require(ownerOf(_tokenId) == msg.sender, "must be owner to transfer"); VestingDetails memory schedule = vestingDetails[_tokenId]; require(_shareAmount > 0, "share amount should be > 0"); require(_shareAmount < schedule.share, "amout gt than current share"); - vestingDetails[_tokenId] = VestingDetails(schedule.share - _shareAmount, schedule.lastClaimedAt); - _mintVestingToken(_to, _shareAmount, schedule.lastClaimedAt); + vestingDetails[_tokenId] = VestingDetails( + schedule.share - _shareAmount, + schedule.lastClaimedAt, + schedule.vestingIndex + ); + _mintVestingToken(_to, _shareAmount, schedule.lastClaimedAt, schedule.vestingIndex); emit VestingShareTransferred(msg.sender, _to, _tokenId, _shareAmount); } diff --git a/contracts/MerkleFeeDistributor.sol b/contracts/MerkleFeeDistributor.sol index 0421a7a6..cea4610e 100644 --- a/contracts/MerkleFeeDistributor.sol +++ b/contracts/MerkleFeeDistributor.sol @@ -32,6 +32,12 @@ contract MerkleFeeDistributor is Ownable, IMerkleFeeDistributor { merkleRoot = _merkleRoot; } + /** + * @notice This view function returns true if fees have already been claimed for a given merkle index, + * and false otherwise. + * @param index The user's merkle index that corresponds to their account and share. + * @return bool Returns true if the index has already been used to claim and false otherwise. + */ function isClaimed(uint256 index) public view override returns (bool) { uint256 claimedWordIndex = index / 256; uint256 claimedBitIndex = index % 256; @@ -54,6 +60,16 @@ contract MerkleFeeDistributor is Ownable, IMerkleFeeDistributor { : claimableAmount; } + /** + * @notice This function allows anyone to claim tokens if they can provide valid data and assocaited + * merkle proof. + * @param _index The index in the merkle tree. + * @param _account The account to claim the fee tokens. + * @param _share The share of tokens to be distributed. + * @param _merkleProof The merkle proof used to verify account and share for claiming. + * NOTE A user may call this function and claim fees on behalf of another user if they can provide + * a valid merkle proof. + */ function claim(uint256 _index, address _account, uint256 _share, bytes32[] calldata _merkleProof) external override { require(!isClaimed(_index), "already claimed"); @@ -72,6 +88,10 @@ contract MerkleFeeDistributor is Ownable, IMerkleFeeDistributor { emit Claimed(_index, _account, _share); } + /** + * @notice This function allows the owner to retrieve all ERC20 tokens specified above that are + * in this contract. + */ function withdrawAll() public onlyOwner { emit Withdrawn(owner(), TOKEN1, IERC20(TOKEN1).balanceOf(address(this))); IERC20(TOKEN1).safeTransfer(owner(), IERC20(TOKEN1).balanceOf(address(this))); @@ -86,6 +106,10 @@ contract MerkleFeeDistributor is Ownable, IMerkleFeeDistributor { IERC20(TOKEN4).safeTransfer(owner(), IERC20(TOKEN4).balanceOf(address(this))); } + /** + * @notice This function allows the owner to retrieve any given ERC20 token in this contract. + * @param _token The address of the ERC20 token. + */ function withdraw(address _token) public onlyOwner { uint256 tokenBalance = IERC20(_token).balanceOf(address(this)); require(tokenBalance > 0, "balance is zero"); diff --git a/contracts/interfaces/IAelinPool.sol b/contracts/interfaces/IAelinPool.sol index 0362f183..6213c466 100644 --- a/contracts/interfaces/IAelinPool.sol +++ b/contracts/interfaces/IAelinPool.sol @@ -15,26 +15,32 @@ interface IAelinPool { NftCollectionRules[] nftCollectionRules; } - // collectionAddress should be unique, otherwise will override + /** + * @dev The collectionAddress should be unique, otherwise will override pre-existing storage. + * NOTE If purchaseAmount equals zero, then unlimited purchase amounts are allowed. Additionally, + * both the tokenIds and minTokensEligible arrays are only applicable for deals involving ERC1155 + * collections. + */ struct NftCollectionRules { - // if 0, then unlimited purchase uint256 purchaseAmount; address collectionAddress; - // An array of Id Ranges for gating specific nfts in unique erc721 collections (e.g. POAP) + // Ranges for 721s IdRange[] idRanges; - // both variables below are only applicable for 1155 + // Ids and minimums for 1155s uint256[] tokenIds; - // min number of tokens required for participating uint256[] minTokensEligible; } - struct NftPurchaseList { - address collectionAddress; - uint256[] tokenIds; - } - + /** + * NOTE The range is inclusive of beginning and ending token Ids. + */ struct IdRange { uint256 begin; uint256 end; } + + struct NftPurchaseList { + address collectionAddress; + uint256[] tokenIds; + } } diff --git a/contracts/interfaces/IAelinUpFrontDeal.sol b/contracts/interfaces/IAelinUpFrontDeal.sol index b0e6df1b..05b4074b 100644 --- a/contracts/interfaces/IAelinUpFrontDeal.sol +++ b/contracts/interfaces/IAelinUpFrontDeal.sol @@ -16,14 +16,40 @@ interface IAelinUpFrontDeal { struct UpFrontDealConfig { uint256 underlyingDealTokenTotal; - uint256 purchaseTokenPerDealToken; uint256 purchaseRaiseMinimum; uint256 purchaseDuration; - uint256 vestingPeriod; - uint256 vestingCliffPeriod; + VestingSchedule[] vestingSchedules; bool allowDeallocation; } + struct VestingSchedule { + uint256 purchaseTokenPerDealToken; + uint256 vestingCliffPeriod; + uint256 vestingPeriod; + } + + event CreateUpFrontDeal( + address indexed dealAddress, + string name, + string symbol, + address purchaseToken, + address underlyingDealToken, + address indexed holder, + address indexed sponsor, + uint256 sponsorFee, + bytes32 merkleRoot, + string ipfsHash + ); + + event CreateUpFrontDealConfig( + address indexed dealAddress, + uint256 underlyingDealTokenTotal, + uint256 purchaseRaiseMinimum, + uint256 purchaseDuration, + VestingSchedule[] vestingSchedules, + bool allowDeallocation + ); + event DepositDealToken( address indexed underlyingDealTokenAddress, address indexed depositor, @@ -34,14 +60,15 @@ interface IAelinUpFrontDeal { address upFrontDealAddress, uint256 timestamp, uint256 purchaseExpiryTimestamp, - uint256 vestingCliffExpiryTimestamp, - uint256 vestingExpiryTimestamp + uint256[] vestingCliffExpiryTimestamps, + uint256[] vestingExpiryTimestamps ); event WithdrewExcess(address upFrontDealAddress, uint256 amountWithdrawn); event AcceptDeal( address indexed user, + uint256 vestingIndex, uint256 amountPurchased, uint256 totalPurchased, uint256 amountDealTokens, diff --git a/contracts/interfaces/IAelinVestingToken.sol b/contracts/interfaces/IAelinVestingToken.sol index dffc9287..f88ffff1 100644 --- a/contracts/interfaces/IAelinVestingToken.sol +++ b/contracts/interfaces/IAelinVestingToken.sol @@ -5,9 +5,16 @@ interface IAelinVestingToken { struct VestingDetails { uint256 share; uint256 lastClaimedAt; + uint256 vestingIndex; } - event VestingTokenMinted(address indexed user, uint256 indexed tokenId, uint256 amount, uint256 lastClaimedAt); + event VestingTokenMinted( + address indexed user, + uint256 indexed tokenId, + uint256 amount, + uint256 lastClaimedAt, + uint256 vestingIndex + ); event VestingTokenBurned(uint256 indexed tokenId); diff --git a/contracts/libraries/AelinAllowList.sol b/contracts/libraries/AelinAllowList.sol index ecfab5fe..1b6c7efd 100644 --- a/contracts/libraries/AelinAllowList.sol +++ b/contracts/libraries/AelinAllowList.sol @@ -14,6 +14,12 @@ library AelinAllowList { bool hasAllowList; } + /** + * @notice This function stores any Allow List information for a deal if there is any. + * @param _init The Allow List information that is to be stored. This includes an array of + * addresses and an array of corresponding amounts that will be mapped together in storage. + * @param _self The storage struct for the Allow List information. + */ function initialize(InitData calldata _init, AllowList storage _self) external { if (_init.allowListAddresses.length > 0 || _init.allowListAmounts.length > 0) { require(_init.allowListAddresses.length == _init.allowListAmounts.length, "arrays should be same length"); diff --git a/contracts/libraries/AelinNftGating.sol b/contracts/libraries/AelinNftGating.sol index 9ae7c747..997e4b0f 100644 --- a/contracts/libraries/AelinNftGating.sol +++ b/contracts/libraries/AelinNftGating.sol @@ -6,30 +6,39 @@ import {NftCheck, IERC721, IERC1155} from "./NftCheck.sol"; library AelinNftGating { uint256 public constant ID_RANGES_MAX_LENGTH = 10; - // A struct that allows specific token Id ranges to be specified in a 721 collection - // Range is inclusive of beginning and ending token Ids - struct IdRange { - uint256 begin; - uint256 end; + /** + * @dev For ERC721 collections the nftId mapping specifies whether a token Id has been used. + * Conversely, for ERC1155 collections the nftId mapping specifies whether a token Id is accepted + * in the deal. + */ + struct NftGatingData { + mapping(address => NftCollectionRules) nftCollectionDetails; + mapping(address => mapping(uint256 => bool)) nftId; + bool hasNftList; } - // collectionAddress should be unique, otherwise will override + /** + * @dev The collectionAddress should be unique, otherwise will override pre-existing storage. + * NOTE If purchaseAmount equals zero, then unlimited purchase amounts are allowed. Additionally, + * both the tokenIds and minTokensEligible arrays are only applicable for deals involving ERC1155 + * collections. + */ struct NftCollectionRules { - // if 0, then unlimited purchase uint256 purchaseAmount; address collectionAddress; - // An array of Id Ranges for gating specific nfts in unique erc721 collections (e.g. POAP) + // Ranges for 721s IdRange[] idRanges; - // both variables below are only applicable for 1155 + // Ids and minimums for 1155s uint256[] tokenIds; - // min number of tokens required for participating uint256[] minTokensEligible; } - struct NftGatingData { - mapping(address => NftCollectionRules) nftCollectionDetails; - mapping(address => mapping(uint256 => bool)) nftId; - bool hasNftList; + /** + * NOTE The range is inclusive of beginning and ending token Ids. + */ + struct IdRange { + uint256 begin; + uint256 end; } struct NftPurchaseList { @@ -38,14 +47,15 @@ library AelinNftGating { } /** - * @dev check if deal is nft gated, sets hasNftList - * if yes, move collection rule array input to mapping in the data storage - * @param _nftCollectionRules array of all nft collection rule data - * @param _data contract storage data passed by reference + * @notice This function helps to set up an NFT-gated deal. + * @dev Checks if deal is NFT-gated, stores NFT-gated collection rule information, and + * sets hasNftList. + * @param _nftCollectionRules An array of all NftCollectionRules for the deal. + * @param _data The contract storage data where the NftCollectionRules data is stored. */ function initialize(NftCollectionRules[] calldata _nftCollectionRules, NftGatingData storage _data) external { if (_nftCollectionRules.length > 0) { - // if the first address supports 721, the entire pool only supports 721 + // If the first address supports 721, the entire pool only supports 721 if (NftCheck.supports721(_nftCollectionRules[0].collectionAddress)) { for (uint256 i; i < _nftCollectionRules.length; ++i) { require(NftCheck.supports721(_nftCollectionRules[i].collectionAddress), "can only contain 721"); @@ -65,7 +75,7 @@ library AelinNftGating { } _data.hasNftList = true; } - // if the first address supports 1155, the entire pool only supports 1155 + // If the first address supports 1155, the entire pool only supports 1155 else if (NftCheck.supports1155(_nftCollectionRules[0].collectionAddress)) { for (uint256 i; i < _nftCollectionRules.length; ++i) { require(NftCheck.supports1155(_nftCollectionRules[i].collectionAddress), "can only contain 1155"); @@ -92,16 +102,14 @@ library AelinNftGating { } /** - * @dev allows anyone to become a purchaser with a qualified erc721 - * nft in the pool depending on the scenarios - * - * Scenarios: - * 1. each wallet holding a qualified NFT to deposit an unlimited amount of purchase tokens - * 2. certain amount of Investment tokens per qualified NFT held - * @param _nftPurchaseList nft collection address and token ids to use for purchase - * @param _data contract storage data for nft gating passed by reference - * @param _purchaseTokenAmount amount to purchase with, must not exceed max allowable from collection rules - * @return uint256 max purchase token amount allowable + * @notice This function allows anyone to become a purchaser with a qualified ERC721 nft in the pool. + * @dev Multiple scenarios supported: + * 1. Each wallet holding a qualified NFT to deposit an unlimited amount of purchase tokens. + * 2. A certain amount of investment tokens per qualified NFT held. + * @param _nftPurchaseList NFT collection address and token Ids to use for purchase. + * @param _data Contract storage data for NFT-gating passed by reference. + * @param _purchaseTokenAmount Amount to purchase with, must not exceed max allowable from collection rules. + * @return uint256 Max purchase token amount allowable. */ function purchaseDealTokensWithNft( NftPurchaseList[] calldata _nftPurchaseList, @@ -119,10 +127,10 @@ library AelinNftGating { uint256 tokenIdsLength; NftCollectionRules memory nftCollectionRules; - //The running total for 721 tokens + // The running total for 721 tokens uint256 maxPurchaseTokenAmount; - //Iterate over the collections + // Iterate over the collections for (uint256 i; i < nftPurchaseListLength; ++i) { nftPurchaseList = _nftPurchaseList[i]; collectionAddress = nftPurchaseList.collectionAddress; @@ -133,7 +141,7 @@ library AelinNftGating { require(collectionAddress != address(0), "collection should not be null"); require(nftCollectionRules.collectionAddress == collectionAddress, "collection not in the pool"); - //Iterate over the token ids + // Iterate over the token ids for (uint256 j; j < tokenIdsLength; ++j) { if (NftCheck.supports721(collectionAddress)) { require(IERC721(collectionAddress).ownerOf(tokenIds[j]) == msg.sender, "has to be the token owner"); @@ -147,7 +155,7 @@ library AelinNftGating { _data.nftId[collectionAddress][tokenIds[j]] = true; emit BlacklistNFT(collectionAddress, tokenIds[j]); } else { - //Must otherwise be an 1155 given initialise function + // Must otherwise be an 1155 given initialise function require(_data.nftId[collectionAddress][tokenIds[j]], "tokenId not in the pool"); require( IERC1155(collectionAddress).balanceOf(msg.sender, tokenIds[j]) >= diff --git a/contracts/libraries/MerkleTree.sol b/contracts/libraries/MerkleTree.sol index f6c899f7..cbd579f3 100644 --- a/contracts/libraries/MerkleTree.sol +++ b/contracts/libraries/MerkleTree.sol @@ -16,8 +16,14 @@ library MerkleTree { } /** - * @dev a function that checks if the index leaf node is valid and if the user has purchased. - * will set the index node to purchased if approved + * @notice This function allows users to verify their allocation in a deal with a merkle proof. + * @dev Checks if the index leaf node is valid and if the user has purchased. It will set the index node + * to purchased if approved. + * @param merkleData The merkle data struct specifying the index, account, amount, and proof of a user's + * allocation in a deal. + * @param self The Bitmap storage location for tracking already claimed tokens using a merkle proof. + * @param _purchaseTokenAmount The amount of tokens to purchase. + * @param merkleRoot The merkle root used to verify the merkle data and a user's allocation. */ function purchaseMerkleAmount( UpFrontMerkleData calldata merkleData, @@ -40,7 +46,7 @@ library MerkleTree { } /** - * @dev sets the claimedBitMap to true for that index + * @dev Sets the claimedBitMap to true for that index. */ function _setPurchased(TrackClaimed storage self, uint256 _index) private { uint256 claimedWordIndex = _index / 256; @@ -49,8 +55,11 @@ library MerkleTree { } /** - * @dev returns if address has purchased merkle - * @return bool index of the leaf node has purchased or not + * @notice This is a view function that returns a boolean specifying whether or not an account has + * has purchased tokens from a deal using a merkle proof. + * @param self The Bitmap storage location for tracking already claimed tokens using a merkle proof. + * @param _index The index of the merkle data to be tested. + * @return bool Returns true if the index of the leaf node has purchased, and false if not. */ function hasPurchasedMerkle(TrackClaimed storage self, uint256 _index) public view returns (bool) { uint256 claimedWordIndex = _index / 256; diff --git a/test/tests/AelinDeal.t.sol b/test/tests/AelinDeal.t.sol index 724cabcb..a332c6b3 100644 --- a/test/tests/AelinDeal.t.sol +++ b/test/tests/AelinDeal.t.sol @@ -212,13 +212,13 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { (uint256 openPeriod, , ) = AelinDeal(dealAddress).openRedemption(); vm.assume(_depositAmount < type(uint256).max); vm.assume(_depositAmount >= AelinDeal(dealAddress).underlyingDealTokenTotal()); - // anybody can call this function - vm.startPrank(user1); - deal(address(underlyingDealToken), user1, type(uint256).max); + // only the holder can call this function + vm.startPrank(dealHolderAddress); + deal(address(underlyingDealToken), dealHolderAddress, type(uint256).max); underlyingDealToken.approve(address(dealAddress), type(uint256).max); vm.expectEmit(true, true, true, true); - emit DepositDealToken(address(underlyingDealToken), user1, _depositAmount); + emit DepositDealToken(address(underlyingDealToken), dealHolderAddress, _depositAmount); vm.expectEmit(true, true, true, true); emit DealFullyFunded( poolAddress, @@ -719,7 +719,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { // we assume user is the first claimer so owns deal token ID = 0; uint256 vestingExpiry = AelinDeal(dealNoOpenRedemptionAddress).vestingExpiry(); uint256 vestingPeriod = AelinDeal(dealNoOpenRedemptionAddress).vestingPeriod(); - (uint256 share, uint256 lastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); assertEq(lastClaimedAt, AelinDeal(dealNoOpenRedemptionAddress).vestingCliffExpiry()); // user can claim a part of their deal tokens vm.warp(AelinDeal(dealNoOpenRedemptionAddress).vestingCliffExpiry() + 1 days); @@ -736,7 +736,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { AelinDeal(dealNoOpenRedemptionAddress).claimUnderlyingTokens(0); // post claim checks - (, lastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (, lastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); assertEq(AelinDeal(dealNoOpenRedemptionAddress).balanceOf(dealCreatorAddress), 1); assertEq(lastClaimedAt, block.timestamp, "lastClaimedAt"); assertEq(underlyingDealToken.balanceOf(dealCreatorAddress), claimableAmount, "userDealTokenBalance"); @@ -759,7 +759,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { AelinDeal(dealNoOpenRedemptionAddress).claimUnderlyingTokens(0); // post claim checks - (, lastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (, lastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); assertEq(lastClaimedAt, block.timestamp, "lastClaimedAt"); assertEq(underlyingDealToken.balanceOf(dealCreatorAddress), 2 * claimableAmount, "userDealTokenBalance"); assertEq( @@ -782,7 +782,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { uint256 totalShareInUnderlying = (AelinDeal(dealNoOpenRedemptionAddress).underlyingPerDealExchangeRate() * share) / 1e18; // post claim checks - (, lastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (, lastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); assertEq(lastClaimedAt, 0, "lastClaimedAt"); assertEq(underlyingDealToken.balanceOf(dealCreatorAddress), totalShareInUnderlying, "userDealTokenBalance"); assertEq( @@ -804,7 +804,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { AelinPool(poolNoOpenRedemptionDealAddress).acceptMaxDealTokens(); // we assume user is the first claimer so owns deal token ID = 0; uint256 vestingExpiry = AelinDeal(dealNoOpenRedemptionAddress).vestingExpiry(); - (uint256 share, uint256 lastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); assertEq(lastClaimedAt, AelinDeal(dealNoOpenRedemptionAddress).vestingCliffExpiry()); // we wait until vesting period ends @@ -822,7 +822,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { assertEq(AelinDeal(dealNoOpenRedemptionAddress).balanceOf(dealCreatorAddress), 0); // post claim checks - (, lastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (, lastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); assertEq(lastClaimedAt, 0, "lastClaimedAt"); assertEq(underlyingDealToken.balanceOf(dealCreatorAddress), claimableAmount, "userDealTokenBalance"); assertEq( @@ -847,7 +847,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { AelinDeal deal = AelinDeal(dealNoOpenRedemptionAddress); // lastClaimedAt is 0 - (uint256 share, uint256 lastClaimedAt) = deal.vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = deal.vestingDetails(0); assertEq(lastClaimedAt, 0); assertEq(deal.claimableUnderlyingTokens(0), 0); @@ -855,7 +855,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { vm.startPrank(dealCreatorAddress); pool.acceptMaxDealTokens(); vm.stopPrank(); - (share, lastClaimedAt) = deal.vestingDetails(0); + (share, lastClaimedAt, ) = deal.vestingDetails(0); // Before vesting Cliff expiry assertGt(lastClaimedAt, 0); @@ -872,7 +872,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { // After vesting expiry vm.warp(deal.vestingCliffExpiry() + deal.vestingPeriod() + 1 days); - (share, lastClaimedAt) = deal.vestingDetails(0); + (share, lastClaimedAt, ) = deal.vestingDetails(0); maxTime = deal.vestingExpiry(); minTime = lastClaimedAt; claimableAmount = @@ -887,14 +887,14 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { AelinDeal deal = AelinDeal(dealNoOpenRedemptionNoVestingPeriodAddress); // lastClaimedAt is 0 - (uint256 share, uint256 lastClaimedAt) = deal.vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = deal.vestingDetails(0); assertEq(lastClaimedAt, 0); assertEq(deal.claimableUnderlyingTokens(0), 0); // lastClaimedAt > 0 vm.startPrank(dealCreatorAddress); pool.acceptMaxDealTokens(); - (share, lastClaimedAt) = deal.vestingDetails(0); + (share, lastClaimedAt, ) = deal.vestingDetails(0); // Before vesting Cliff expiry assertGt(lastClaimedAt, 0); @@ -1015,8 +1015,8 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { indices[0] = 0; indices[1] = 1; vm.expectEmit(true, true, false, true); - (uint256 share1, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); - (uint256 share2, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (uint256 share1, , ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (uint256 share2, , ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); emit ClaimedUnderlyingDealToken(user1, 0, address(underlyingDealToken), share1); emit ClaimedUnderlyingDealToken(user1, 0, address(underlyingDealToken), share2); AelinDeal(dealAddr).claimUnderlyingMultipleEntries(indices); @@ -1111,14 +1111,14 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { assertEq(AelinDeal(dealAddr).tokenCount(), 0); vm.expectEmit(true, true, false, true); emit AcceptDeal(user1, dealAddr, depositAmount / 2, sponsorFee, aelinFee); - emit VestingTokenMinted(user1, 0, computedShareAmount, AelinDeal(dealAddr).vestingCliffExpiry()); + emit VestingTokenMinted(user1, 0, computedShareAmount, AelinDeal(dealAddr).vestingCliffExpiry(), 0); AelinPool(poolAddr).acceptMaxDealTokens(); // user1 now owns a vesting token assertEq(AelinDeal(dealAddr).tokenCount(), 1, "tokenCount"); assertEq(AelinDeal(dealAddr).ownerOf(0), user1, "ownerOf"); assertEq(AelinDeal(dealAddr).balanceOf(user1), 1); - (uint256 share, uint256 lastClaimedAt) = AelinDeal(dealAddr).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinDeal(dealAddr).vestingDetails(0); assertEq(share, computedShareAmount, "shareAmount"); assertEq(lastClaimedAt, AelinDeal(dealAddr).vestingCliffExpiry()); @@ -1128,14 +1128,14 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { vm.startPrank(user2); vm.expectEmit(true, true, false, true); emit AcceptDeal(user2, dealAddr, depositAmount / 2, sponsorFee, aelinFee); - emit VestingTokenMinted(user2, 1, computedShareAmount, AelinDeal(dealAddr).vestingCliffExpiry()); + emit VestingTokenMinted(user2, 1, computedShareAmount, AelinDeal(dealAddr).vestingCliffExpiry(), 0); AelinPool(poolAddr).acceptMaxDealTokens(); // user2 now owns a vesting token assertEq(AelinDeal(dealAddr).tokenCount(), 2, "tokenCount"); assertEq(AelinDeal(dealAddr).ownerOf(1), user2, "ownerOf"); assertEq(AelinDeal(dealAddr).balanceOf(user2), 1); - (share, lastClaimedAt) = AelinDeal(dealAddr).vestingDetails(1); + (share, lastClaimedAt, ) = AelinDeal(dealAddr).vestingDetails(1); assertEq(share, computedShareAmount, "shareAmount"); assertEq(lastClaimedAt, AelinDeal(dealAddr).vestingCliffExpiry()); @@ -1269,7 +1269,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { AelinPool(poolNoOpenRedemptionDealAddress).acceptMaxDealTokens(); assertEq(AelinDeal(dealNoOpenRedemptionAddress).balanceOf(dealCreatorAddress), 1); assertEq(AelinDeal(dealNoOpenRedemptionAddress).ownerOf(0), dealCreatorAddress); - (uint256 shareInitial, uint256 lastClaimedAtInitial) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (uint256 shareInitial, uint256 lastClaimedAtInitial, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); // dealCreator user transfers their token to user1 AelinDeal(dealNoOpenRedemptionAddress).transfer(user1, 0, "0x0"); @@ -1284,7 +1284,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { assertEq(AelinDeal(dealNoOpenRedemptionAddress).balanceOf(user1), 0); assertEq(AelinDeal(dealNoOpenRedemptionAddress).balanceOf(user2), 1); assertEq(AelinDeal(dealNoOpenRedemptionAddress).ownerOf(0), user2); - (uint256 share, uint256 lastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); uint256 claimableAmount = (AelinDeal(dealNoOpenRedemptionAddress).underlyingPerDealExchangeRate() * share) / 1e18; // we check that vesting schedule is identical @@ -1358,7 +1358,7 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { assertEq(AelinDeal(dealNoOpenRedemptionAddress).balanceOf(dealCreatorAddress), 1); assertEq(AelinDeal(dealNoOpenRedemptionAddress).ownerOf(0), dealCreatorAddress); - (uint256 share, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (uint256 share, , ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); // dealCreator user sends more than their share vm.expectRevert("amout gt than current share"); AelinDeal(dealNoOpenRedemptionAddress).transferVestingShare(user2, 0, share + 1); @@ -1373,14 +1373,14 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { assertEq(AelinDeal(dealNoOpenRedemptionAddress).balanceOf(dealCreatorAddress), 1); assertEq(AelinDeal(dealNoOpenRedemptionAddress).ownerOf(0), dealCreatorAddress); - (uint256 share, uint256 lastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); vm.assume(_shareAmount > 1e18); vm.assume(_shareAmount < share); // dealCreator user transfers a part of their share to user1 assertEq(AelinDeal(dealNoOpenRedemptionAddress).balanceOf(user1), 0); vm.expectEmit(true, true, false, true); - emit VestingTokenMinted(user1, 1, _shareAmount, lastClaimedAt); + emit VestingTokenMinted(user1, 1, _shareAmount, lastClaimedAt, 0); vm.expectEmit(true, true, true, true); emit VestingShareTransferred(dealCreatorAddress, user1, 0, _shareAmount); AelinDeal(dealNoOpenRedemptionAddress).transferVestingShare(user1, 0, _shareAmount); @@ -1390,12 +1390,12 @@ contract AelinDealTest is Test, AelinTestUtils, IAelinDeal, IAelinVestingToken { assertEq(AelinDeal(dealNoOpenRedemptionAddress).ownerOf(1), user1); // dealCreator has their share reduced - (uint256 newShare, uint256 newLastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); + (uint256 newShare, uint256 newLastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(0); assertEq(newShare, share - _shareAmount); assertEq(newLastClaimedAt, lastClaimedAt); // user1 now has their share - (newShare, newLastClaimedAt) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(1); + (newShare, newLastClaimedAt, ) = AelinDeal(dealNoOpenRedemptionAddress).vestingDetails(1); assertEq(newShare, _shareAmount); assertEq(newLastClaimedAt, lastClaimedAt); diff --git a/test/tests/AelinFeeEscrow.t.sol b/test/tests/AelinFeeEscrow.t.sol index ee21059f..c257b01f 100644 --- a/test/tests/AelinFeeEscrow.t.sol +++ b/test/tests/AelinFeeEscrow.t.sol @@ -64,14 +64,18 @@ contract AelinFeeEscrowTest is Test, AelinTestUtils { merkleRoot: 0x0000000000000000000000000000000000000000000000000000000000000000 }); + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](1); + + vestingSchedules[0].purchaseTokenPerDealToken = 3e20; + vestingSchedules[0].vestingCliffPeriod = 60 days; + vestingSchedules[0].vestingPeriod = 365 days; + IAelinUpFrontDeal.UpFrontDealConfig memory dealConfig; dealConfig = IAelinUpFrontDeal.UpFrontDealConfig({ underlyingDealTokenTotal: 1e35, - purchaseTokenPerDealToken: 3e20, purchaseRaiseMinimum: 0, purchaseDuration: 10 days, - vestingPeriod: 365 days, - vestingCliffPeriod: 60 days, + vestingSchedules: vestingSchedules, allowDeallocation: true }); @@ -94,7 +98,7 @@ contract AelinFeeEscrowTest is Test, AelinTestUtils { vm.startPrank(user1); deal(address(purchaseToken), user1, 3e18); purchaseToken.approve(address(upfrontDeal), 3e18); - AelinUpFrontDeal(upfrontDeal).acceptDeal(nftPurchaseList, merkleDataEmpty, 3e18); + AelinUpFrontDeal(upfrontDeal).acceptDeal(nftPurchaseList, merkleDataEmpty, 3e18, 0); vm.stopPrank(); // Holder claim diff --git a/test/tests/AelinPool.misc.t.sol b/test/tests/AelinPool.misc.t.sol index f9226245..fe285c4a 100644 --- a/test/tests/AelinPool.misc.t.sol +++ b/test/tests/AelinPool.misc.t.sol @@ -87,7 +87,13 @@ contract AelinPoolMiscTest is Test, AelinTestUtils { event SetSponsor(address indexed sponsor); event Vouch(address indexed voucher); event Disavow(address indexed voucher); - event VestingTokenMinted(address indexed user, uint256 indexed tokenId, uint256 amount, uint256 lastClaimedAt); + event VestingTokenMinted( + address indexed user, + uint256 indexed tokenId, + uint256 amount, + uint256 lastClaimedAt, + uint256 vestingIndex + ); /*////////////////////////////////////////////////////////////// helpers @@ -2209,7 +2215,7 @@ contract AelinPoolMiscTest is Test, AelinTestUtils { ); assertEq(MockERC20(purchaseToken).balanceOf(user2), acceptedAmount); - (uint256 share, uint256 lastClaimedAt) = AelinDeal(poolVars.dealAddress).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinDeal(poolVars.dealAddress).vestingDetails(0); assertEq(AelinDeal(poolVars.dealAddress).ownerOf(0), user1); assertEq(share, poolTokenDealFormatted - (sponsorFeeAmount + aelinFeeAmount)); assertEq(lastClaimedAt, AelinDeal(poolVars.dealAddress).vestingCliffExpiry()); @@ -2309,7 +2315,7 @@ contract AelinPoolMiscTest is Test, AelinTestUtils { ); assertEq(MockERC20(purchaseToken).balanceOf(user2), acceptedAmount); - (uint256 share, uint256 lastClaimedAt) = AelinDeal(poolVars.dealAddress).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinDeal(poolVars.dealAddress).vestingDetails(0); assertEq(AelinDeal(poolVars.dealAddress).ownerOf(0), user1); assertEq(share, poolTokenDealFormatted - (sponsorFeeAmount + aelinFeeAmount)); assertEq(lastClaimedAt, AelinDeal(poolVars.dealAddress).vestingCliffExpiry()); @@ -2409,7 +2415,7 @@ contract AelinPoolMiscTest is Test, AelinTestUtils { ); assertEq(MockERC20(purchaseToken).balanceOf(user2), acceptedAmount); - (uint256 share, uint256 lastClaimedAt) = AelinDeal(poolVars.dealAddress).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinDeal(poolVars.dealAddress).vestingDetails(0); assertEq(AelinDeal(poolVars.dealAddress).ownerOf(0), user1); assertEq(share, poolTokenDealFormatted - (sponsorFeeAmount + aelinFeeAmount)); assertEq(lastClaimedAt, AelinDeal(poolVars.dealAddress).vestingCliffExpiry()); @@ -2513,7 +2519,7 @@ contract AelinPoolMiscTest is Test, AelinTestUtils { ); assertEq(MockERC20(purchaseToken).balanceOf(user2), acceptedAmount); - (uint256 share, uint256 lastClaimedAt) = AelinDeal(poolVars.dealAddress).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinDeal(poolVars.dealAddress).vestingDetails(0); assertEq(AelinDeal(poolVars.dealAddress).ownerOf(0), user1); assertEq(share, poolTokenDealFormatted - (sponsorFeeAmount + aelinFeeAmount)); assertEq(lastClaimedAt, AelinDeal(poolVars.dealAddress).vestingCliffExpiry()); @@ -3756,7 +3762,8 @@ contract AelinPoolMiscTest is Test, AelinTestUtils { user1, 1, poolVars.pool.totalSponsorFeeAmount(), - AelinDeal(poolVars.dealAddress).vestingCliffExpiry() + AelinDeal(poolVars.dealAddress).vestingCliffExpiry(), + 0 ); poolVars.pool.sponsorClaim(); vm.stopPrank(); diff --git a/test/tests/AelinUpFontDeal.claim.t.sol b/test/tests/AelinUpFontDeal.claim.t.sol index bcedd2b3..416bfb9d 100644 --- a/test/tests/AelinUpFontDeal.claim.t.sol +++ b/test/tests/AelinUpFontDeal.claim.t.sol @@ -32,6 +32,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I address dealAddressNftGating1155; address dealAddressNoVestingPeriod; address dealAddressLowDecimals; + address dealAddressMultipleVestingSchedules; function setUp() public { AelinAllowList.InitData memory allowListEmpty; @@ -46,9 +47,11 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I IAelinUpFrontDeal.UpFrontDealData memory dealData = getDealData(); IAelinUpFrontDeal.UpFrontDealConfig memory dealConfig = getDealConfig(); IAelinUpFrontDeal.UpFrontDealConfig memory dealConfigAllowDeallocation = getDealConfigAllowDeallocation(); + IAelinUpFrontDeal.UpFrontDealConfig + memory dealConfigMultipleVestingSchedules = getDealConfigMultipleVestingSchedules(); IAelinUpFrontDeal.UpFrontDealConfig memory dealConfigNoVestingPeriod = getDealConfig(); - dealConfigNoVestingPeriod.vestingPeriod = 0; + dealConfigNoVestingPeriod.vestingSchedules[0].vestingPeriod = 0; IAelinUpFrontDeal.UpFrontDealData memory dealDataLowDecimals = getDealData(); dealDataLowDecimals.underlyingDealToken = address(underlyingDealTokenLowDecimals); @@ -119,6 +122,13 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I allowListEmpty ); + dealAddressMultipleVestingSchedules = upFrontDealFactory.createUpFrontDeal( + dealData, + dealConfigMultipleVestingSchedules, + nftCollectionRulesEmpty, + allowListEmpty + ); + vm.stopPrank(); vm.startPrank(dealHolderAddress); @@ -149,6 +159,9 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I underlyingDealToken.approve(address(dealAddressNftGating1155), type(uint256).max); AelinUpFrontDeal(dealAddressNftGating1155).depositUnderlyingTokens(1e35); + underlyingDealToken.approve(address(dealAddressMultipleVestingSchedules), type(uint256).max); + AelinUpFrontDeal(dealAddressMultipleVestingSchedules).depositUnderlyingTokens(1e35); + vm.stopPrank(); } @@ -205,9 +218,9 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.assume(_user != address(0)); vm.startPrank(_user); vm.expectRevert("underlying deposit incomplete"); - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).purchaserClaim(); + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).purchaserClaim(0); vm.expectRevert("purchase period not over"); - AelinUpFrontDeal(dealAddressNoDeallocation).purchaserClaim(); + AelinUpFrontDeal(dealAddressNoDeallocation).purchaserClaim(0); vm.stopPrank(); } @@ -217,7 +230,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I uint256 purchaseExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).purchaseExpiry(); vm.warp(purchaseExpiry + 1 days); vm.expectRevert("no pool shares to claim with"); - AelinUpFrontDeal(dealAddressNoDeallocation).purchaserClaim(); + AelinUpFrontDeal(dealAddressNoDeallocation).purchaserClaim(0); vm.stopPrank(); } @@ -260,17 +273,17 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I dealVars.purchaseTokenPerDealToken; vm.expectEmit(true, true, true, true); - emit AcceptDeal(user1, _purchaseAmount, _purchaseAmount, poolSharesAmount, poolSharesAmount); - fuzzed.upFrontDeal.acceptDeal(nftPurchaseListEmpty, merkleDataEmpty, _purchaseAmount); + emit AcceptDeal(user1, 0, _purchaseAmount, _purchaseAmount, poolSharesAmount, poolSharesAmount); + fuzzed.upFrontDeal.acceptDeal(nftPurchaseListEmpty, merkleDataEmpty, _purchaseAmount, 0); assertEq(fuzzed.upFrontDeal.totalPurchasingAccepted(), _purchaseAmount); - assertEq(fuzzed.upFrontDeal.purchaseTokensPerUser(user1), _purchaseAmount); + assertEq(fuzzed.upFrontDeal.purchaseTokensPerUser(user1, 0), _purchaseAmount); assertEq(IERC20(fuzzed.dealData.purchaseToken).balanceOf(user1), type(uint256).max - _purchaseAmount); // purchase period is over, user1 tries to claim and gets a refund instead vm.warp(dealVars.purchaseDuration + 2); vm.expectEmit(true, true, true, true); emit ClaimDealTokens(user1, 0, _purchaseAmount); - fuzzed.upFrontDeal.purchaserClaim(); + fuzzed.upFrontDeal.purchaserClaim(0); assertEq(IERC20(fuzzed.dealData.purchaseToken).balanceOf(user1), type(uint256).max); vm.stopPrank(); @@ -315,30 +328,31 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I MockERC20(fuzzed.dealData.purchaseToken).approve(address(fuzzed.upFrontDeal), type(uint256).max); vm.expectEmit(true, true, true, true); - emit AcceptDeal(user1, _purchaseAmount, _purchaseAmount, poolSharesAmount, poolSharesAmount); - fuzzed.upFrontDeal.acceptDeal(nftPurchaseListEmpty, merkleDataEmpty, _purchaseAmount); + emit AcceptDeal(user1, 0, _purchaseAmount, _purchaseAmount, poolSharesAmount, poolSharesAmount); + fuzzed.upFrontDeal.acceptDeal(nftPurchaseListEmpty, merkleDataEmpty, _purchaseAmount, 0); assertEq(fuzzed.upFrontDeal.totalPurchasingAccepted(), _purchaseAmount); - assertEq(fuzzed.upFrontDeal.purchaseTokensPerUser(user1), _purchaseAmount); + assertEq(fuzzed.upFrontDeal.purchaseTokensPerUser(user1, 0), _purchaseAmount); assertEq(IERC20(fuzzed.dealData.purchaseToken).balanceOf(user1), type(uint256).max - _purchaseAmount); // purchase period is over and user1 tries to claim vm.warp(dealVars.purchaseDuration + 2); - assertEq(fuzzed.upFrontDeal.poolSharesPerUser(user1), poolSharesAmount); + assertEq(fuzzed.upFrontDeal.poolSharesPerUser(user1, 0), poolSharesAmount); uint256 adjustedShareAmountForUser = ((BASE - AELIN_FEE - dealVars.sponsorFee) * poolSharesAmount) / BASE; uint256 tokenCount = fuzzed.upFrontDeal.tokenCount(); vm.expectEmit(true, true, true, true); - emit VestingTokenMinted(user1, tokenCount, adjustedShareAmountForUser, dealVars.purchaseDuration + 1); + emit VestingTokenMinted(user1, tokenCount, adjustedShareAmountForUser, dealVars.purchaseDuration + 1, 0); vm.expectEmit(true, false, false, true); emit ClaimDealTokens(user1, adjustedShareAmountForUser, 0); - fuzzed.upFrontDeal.purchaserClaim(); + fuzzed.upFrontDeal.purchaserClaim(0); vm.stopPrank(); } function testFuzz_PurchaserClaim_AllowDeallocation(uint256 _purchaseAmount1, uint256 _purchaseAmount2) public { - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal( - dealAddressAllowDeallocation - ).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).getVestingScheduleDetails( + 0 + ); (, , , , , , uint256 sponsorFee, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealData(); uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); AelinNftGating.NftPurchaseList[] memory nftPurchaseList; @@ -357,10 +371,10 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressAllowDeallocation), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, _purchaseAmount1, _purchaseAmount1, poolSharesAmount1, poolSharesAmount1); - AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount1); + emit AcceptDeal(user1, 0, _purchaseAmount1, _purchaseAmount1, poolSharesAmount1, poolSharesAmount1); + AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount1, 0); assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).totalPurchasingAccepted(), _purchaseAmount1); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user1), _purchaseAmount1); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user1, 0), _purchaseAmount1); assertEq(IERC20(address(purchaseToken)).balanceOf(user1), type(uint256).max - _purchaseAmount1); vm.stopPrank(); @@ -369,13 +383,13 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I deal(address(purchaseToken), user2, type(uint256).max); purchaseToken.approve(address(dealAddressAllowDeallocation), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user2, _purchaseAmount2, _purchaseAmount2, poolSharesAmount2, poolSharesAmount2); - AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount2); + emit AcceptDeal(user2, 0, _purchaseAmount2, _purchaseAmount2, poolSharesAmount2, poolSharesAmount2); + AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount2, 0); assertEq( AelinUpFrontDeal(dealAddressAllowDeallocation).totalPurchasingAccepted(), _purchaseAmount1 + _purchaseAmount2 ); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user2), _purchaseAmount2); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user2, 0), _purchaseAmount2); assertEq(IERC20(address(purchaseToken)).balanceOf(user2), type(uint256).max - _purchaseAmount2); vm.stopPrank(); @@ -384,7 +398,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // user1 tries to claim vm.startPrank(user1); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user1), poolSharesAmount1); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user1, 0), poolSharesAmount1); uint256 adjustedShareAmountForUser1 = (((poolSharesAmount1 * underlyingDealTokenTotal) / AelinUpFrontDeal(dealAddressAllowDeallocation).totalPoolShares()) * (BASE - AELIN_FEE - sponsorFee)) / BASE; uint256 refundAmount = _purchaseAmount1 - @@ -396,17 +410,18 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I user1, AelinUpFrontDeal(dealAddressAllowDeallocation).tokenCount(), adjustedShareAmountForUser1, - AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseExpiry() + AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseExpiry(), + 0 ); vm.expectEmit(true, false, false, true); emit ClaimDealTokens(user1, adjustedShareAmountForUser1, refundAmount); - AelinUpFrontDeal(dealAddressAllowDeallocation).purchaserClaim(); + AelinUpFrontDeal(dealAddressAllowDeallocation).purchaserClaim(0); vm.stopPrank(); // post claim checks assertEq(IERC20(address(purchaseToken)).balanceOf(user1), type(uint256).max - _purchaseAmount1 + refundAmount); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user1), 0); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user1), 0); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user1, 0), 0); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user1, 0), 0); assertEq( AelinUpFrontDeal(dealAddressAllowDeallocation).totalPurchasingAccepted(), _purchaseAmount1 + _purchaseAmount2 @@ -415,7 +430,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // user2 tries to claim vm.startPrank(user2); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user2), poolSharesAmount2); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user2, 0), poolSharesAmount2); uint256 adjustedShareAmountForUser2 = (((poolSharesAmount2 * underlyingDealTokenTotal) / AelinUpFrontDeal(dealAddressAllowDeallocation).totalPoolShares()) * (BASE - AELIN_FEE - sponsorFee)) / BASE; refundAmount = @@ -428,17 +443,18 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I user2, AelinUpFrontDeal(dealAddressAllowDeallocation).tokenCount(), adjustedShareAmountForUser2, - AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseExpiry() + AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseExpiry(), + 0 ); vm.expectEmit(true, false, false, true); emit ClaimDealTokens(user2, adjustedShareAmountForUser2, refundAmount); - AelinUpFrontDeal(dealAddressAllowDeallocation).purchaserClaim(); + AelinUpFrontDeal(dealAddressAllowDeallocation).purchaserClaim(0); vm.stopPrank(); // post claim checks assertEq(IERC20(address(purchaseToken)).balanceOf(user2), type(uint256).max - _purchaseAmount2 + refundAmount); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user2), 0); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user2), 0); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user2, 0), 0); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user2, 0), 0); assertEq( AelinUpFrontDeal(dealAddressAllowDeallocation).totalPurchasingAccepted(), _purchaseAmount1 + _purchaseAmount2 @@ -448,14 +464,14 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // checks if user1 got their vesting token assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).balanceOf(user1), 1); assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(0), user1); - (uint256 userShare, uint256 lastClaimedAt) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); + (uint256 userShare, uint256 lastClaimedAt, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); assertEq(userShare, adjustedShareAmountForUser1); assertEq(lastClaimedAt, AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseExpiry()); // checks if user2 got their vesting token assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).balanceOf(user2), 1); assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(1), user2); - (userShare, lastClaimedAt) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(1); + (userShare, lastClaimedAt, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(1); assertEq(userShare, adjustedShareAmountForUser2); assertEq(lastClaimedAt, AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseExpiry()); } @@ -467,17 +483,16 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I function testFuzz_SponsorClaim_RevertWhen_NotInWindow(address _user) public { vm.startPrank(_user); vm.expectRevert("underlying deposit incomplete"); - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).sponsorClaim(); + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).sponsorClaim(0); vm.expectRevert("purchase period not over"); - AelinUpFrontDeal(dealAddressNoDeallocation).sponsorClaim(); + AelinUpFrontDeal(dealAddressNoDeallocation).sponsorClaim(0); vm.stopPrank(); } function testFuzz_SponsorClaim_RevertWhen_NotReachedMininumRaise(uint256 _purchaseAmount) public { uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (, uint256 purchaseTokenPerDealToken, uint256 purchaseRaiseMinimum, , , , ) = AelinUpFrontDeal( - dealAddressNoDeallocation - ).dealConfig(); + (, uint256 purchaseRaiseMinimum, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); vm.assume(_purchaseAmount > 0); vm.assume(_purchaseAmount < purchaseRaiseMinimum); uint256 poolSharesAmount = (_purchaseAmount * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; @@ -488,20 +503,20 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressNoDeallocation), type(uint256).max); AelinNftGating.NftPurchaseList[] memory nftPurchaseList; - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); console.logUint(_purchaseAmount); // purchase period is now over vm.warp(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseExpiry() + 1 days); // user tries to call sponsorClaim() and it reverts vm.expectRevert("does not pass min raise"); - AelinUpFrontDeal(dealAddressNoDeallocation).sponsorClaim(); + AelinUpFrontDeal(dealAddressNoDeallocation).sponsorClaim(0); vm.stopPrank(); // sponsor tries to call sponsorClaim() and it reverts vm.startPrank(dealCreatorAddress); vm.expectRevert("does not pass min raise"); - AelinUpFrontDeal(dealAddressNoDeallocation).sponsorClaim(); + AelinUpFrontDeal(dealAddressNoDeallocation).sponsorClaim(0); vm.stopPrank(); } @@ -515,7 +530,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // user tries to call sponsorClaim() and it reverts vm.expectRevert("must be sponsor"); - AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(); + AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(0); vm.stopPrank(); } @@ -529,11 +544,11 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // sponsor now claims vm.startPrank(dealCreatorAddress); - AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(); + AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(0); // sponsor tries to claim again and it fails vm.expectRevert("sponsor already claimed"); - AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(); + AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(0); vm.stopPrank(); } @@ -555,15 +570,15 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I uint256 totalSold = AelinUpFrontDeal(dealAddressNoDeallocation).totalPoolShares(); uint256 shareAmount = (totalSold * sponsorFee) / BASE; vm.expectEmit(true, true, false, true); - emit VestingTokenMinted(dealCreatorAddress, tokenCount, shareAmount, purchaseExpiry); + emit VestingTokenMinted(dealCreatorAddress, tokenCount, shareAmount, purchaseExpiry, 0); vm.expectEmit(true, false, false, true); emit SponsorClaim(dealCreatorAddress, shareAmount); - AelinUpFrontDeal(dealAddressNoDeallocation).sponsorClaim(); + AelinUpFrontDeal(dealAddressNoDeallocation).sponsorClaim(0); vm.stopPrank(); } function testFuzz_SponsorClaim_AllowDeallocation(uint256 _purchaseAmount) public { - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); (, , , , , , uint256 sponsorFee, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealData(); uint256 purchaseExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseExpiry(); @@ -581,11 +596,11 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I uint256 shareAmount = (underlyingDealTokenTotal * sponsorFee) / BASE; console.logUint(shareAmount); vm.expectEmit(true, true, false, true); - emit VestingTokenMinted(dealCreatorAddress, tokenCount, shareAmount, purchaseExpiry); + emit VestingTokenMinted(dealCreatorAddress, tokenCount, shareAmount, purchaseExpiry, 0); vm.expectEmit(true, false, false, true); emit SponsorClaim(dealCreatorAddress, shareAmount); - AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(); - vm.warp(AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiry()); + AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(0); + vm.warp(AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiries(0)); AelinUpFrontDeal(dealAddressAllowDeallocation).claimUnderlying(0); vm.stopPrank(); @@ -622,7 +637,9 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I function testFuzz_HolderClaim_RevertWhen_AlreadyClaimed(uint256 _purchaseAmount) public { vm.assume(_purchaseAmount > 0); uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).getVestingScheduleDetails( + 0 + ); (bool success, ) = SafeMath.tryMul(_purchaseAmount, 10 ** underlyingTokenDecimals); vm.assume(success); uint256 poolSharesAmount = (_purchaseAmount * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; @@ -648,9 +665,8 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I function testFuzz_HolderClaim_NotReachedMinimumRaise(uint256 _purchaseAmount) public { uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (, uint256 purchaseTokenPerDealToken, uint256 purchaseRaiseMinimum, , , , ) = AelinUpFrontDeal( - dealAddressNoDeallocation - ).dealConfig(); + (, uint256 purchaseRaiseMinimum, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); vm.assume(_purchaseAmount < purchaseRaiseMinimum); uint256 poolSharesAmount = (_purchaseAmount * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; vm.assume(poolSharesAmount > 0); @@ -660,7 +676,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressNoDeallocation), type(uint256).max); AelinNftGating.NftPurchaseList[] memory nftPurchaseList; - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); // purchase period is now over @@ -686,7 +702,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I } function testFuzz_HolderClaim_NoDeallocation(uint256 _purchaseAmount) public { - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); // user accepts the deal with purchaseMinimum < purchaseAmount < deal total vm.startPrank(user1); @@ -700,7 +716,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.startPrank(dealHolderAddress); uint256 amountRaise = purchaseToken.balanceOf(dealAddressNoDeallocation); uint256 amountRefund = underlyingDealTokenTotal - - AelinUpFrontDeal(dealAddressNoDeallocation).poolSharesPerUser(user1); + AelinUpFrontDeal(dealAddressNoDeallocation).poolSharesPerUser(user1, 0); uint256 amountBeforeClaim = underlyingDealToken.balanceOf(dealHolderAddress); uint256 totalPoolShares = AelinUpFrontDeal(dealAddressNoDeallocation).totalPoolShares(); uint256 feeAmount = (totalPoolShares * AELIN_FEE) / BASE; @@ -726,9 +742,10 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I function testFuzz_HolderClaim_AllowDeallocation(uint256 _purchaseAmount) public { uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal( - dealAddressAllowDeallocation - ).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).getVestingScheduleDetails( + 0 + ); // user accepts the deal with purchaseAmount > deal total vm.startPrank(user1); @@ -777,9 +794,8 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I } function testFuzz_FeeEscrowClaim_RevertWhen_NotReachedMinimumRaise(uint256 _purchaseAmount) public { - (, uint256 purchaseTokenPerDealToken, uint256 purchaseRaiseMinimum, , , , ) = AelinUpFrontDeal( - dealAddressNoDeallocation - ).dealConfig(); + (, uint256 purchaseRaiseMinimum, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); uint256 purchaseExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).purchaseExpiry(); AelinNftGating.NftPurchaseList[] memory nftPurchaseList; @@ -793,10 +809,10 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressNoDeallocation), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, _purchaseAmount, _purchaseAmount, poolSharesAmount, poolSharesAmount); - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + emit AcceptDeal(user1, 0, _purchaseAmount, _purchaseAmount, poolSharesAmount, poolSharesAmount); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).totalPurchasingAccepted(), _purchaseAmount); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseTokensPerUser(user1), _purchaseAmount); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseTokensPerUser(user1, 0), _purchaseAmount); assertEq(IERC20(address(purchaseToken)).balanceOf(user1), type(uint256).max - _purchaseAmount); // purchase period is over, user1 tries to claim and gets a refund instead @@ -830,7 +846,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I } function testFuzz_FeeEscrowClaim_AllowDeallocation(uint256 _purchaseAmount) public { - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); // user accepts the deal with purchaseAmount > deal total vm.startPrank(user1); @@ -892,7 +908,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I function testFuzz_ClaimableUnderlyingTokens_DuringVestingCliffPeriod(uint256 _purchaseAmount, uint256 _delay) public { uint256 purchaseExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).purchaseExpiry(); - uint256 vestingCliffExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiry(); + uint256 vestingCliffExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiries(0); (bool success, ) = SafeMath.tryAdd(purchaseExpiry, _delay); vm.assume(success); vm.assume(_delay > 0); @@ -948,16 +964,16 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I deal(address(fuzzed.dealData.purchaseToken), user1, type(uint256).max); MockERC20(fuzzed.dealData.purchaseToken).approve(address(fuzzed.upFrontDeal), type(uint256).max); - fuzzed.upFrontDeal.acceptDeal(nftPurchaseListEmpty, merkleDataEmpty, _purchaseAmount); + fuzzed.upFrontDeal.acceptDeal(nftPurchaseListEmpty, merkleDataEmpty, _purchaseAmount, 0); - uint256 vestingCliffExpiry = fuzzed.upFrontDeal.vestingCliffExpiry(); + uint256 vestingCliffExpiry = fuzzed.upFrontDeal.vestingCliffExpiries(0); vm.warp(vestingCliffExpiry + _delay); uint256 vestingTokenId = fuzzed.upFrontDeal.tokenCount(); - uint256 shareAmount = ((BASE - AELIN_FEE - dealVars.sponsorFee) * fuzzed.upFrontDeal.poolSharesPerUser(user1)) / + uint256 shareAmount = ((BASE - AELIN_FEE - dealVars.sponsorFee) * fuzzed.upFrontDeal.poolSharesPerUser(user1, 0)) / BASE; - fuzzed.upFrontDeal.purchaserClaim(); + fuzzed.upFrontDeal.purchaserClaim(0); assertEq(MockERC721(address(fuzzed.upFrontDeal)).ownerOf(vestingTokenId), user1); uint256 amountToClaim = (shareAmount * (block.timestamp - vestingCliffExpiry)) / dealVars.vestingPeriod; assertEq(fuzzed.upFrontDeal.claimableUnderlyingTokens(vestingTokenId), amountToClaim, "claimableAmount"); @@ -1005,16 +1021,16 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I deal(address(fuzzed.dealData.purchaseToken), user1, type(uint256).max); MockERC20(fuzzed.dealData.purchaseToken).approve(address(fuzzed.upFrontDeal), type(uint256).max); - fuzzed.upFrontDeal.acceptDeal(nftPurchaseListEmpty, merkleDataEmpty, _purchaseAmount); + fuzzed.upFrontDeal.acceptDeal(nftPurchaseListEmpty, merkleDataEmpty, _purchaseAmount, 0); - uint256 vestingCliffExpiry = fuzzed.upFrontDeal.vestingCliffExpiry(); + uint256 vestingCliffExpiry = fuzzed.upFrontDeal.vestingCliffExpiries(0); vm.warp(vestingCliffExpiry + _delay); uint256 vestingTokenId = fuzzed.upFrontDeal.tokenCount(); uint256 adjustedShareAmountForUser = ((BASE - AELIN_FEE - dealVars.sponsorFee) * - fuzzed.upFrontDeal.poolSharesPerUser(user1)) / BASE; + fuzzed.upFrontDeal.poolSharesPerUser(user1, 0)) / BASE; - fuzzed.upFrontDeal.purchaserClaim(); + fuzzed.upFrontDeal.purchaserClaim(0); assertEq(MockERC721(address(fuzzed.upFrontDeal)).ownerOf(vestingTokenId), user1); assertEq( @@ -1028,7 +1044,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I function testFuzz_ClaimableUnderlyingTokens_NoVestingPeriod(uint256 _purchaseAmount) public { AelinUpFrontDeal deal = AelinUpFrontDeal(dealAddressNoVestingPeriod); - uint256 vestingCliffExpiry = deal.vestingCliffExpiry(); + uint256 vestingCliffExpiry = deal.vestingCliffExpiries(0); vm.startPrank(user1); // lastClaimedAt is 0 @@ -1044,14 +1060,14 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // After vesting cliff expiry vm.warp(vestingCliffExpiry); - (uint256 share, ) = deal.vestingDetails(0); + (uint256 share, , ) = deal.vestingDetails(0); assertEq(deal.claimableUnderlyingTokens(0), share, "claimableAmount after vesting cliff expiry => all shares"); vm.stopPrank(); } /*////////////////////////////////////////////////////////////// - claimUnderlying() + claimUnderlying() //////////////////////////////////////////////////////////////*/ function testFuzz_ClaimUnderlying_NothingToClaim(uint256 _purchaseAmount) public { @@ -1095,7 +1111,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I function testFuzz_ClaimUnderlying_VestingCliff(uint256 _purchaseAmount, uint256 _delay) public { uint256 purchaseExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).purchaseExpiry(); - uint256 vestingCliffExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiry(); + uint256 vestingCliffExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiries(0); (bool success, ) = SafeMath.tryAdd(purchaseExpiry, _delay); vm.assume(success); vm.assume(_delay > 0); @@ -1111,9 +1127,9 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I } function testFuzz_ClaimUnderlying_DeallocationVestingPeriod(uint256 _purchaseAmount, uint256 _delay) public { - uint256 vestingCliffExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingCliffExpiry(); - uint256 vestingExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiry(); - (, , , , uint256 vestingPeriod, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + uint256 vestingCliffExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingCliffExpiries(0); + uint256 vestingExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiries(0); + (, , uint256 vestingPeriod) = AelinUpFrontDeal(dealAddressAllowDeallocation).getVestingScheduleDetails(0); (bool success, ) = SafeMath.tryAdd(vestingCliffExpiry, _delay); vm.assume(success); vm.assume(_delay > 0); @@ -1128,7 +1144,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // we are now in the vesting window and user can claim vm.warp(vestingCliffExpiry + _delay); vm.expectEmit(true, false, false, true); - (uint256 shareAmount, uint256 lastClaimedAt) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails( + (uint256 shareAmount, uint256 lastClaimedAt, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails( vestingTokenId ); uint256 amountToClaim = (shareAmount * (block.timestamp - vestingCliffExpiry)) / vestingPeriod; @@ -1145,7 +1161,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // Second claim after vesting period is over vm.warp(vestingExpiry + 1 days); - (shareAmount, lastClaimedAt) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(vestingTokenId); + (shareAmount, lastClaimedAt, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(vestingTokenId); assertEq(lastClaimedAt, vestingCliffExpiry + _delay, "lastClaimedAt"); uint256 amountToClaim2 = (shareAmount * (vestingExpiry - lastClaimedAt)) / vestingPeriod; vm.expectEmit(true, false, false, true); @@ -1174,9 +1190,9 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I } function testFuzz_ClaimUnderlying_NoDeallocationVestingPeriod(uint256 _purchaseAmount, uint256 _delay) public { - uint256 vestingCliffExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiry(); - uint256 vestingExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingExpiry(); - (, , , , uint256 vestingPeriod, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + uint256 vestingCliffExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiries(0); + uint256 vestingExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingExpiries(0); + (, , uint256 vestingPeriod) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); (bool success, ) = SafeMath.tryAdd(vestingCliffExpiry, _delay); vm.assume(success); vm.assume(_delay > 0); @@ -1191,7 +1207,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // we are now in the vesting window and user can claim vm.warp(vestingCliffExpiry + _delay); vm.expectEmit(true, false, false, true); - (uint256 shareAmount, uint256 lastClaimedAt) = AelinUpFrontDeal(dealAddressNoDeallocation).vestingDetails( + (uint256 shareAmount, uint256 lastClaimedAt, ) = AelinUpFrontDeal(dealAddressNoDeallocation).vestingDetails( vestingTokenId ); uint256 amountToClaim = (shareAmount * (block.timestamp - vestingCliffExpiry)) / vestingPeriod; @@ -1208,7 +1224,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // Second claim after vesting period is over vm.warp(vestingExpiry + 1 days); - (shareAmount, lastClaimedAt) = AelinUpFrontDeal(dealAddressNoDeallocation).vestingDetails(vestingTokenId); + (shareAmount, lastClaimedAt, ) = AelinUpFrontDeal(dealAddressNoDeallocation).vestingDetails(vestingTokenId); assertEq(lastClaimedAt, vestingCliffExpiry + _delay, "lastClaimedAt"); uint256 amountToClaim2 = (shareAmount * (vestingExpiry - lastClaimedAt)) / vestingPeriod; vm.expectEmit(true, false, false, true); @@ -1242,9 +1258,9 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I ) public { AelinUpFrontDeal deal = AelinUpFrontDeal(dealAddressLowDecimals); - uint256 vestingCliffExpiry = deal.vestingCliffExpiry(); - uint256 vestingExpiry = deal.vestingExpiry(); - (, , , , uint256 vestingPeriod, , ) = deal.dealConfig(); + uint256 vestingCliffExpiry = deal.vestingCliffExpiries(0); + uint256 vestingExpiry = deal.vestingExpiries(0); + (, , uint256 vestingPeriod) = deal.getVestingScheduleDetails(0); (bool success, ) = SafeMath.tryAdd(vestingCliffExpiry, _delay); vm.assume(success); vm.assume(_delay > 0); @@ -1259,7 +1275,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // we are now in the vesting window and user can claim vm.warp(vestingCliffExpiry + _delay); vm.expectEmit(true, false, false, true); - (uint256 shareAmount, uint256 lastClaimedAt) = deal.vestingDetails(vestingTokenId); + (uint256 shareAmount, uint256 lastClaimedAt, ) = deal.vestingDetails(vestingTokenId); uint256 amountToClaim = (shareAmount * (block.timestamp - vestingCliffExpiry)) / vestingPeriod; emit ClaimedUnderlyingDealToken(user1, vestingTokenId, address(underlyingDealTokenLowDecimals), amountToClaim); deal.claimUnderlying(vestingTokenId); @@ -1270,7 +1286,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // Second claim after vesting period is over vm.warp(vestingExpiry + 1 days); - (shareAmount, lastClaimedAt) = deal.vestingDetails(vestingTokenId); + (shareAmount, lastClaimedAt, ) = deal.vestingDetails(vestingTokenId); assertEq(lastClaimedAt, vestingCliffExpiry + _delay, "lastClaimedAt"); uint256 amountToClaim2 = (shareAmount * (vestingExpiry - lastClaimedAt)) / vestingPeriod; vm.expectEmit(true, false, false, true); @@ -1292,8 +1308,8 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I function testFuzz_ClaimUnderlying_DeallocationVestingEnd(uint256 _purchaseAmount) public { uint256 purchaseExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseExpiry(); - uint256 vestingExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiry(); - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + uint256 vestingExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiries(0); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); (, , , , , , uint256 sponsorFee, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealData(); vm.startPrank(user1); @@ -1304,7 +1320,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // Second claim after vesting period is over vm.warp(vestingExpiry + 1 days); - (uint256 shareAmount, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(vestingTokenId); + (uint256 shareAmount, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(vestingTokenId); vm.expectEmit(true, false, false, true); emit ClaimedUnderlyingDealToken(user1, vestingTokenId, address(underlyingDealToken), shareAmount); AelinUpFrontDeal(dealAddressAllowDeallocation).claimUnderlying(vestingTokenId); @@ -1332,10 +1348,10 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.startPrank(dealCreatorAddress); uint256 sponsorShareAmount = (underlyingDealTokenTotal * sponsorFee) / BASE; vm.expectEmit(true, true, false, true); - emit VestingTokenMinted(dealCreatorAddress, vestingTokenId + 1, sponsorShareAmount, purchaseExpiry); + emit VestingTokenMinted(dealCreatorAddress, vestingTokenId + 1, sponsorShareAmount, purchaseExpiry, 0); vm.expectEmit(true, false, false, true); emit SponsorClaim(dealCreatorAddress, sponsorShareAmount); - AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(); + AelinUpFrontDeal(dealAddressAllowDeallocation).sponsorClaim(0); vm.expectEmit(true, false, false, true); emit ClaimedUnderlyingDealToken( dealCreatorAddress, @@ -1358,7 +1374,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I } function testFuzz_ClaimUnderlying_NoDeallocationVestingEnd(uint256 _purchaseAmount) public { - uint256 vestingExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingExpiry(); + uint256 vestingExpiry = AelinUpFrontDeal(dealAddressNoDeallocation).vestingExpiries(0); vm.startPrank(user1); setupAndAcceptDealNoDeallocation(dealAddressNoDeallocation, _purchaseAmount, user1); @@ -1368,7 +1384,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // Second claim after vesting period is over vm.warp(vestingExpiry + 1 days); - (uint256 shareAmount, ) = AelinUpFrontDeal(dealAddressNoDeallocation).vestingDetails(vestingTokenId); + (uint256 shareAmount, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).vestingDetails(vestingTokenId); vm.expectEmit(true, false, false, true); emit ClaimedUnderlyingDealToken(user1, vestingTokenId, address(underlyingDealToken), shareAmount); AelinUpFrontDeal(dealAddressNoDeallocation).claimUnderlying(vestingTokenId); @@ -1395,7 +1411,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I function testFuzz_ClaimUnderlying_NoDeallocationVestingEndLowDecimals(uint256 _purchaseAmount) public { AelinUpFrontDeal deal = AelinUpFrontDeal(dealAddressLowDecimals); - uint256 vestingExpiry = deal.vestingExpiry(); + uint256 vestingExpiry = deal.vestingExpiries(0); vm.startPrank(user1); setupAndAcceptDealNoDeallocation(dealAddressLowDecimals, _purchaseAmount, user1); @@ -1405,7 +1421,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // Second claim after vesting period is over vm.warp(vestingExpiry + 1 days); - (uint256 shareAmount, ) = deal.vestingDetails(vestingTokenId); + (uint256 shareAmount, , ) = deal.vestingDetails(vestingTokenId); vm.expectEmit(true, false, false, true); emit ClaimedUnderlyingDealToken(user1, vestingTokenId, address(underlyingDealTokenLowDecimals), shareAmount); deal.claimUnderlying(vestingTokenId); @@ -1422,12 +1438,70 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.stopPrank(); } + function testFuzz_ClaimUnderlying_MultipleVestingSchedules(uint256 _purchaseAmount1, uint256 _delay) public { + vm.assume(_delay > 0); + vm.assume(_delay < 10 days); // Delta between vestingCliffExpiry1 and vestingCliffExpiry2 is 10 days + AelinUpFrontDeal deal = AelinUpFrontDeal(dealAddressMultipleVestingSchedules); + + uint256 vestingCliffExpiry1 = deal.vestingCliffExpiries(0); + uint256 vestingCliffExpiry2 = deal.vestingCliffExpiries(1); + + // user1 accepts deal across two vesting periods + vm.startPrank(user1); + setupAndAcceptDealWithMultipleVesting(dealAddressMultipleVestingSchedules, _purchaseAmount1, user1); + + //User claims vesting tokens, we know that vestingCliffExpiry2 > vestingCliffExpiry1 + vm.warp(vestingCliffExpiry1 - _delay); + + uint256 vestingTokenId1 = deal.tokenCount(); + deal.purchaserClaim(0); + deal.purchaserClaim(1); + + assertEq(deal.ownerOf(vestingTokenId1), user1); + assertEq(deal.ownerOf(vestingTokenId1 + 1), user1); + + //before each vesting has finished, claims should get nothing + assertEq(deal.claimUnderlying(vestingTokenId1), 0); + assertEq(deal.claimUnderlying(vestingTokenId1 + 1), 0); + + //Then claim 1 will succeed, and two will recieve nothing + vm.warp(vestingCliffExpiry1 + _delay); + uint256 claimableUnderlyingTokens1 = deal.claimableUnderlyingTokens(vestingTokenId1); + assertGt(claimableUnderlyingTokens1, 0); + vm.expectEmit(true, false, false, true); + emit ClaimedUnderlyingDealToken(user1, vestingTokenId1, address(underlyingDealToken), claimableUnderlyingTokens1); + deal.claimUnderlying(vestingTokenId1); + assertEq(deal.claimUnderlying(vestingTokenId1 + 1), 0); + + //Then both will start receiving when claining + vm.warp(vestingCliffExpiry2 + _delay); + claimableUnderlyingTokens1 = deal.claimableUnderlyingTokens(vestingTokenId1); + uint256 claimableUnderlyingTokens2 = deal.claimableUnderlyingTokens(vestingTokenId1 + 1); + assertGt(claimableUnderlyingTokens1, 0); + assertGt(claimableUnderlyingTokens2, 0); + + vm.expectEmit(true, false, false, true); + emit ClaimedUnderlyingDealToken(user1, vestingTokenId1, address(underlyingDealToken), claimableUnderlyingTokens1); + deal.claimUnderlying(vestingTokenId1); + + vm.expectEmit(true, false, false, true); + emit ClaimedUnderlyingDealToken( + user1, + vestingTokenId1 + 1, + address(underlyingDealToken), + claimableUnderlyingTokens2 + ); + deal.claimUnderlying(vestingTokenId1 + 1); + + vm.stopPrank(); + } + /*////////////////////////////////////////////////////////////// - claimUnderlyingMutlipleEntries() + claimUnderlyingMutlipleEntries() //////////////////////////////////////////////////////////////*/ - function testFuzz_ClaimUnderlyingMultipleEntries(uint256 _purchaseAmount1) public { - uint256 vestingExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiry(); + function testFuzz_ClaimUnderlyingMultipleEntries_OneVestingSchedule(uint256 _purchaseAmount1) public { + uint256 vestingExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiries(0); // user1 accepts vm.startPrank(user1); @@ -1460,11 +1534,16 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(MockERC721(dealAddressAllowDeallocation).balanceOf(user1), 2); // user2 tries claiming and it reverts - uint256[] memory indices = new uint256[](2); - indices[0] = vestingTokenId; - indices[1] = vestingTokenId - 1; + // TokenIds + uint256[] memory tokenIds = new uint256[](2); + tokenIds[0] = vestingTokenId; + tokenIds[1] = vestingTokenId - 1; + // Vesting Indices + uint256[] memory vestingIndices = new uint256[](1); + vestingIndices[0] = 0; + vm.expectRevert("must be owner to claim"); - AelinUpFrontDeal(dealAddressAllowDeallocation).claimUnderlyingMultipleEntries(indices); + AelinUpFrontDeal(dealAddressAllowDeallocation).claimUnderlyingMultipleEntries(tokenIds); vm.expectRevert("must be owner to claim"); AelinUpFrontDeal(dealAddressAllowDeallocation).claimUnderlying(vestingTokenId); vm.stopPrank(); @@ -1472,13 +1551,13 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // user1 tries to claiming vm.warp(vestingExpiry + 1 days); vm.startPrank(user1); - (uint256 share1, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(vestingTokenId - 1); - (uint256 share2, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(vestingTokenId); + (uint256 share1, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(vestingTokenId - 1); + (uint256 share2, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(vestingTokenId); vm.expectEmit(true, false, false, true); emit ClaimedUnderlyingDealToken(user1, vestingTokenId - 1, address(underlyingDealToken), share1); vm.expectEmit(true, false, false, true); emit ClaimedUnderlyingDealToken(user1, vestingTokenId, address(underlyingDealToken), share2); - AelinUpFrontDeal(dealAddressAllowDeallocation).claimUnderlyingMultipleEntries(indices); + AelinUpFrontDeal(dealAddressAllowDeallocation).claimUnderlyingMultipleEntries(tokenIds); assertEq(underlyingDealToken.balanceOf(user1), share1 + share2); // user1 attempts claiming more the next day but it reverts @@ -1487,7 +1566,55 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).claimableUnderlyingTokens(vestingTokenId - 1), 0); assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).claimableUnderlyingTokens(vestingTokenId), 0); vm.expectRevert("ERC721: invalid token ID"); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).claimUnderlyingMultipleEntries(indices), 0); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).claimUnderlyingMultipleEntries(tokenIds), 0); + vm.stopPrank(); + } + + function testFuzz_ClaimUnderlyingMultipleEntries_MultipleVestingSchedules( + uint256 _purchaseAmount1, + uint256 _delay + ) public { + vm.assume(_delay > 0); + vm.assume(_delay < 10 days); // Delta between vestingCliffExpiry1 and vestingCliffExpiry2 is 10 days + AelinUpFrontDeal deal = AelinUpFrontDeal(dealAddressMultipleVestingSchedules); + + uint256 vestingCliffExpiry1 = deal.vestingCliffExpiries(0); + uint256 vestingCliffExpiry2 = deal.vestingCliffExpiries(1); + + // user1 accepts deal across two vesting periods + vm.startPrank(user1); + setupAndAcceptDealWithMultipleVesting(dealAddressMultipleVestingSchedules, _purchaseAmount1, user1); + + //Then claim 1 will succeed, and two will recieve nothing + vm.warp(vestingCliffExpiry1 + _delay); + + uint256 vestingTokenId1 = deal.tokenCount(); + deal.purchaserClaim(0); + deal.purchaserClaim(1); + + uint256 claimableUnderlyingTokens1 = deal.claimableUnderlyingTokens(vestingTokenId1); + uint256 claimableUnderlyingTokens2 = deal.claimableUnderlyingTokens(vestingTokenId1 + 1); + assertGt(claimableUnderlyingTokens1, 0); + assertEq(claimableUnderlyingTokens2, 0); + + // TokenIds + uint256[] memory tokenIds = new uint256[](2); + tokenIds[0] = vestingTokenId1; + tokenIds[1] = vestingTokenId1 + 1; + + vm.expectEmit(true, false, false, true); + emit ClaimedUnderlyingDealToken(user1, vestingTokenId1, address(underlyingDealToken), claimableUnderlyingTokens1); + deal.claimUnderlyingMultipleEntries(tokenIds); + + //Then both will start receiving when claining + vm.warp(vestingCliffExpiry2 + _delay); + claimableUnderlyingTokens1 = deal.claimableUnderlyingTokens(vestingTokenId1); + claimableUnderlyingTokens2 = deal.claimableUnderlyingTokens(vestingTokenId1 + 1); + assertGt(claimableUnderlyingTokens1, 0); + assertGt(claimableUnderlyingTokens2, 0); + + assertEq(deal.claimUnderlyingMultipleEntries(tokenIds), claimableUnderlyingTokens1 + claimableUnderlyingTokens2); + vm.stopPrank(); } @@ -1528,14 +1655,14 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(0), user1, "vestingTokenOwnerOf"); // user1 transfers their token to user2 - (uint256 share, uint256 lastClaimedAt) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); + (uint256 share, uint256 lastClaimedAt, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); vm.expectEmit(true, true, true, false); emit Transfer(user1, user2, 0); AelinUpFrontDeal(dealAddressAllowDeallocation).transfer(user2, 0, "0x0"); assertEq(MockERC721(dealAddressAllowDeallocation).balanceOf(user1), 0, "vestingTokenBalance"); assertEq(MockERC721(dealAddressAllowDeallocation).balanceOf(user2), 1, "vestingTokenBalance"); assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(0), user2, "vestingTokenOwnerOf"); - (uint256 shareTemp, uint256 lastClaimedAtTemp) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); + (uint256 shareTemp, uint256 lastClaimedAtTemp, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); assertEq(share, shareTemp, "shareAmount"); assertEq(lastClaimedAt, lastClaimedAtTemp, "lastClaimedAt"); @@ -1552,7 +1679,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(MockERC721(dealAddressAllowDeallocation).balanceOf(user2), 0, "vestingTokenBalance"); assertEq(MockERC721(dealAddressAllowDeallocation).balanceOf(user3), 1, "vestingTokenBalance"); assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(0), user3, "vestingTokenOwnerOf"); - (shareTemp, lastClaimedAtTemp) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); + (shareTemp, lastClaimedAtTemp, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); assertEq(share, shareTemp, "shareAmount"); assertEq(lastClaimedAt, lastClaimedAtTemp, "lastClaimedAt"); vm.stopPrank(); @@ -1573,7 +1700,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // user2 tries to transfer the token vm.startPrank(user2); - (uint256 share, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); + (uint256 share, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); vm.expectRevert("must be owner to transfer"); AelinUpFrontDeal(dealAddressAllowDeallocation).transferVestingShare(user3, 0, share - 1); vm.stopPrank(); @@ -1610,7 +1737,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(0), user1, "vestingTokenOwnerOf"); // user1 tries to transfer an amount greather than the total value of their vesting - (uint256 share, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); + (uint256 share, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(0); vm.expectRevert("amout gt than current share"); AelinUpFrontDeal(dealAddressAllowDeallocation).transferVestingShare(user2, 0, share + 1); } @@ -1623,13 +1750,13 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I purchaserClaim(dealAddressAllowDeallocation); assertEq(MockERC721(dealAddressAllowDeallocation).balanceOf(user1), 1, "vestingTokenBalance"); assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(tokenCount), user1, "vestingTokenOwnerOf"); - (uint256 share, uint256 lastClaimedAt) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(tokenCount); + (uint256 share, uint256 lastClaimedAt, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(tokenCount); vm.assume(_shareAmount > 0); vm.assume(_shareAmount < share); // user1 transfers a part of their share to user2 vm.expectEmit(true, true, false, true); - emit VestingTokenMinted(user2, tokenCount + 1, _shareAmount, lastClaimedAt); + emit VestingTokenMinted(user2, tokenCount + 1, _shareAmount, lastClaimedAt, 0); vm.expectEmit(true, true, true, false); emit VestingShareTransferred(user1, user2, tokenCount, _shareAmount); AelinUpFrontDeal(dealAddressAllowDeallocation).transferVestingShare(user2, tokenCount, _shareAmount); @@ -1637,7 +1764,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // user1 still has the same token but with a smaller share assertEq(MockERC721(dealAddressAllowDeallocation).balanceOf(user1), 1, "vestingTokenBalance"); assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(tokenCount), user1, "vestingTokenOwnerOf"); - (uint256 newShare, uint256 newLastClaimedAt) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails( + (uint256 newShare, uint256 newLastClaimedAt, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails( tokenCount ); assertEq(newShare, share - _shareAmount); @@ -1646,13 +1773,13 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // user2 has a new vesting token with a share of user1 assertEq(MockERC721(dealAddressAllowDeallocation).balanceOf(user2), 1, "vestingTokenBalance"); assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(tokenCount + 1), user2, "vestingTokenOwnerOf"); - (newShare, newLastClaimedAt) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(tokenCount + 1); + (newShare, newLastClaimedAt, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).vestingDetails(tokenCount + 1); assertEq(newShare, _shareAmount); assertEq(newLastClaimedAt, lastClaimedAt); vm.stopPrank(); // vesting is now over - vm.warp(AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiry() + 1 days); + vm.warp(AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiries(0) + 1 days); // user1 claims all and transfer to user3 vm.startPrank(user1); @@ -1665,15 +1792,15 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).balanceOf(user1), 0); // user1 can't transfer because the token has been burned - vm.warp(AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiry() + 2 days); + vm.warp(AelinUpFrontDeal(dealAddressAllowDeallocation).vestingExpiries(0) + 2 days); vm.expectRevert("ERC721: invalid token ID"); AelinUpFrontDeal(dealAddressAllowDeallocation).transferVestingShare(user3, tokenCount, (share - _shareAmount) / 2); vm.stopPrank(); } - // /*////////////////////////////////////////////////////////////// - // Scenarios with precision error - // //////////////////////////////////////////////////////////////*/ + /*////////////////////////////////////////////////////////////// + Scenarios with precision error + //////////////////////////////////////////////////////////////*/ function test_PrecisionError_PurchaserSide() public { // Deal config @@ -1694,14 +1821,18 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I merkleRoot: 0 }); + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](1); + + vestingSchedules[0].purchaseTokenPerDealToken = 2e18; + vestingSchedules[0].vestingCliffPeriod = 1 days; + vestingSchedules[0].vestingPeriod = 10 days; + IAelinUpFrontDeal.UpFrontDealConfig memory dealConfig; dealConfig = IAelinUpFrontDeal.UpFrontDealConfig({ underlyingDealTokenTotal: 1.5e18, - purchaseTokenPerDealToken: 2e18, purchaseRaiseMinimum: 0, purchaseDuration: 1 days, - vestingPeriod: 10 days, - vestingCliffPeriod: 1 days, + vestingSchedules: vestingSchedules, allowDeallocation: true }); @@ -1712,9 +1843,8 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I allowListInitEmpty ); - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal( - upfrontDealAddress - ).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(upfrontDealAddress).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(upfrontDealAddress).getVestingScheduleDetails(0); uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); // Deal funding @@ -1731,21 +1861,21 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I uint256 purchaseAmount1 = 1e18; deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(upfrontDealAddress), type(uint256).max); - AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount1); + AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount1, 0); vm.stopPrank(); vm.startPrank(user2); uint256 purchaseAmount2 = 10e18; deal(address(purchaseToken), user2, type(uint256).max); purchaseToken.approve(address(upfrontDealAddress), type(uint256).max); - AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount2); + AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount2, 0); vm.stopPrank(); vm.startPrank(user3); uint256 purchaseAmount3 = 20e18; deal(address(purchaseToken), user3, type(uint256).max); purchaseToken.approve(address(upfrontDealAddress), type(uint256).max); - AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount3); + AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount3, 0); //HolderClaim vm.stopPrank(); @@ -1764,11 +1894,11 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.stopPrank(); vm.startPrank(user1); - uint256 adjustedShareAmountForUser = (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user1) * + uint256 adjustedShareAmountForUser = (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user1, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()) * (BASE - AELIN_FEE)) / BASE; - uint256 purchasingRefund = AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user1) - - ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user1) * underlyingDealTokenTotal) / + uint256 purchasingRefund = AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user1, 0) - + ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user1, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()); assertEq(purchaseToken.balanceOf(user1), type(uint256).max - purchaseAmount1); @@ -1776,7 +1906,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I uint256 tokenCount = AelinUpFrontDeal(upfrontDealAddress).tokenCount(); assertEq(tokenCount, 0); emit ClaimDealTokens(user1, adjustedShareAmountForUser, purchasingRefund); - AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(); + AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(0); // First purchaser gets refunded assertEq(purchaseToken.balanceOf(user1), type(uint256).max - purchaseAmount1 + purchasingRefund); @@ -1786,13 +1916,13 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.startPrank(user2); adjustedShareAmountForUser = - (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user2) * underlyingDealTokenTotal) / + (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user2, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()) * (BASE - AELIN_FEE)) / BASE; purchasingRefund = - AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user2) - - ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user2) * underlyingDealTokenTotal) / + AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user2, 0) - + ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user2, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()); assertEq(purchaseToken.balanceOf(user2), type(uint256).max - purchaseAmount2); @@ -1800,7 +1930,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I tokenCount = AelinUpFrontDeal(upfrontDealAddress).tokenCount(); assertEq(tokenCount, 1); emit ClaimDealTokens(user2, adjustedShareAmountForUser, purchasingRefund); - AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(); + AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(0); // Second purchaser gets refunded entirely assertEq(purchaseToken.balanceOf(user2), type(uint256).max - purchaseAmount2 + purchasingRefund); @@ -1810,13 +1940,13 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.startPrank(user3); adjustedShareAmountForUser = - (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user3) * underlyingDealTokenTotal) / + (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user3, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()) * (BASE - AELIN_FEE)) / BASE; purchasingRefund = - AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user3) - - ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user3) * underlyingDealTokenTotal) / + AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user3, 0) - + ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user3, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()); assertEq(purchaseToken.balanceOf(user3), type(uint256).max - purchaseAmount3); @@ -1830,7 +1960,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(tokenCount, 2); emit ClaimDealTokens(user3, adjustedShareAmountForUser, contractRemainingBalance); assertEq(purchaseToken.balanceOf(user3), type(uint256).max - purchaseAmount3); - AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(); + AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(0); assertEq(purchaseToken.balanceOf(user3), type(uint256).max - purchaseAmount3 + contractRemainingBalance); } @@ -1853,14 +1983,18 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I merkleRoot: 0 }); + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](1); + + vestingSchedules[0].purchaseTokenPerDealToken = 2e18; + vestingSchedules[0].vestingCliffPeriod = 1 days; + vestingSchedules[0].vestingPeriod = 10 days; + IAelinUpFrontDeal.UpFrontDealConfig memory dealConfig; dealConfig = IAelinUpFrontDeal.UpFrontDealConfig({ underlyingDealTokenTotal: 1.5e18, - purchaseTokenPerDealToken: 2e18, purchaseRaiseMinimum: 0, purchaseDuration: 1 days, - vestingPeriod: 10 days, - vestingCliffPeriod: 1 days, + vestingSchedules: vestingSchedules, allowDeallocation: true }); @@ -1871,9 +2005,8 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I allowListInitEmpty ); - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal( - upfrontDealAddress - ).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(upfrontDealAddress).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(upfrontDealAddress).getVestingScheduleDetails(0); uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); // Deal funding @@ -1890,21 +2023,21 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I uint256 purchaseAmount1 = 1e18; deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(upfrontDealAddress), type(uint256).max); - AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount1); + AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount1, 0); vm.stopPrank(); vm.startPrank(user2); uint256 purchaseAmount2 = 10e18; deal(address(purchaseToken), user2, type(uint256).max); purchaseToken.approve(address(upfrontDealAddress), type(uint256).max); - AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount2); + AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount2, 0); vm.stopPrank(); vm.startPrank(user3); uint256 purchaseAmount3 = 20e18; deal(address(purchaseToken), user3, type(uint256).max); purchaseToken.approve(address(upfrontDealAddress), type(uint256).max); - AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount3); + AelinUpFrontDeal(upfrontDealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount3, 0); vm.warp(AelinUpFrontDeal(upfrontDealAddress).purchaseExpiry() + 1 days); @@ -1912,11 +2045,11 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.stopPrank(); vm.startPrank(user1); - uint256 adjustedShareAmountForUser = (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user1) * + uint256 adjustedShareAmountForUser = (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user1, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()) * (BASE - AELIN_FEE)) / BASE; - uint256 purchasingRefund = AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user1) - - ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user1) * underlyingDealTokenTotal) / + uint256 purchasingRefund = AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user1, 0) - + ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user1, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()); assertEq(purchaseToken.balanceOf(user1), type(uint256).max - purchaseAmount1); @@ -1924,7 +2057,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(tokenCount, 0); vm.expectEmit(true, false, false, true); emit ClaimDealTokens(user1, adjustedShareAmountForUser, purchasingRefund); - AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(); + AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(0); // First purchaser gets refunded entirely assertEq(purchaseToken.balanceOf(user1), type(uint256).max - purchaseAmount1 + purchasingRefund); @@ -1934,13 +2067,13 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.startPrank(user2); adjustedShareAmountForUser = - (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user2) * underlyingDealTokenTotal) / + (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user2, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()) * (BASE - AELIN_FEE)) / BASE; purchasingRefund = - AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user2) - - ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user2) * underlyingDealTokenTotal) / + AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user2, 0) - + ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user2, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()); assertEq(purchaseToken.balanceOf(user2), type(uint256).max - purchaseAmount2); @@ -1948,7 +2081,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(tokenCount, 1); vm.expectEmit(true, false, false, true); emit ClaimDealTokens(user2, adjustedShareAmountForUser, purchasingRefund); - AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(); + AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(0); // Second purchaser gets refunded entirely assertEq(purchaseToken.balanceOf(user2), type(uint256).max - purchaseAmount2 + purchasingRefund); @@ -1958,13 +2091,13 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.startPrank(user3); adjustedShareAmountForUser = - (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user3) * underlyingDealTokenTotal) / + (((AelinUpFrontDeal(upfrontDealAddress).poolSharesPerUser(user3, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()) * (BASE - AELIN_FEE)) / BASE; purchasingRefund = - AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user3) - - ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user3) * underlyingDealTokenTotal) / + AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user3, 0) - + ((AelinUpFrontDeal(upfrontDealAddress).purchaseTokensPerUser(user3, 0) * underlyingDealTokenTotal) / AelinUpFrontDeal(upfrontDealAddress).totalPoolShares()); assertEq(purchaseToken.balanceOf(user3), type(uint256).max - purchaseAmount3); @@ -1973,7 +2106,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.expectEmit(true, false, false, true); emit ClaimDealTokens(user3, adjustedShareAmountForUser, purchasingRefund); assertEq(purchaseToken.balanceOf(user3), type(uint256).max - purchaseAmount3); - AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(); + AelinUpFrontDeal(upfrontDealAddress).purchaserClaim(0); assertEq(purchaseToken.balanceOf(user3), type(uint256).max - purchaseAmount3 + purchasingRefund); // Holder claim. @@ -2016,9 +2149,9 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I uint256 _purchaseAmount2, uint256 _purchaseAmount3 ) public { - vm.assume(_purchaseAmount1 > 0 && _purchaseAmount1 < 1000000 * BASE); - vm.assume(_purchaseAmount2 > 0 && _purchaseAmount2 < 1000000 * BASE); - vm.assume(_purchaseAmount3 > 0 && _purchaseAmount3 < 1000000 * BASE); + _purchaseAmount1 = bound(_purchaseAmount1, 1, (1000000 * BASE) - 1); + _purchaseAmount2 = bound(_purchaseAmount2, 1, (1000000 * BASE) - 1); + _purchaseAmount3 = bound(_purchaseAmount3, 1, (1000000 * BASE) - 1); AelinAllowList.InitData memory allowListInitEmpty; AelinNftGating.NftPurchaseList[] memory nftPurchaseList; @@ -2077,19 +2210,19 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I vm.startPrank(user1); deal(address(fuzzed.dealData.purchaseToken), user1, type(uint256).max); MockERC20(fuzzed.dealData.purchaseToken).approve(address(fuzzed.upFrontDeal), type(uint256).max); - fuzzed.upFrontDeal.acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount1); + fuzzed.upFrontDeal.acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount1, 0); vm.stopPrank(); vm.startPrank(user2); deal(address(fuzzed.dealData.purchaseToken), user2, type(uint256).max); MockERC20(fuzzed.dealData.purchaseToken).approve(address(fuzzed.upFrontDeal), type(uint256).max); - fuzzed.upFrontDeal.acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount2); + fuzzed.upFrontDeal.acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount2, 0); vm.stopPrank(); vm.startPrank(user3); deal(address(fuzzed.dealData.purchaseToken), user3, type(uint256).max); MockERC20(fuzzed.dealData.purchaseToken).approve(address(fuzzed.upFrontDeal), type(uint256).max); - fuzzed.upFrontDeal.acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount3); + fuzzed.upFrontDeal.acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount3, 0); vm.stopPrank(); //HolderClaim @@ -2101,19 +2234,19 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // PurchaserClaim1 vm.startPrank(user1); - uint256 adjustedShareAmountForUser = (((fuzzed.upFrontDeal.poolSharesPerUser(user1) * + uint256 adjustedShareAmountForUser = (((fuzzed.upFrontDeal.poolSharesPerUser(user1, 0) * dealVars.underlyingDealTokenTotal) / fuzzed.upFrontDeal.totalPoolShares()) * (BASE - AELIN_FEE - dealVars.sponsorFee)) / BASE; - uint256 purchasingRefund = fuzzed.upFrontDeal.purchaseTokensPerUser(user1) - - ((fuzzed.upFrontDeal.purchaseTokensPerUser(user1) * dealVars.underlyingDealTokenTotal) / + uint256 purchasingRefund = fuzzed.upFrontDeal.purchaseTokensPerUser(user1, 0) - + ((fuzzed.upFrontDeal.purchaseTokensPerUser(user1, 0) * dealVars.underlyingDealTokenTotal) / fuzzed.upFrontDeal.totalPoolShares()); assertEq(MockERC20(fuzzed.dealData.purchaseToken).balanceOf(user1), type(uint256).max - _purchaseAmount1); vm.expectEmit(true, false, false, true); assertEq(fuzzed.upFrontDeal.tokenCount(), 0); emit ClaimDealTokens(user1, adjustedShareAmountForUser, purchasingRefund); - fuzzed.upFrontDeal.purchaserClaim(); + fuzzed.upFrontDeal.purchaserClaim(0); assertEq( MockERC20(fuzzed.dealData.purchaseToken).balanceOf(dealHolderAddress), @@ -2130,20 +2263,20 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // PurchaserClaim 2 vm.startPrank(user2); adjustedShareAmountForUser = - (((fuzzed.upFrontDeal.poolSharesPerUser(user2) * dealVars.underlyingDealTokenTotal) / + (((fuzzed.upFrontDeal.poolSharesPerUser(user2, 0) * dealVars.underlyingDealTokenTotal) / fuzzed.upFrontDeal.totalPoolShares()) * (BASE - AELIN_FEE - dealVars.sponsorFee)) / BASE; purchasingRefund = - fuzzed.upFrontDeal.purchaseTokensPerUser(user2) - - ((fuzzed.upFrontDeal.purchaseTokensPerUser(user2) * dealVars.underlyingDealTokenTotal) / + fuzzed.upFrontDeal.purchaseTokensPerUser(user2, 0) - + ((fuzzed.upFrontDeal.purchaseTokensPerUser(user2, 0) * dealVars.underlyingDealTokenTotal) / fuzzed.upFrontDeal.totalPoolShares()); assertEq(MockERC20(fuzzed.dealData.purchaseToken).balanceOf(user2), type(uint256).max - _purchaseAmount2); vm.expectEmit(true, false, false, true); assertEq(fuzzed.upFrontDeal.tokenCount(), 1); emit ClaimDealTokens(user2, adjustedShareAmountForUser, purchasingRefund); - fuzzed.upFrontDeal.purchaserClaim(); + fuzzed.upFrontDeal.purchaserClaim(0); // Second purchaser refunded assertEq( @@ -2155,12 +2288,12 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // PurchaserClaim 3 vm.startPrank(user3); adjustedShareAmountForUser = - (((fuzzed.upFrontDeal.poolSharesPerUser(user3) * dealVars.underlyingDealTokenTotal) / + (((fuzzed.upFrontDeal.poolSharesPerUser(user3, 0) * dealVars.underlyingDealTokenTotal) / fuzzed.upFrontDeal.totalPoolShares()) * (BASE - AELIN_FEE - dealVars.sponsorFee)) / BASE; purchasingRefund = - fuzzed.upFrontDeal.purchaseTokensPerUser(user3) - - ((fuzzed.upFrontDeal.purchaseTokensPerUser(user3) * dealVars.underlyingDealTokenTotal) / + fuzzed.upFrontDeal.purchaseTokensPerUser(user3, 0) - + ((fuzzed.upFrontDeal.purchaseTokensPerUser(user3, 0) * dealVars.underlyingDealTokenTotal) / fuzzed.upFrontDeal.totalPoolShares()); assertEq(MockERC20(fuzzed.dealData.purchaseToken).balanceOf(user3), type(uint256).max - _purchaseAmount3); @@ -2173,7 +2306,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I assertEq(fuzzed.upFrontDeal.tokenCount(), 2); emit ClaimDealTokens(user3, adjustedShareAmountForUser, contractRemainingBalance); assertEq(MockERC20(fuzzed.dealData.purchaseToken).balanceOf(user3), type(uint256).max - _purchaseAmount3); - fuzzed.upFrontDeal.purchaserClaim(); + fuzzed.upFrontDeal.purchaserClaim(0); assertEq( MockERC20(fuzzed.dealData.purchaseToken).balanceOf(user3), type(uint256).max - _purchaseAmount3 + contractRemainingBalance @@ -2189,9 +2322,10 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I // purchasing uint256 totalPurchaseAccepted; uint256 totalPoolShares; - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal( - dealAddressAllowDeallocation - ).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).getVestingScheduleDetails( + 0 + ); uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); vm.fee(1 gwei); for (uint256 i = 1; i < 10000; ++i) { @@ -2210,12 +2344,12 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I totalPurchaseAccepted += _purchaseAmount; totalPoolShares += poolSharesAmount; vm.expectEmit(true, false, false, true); - emit AcceptDeal(user, _purchaseAmount, _purchaseAmount, poolSharesAmount, poolSharesAmount); - AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + emit AcceptDeal(user, 0, _purchaseAmount, _purchaseAmount, poolSharesAmount, poolSharesAmount); + AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).totalPoolShares(), totalPoolShares); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user), poolSharesAmount); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user, 0), poolSharesAmount); assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).totalPurchasingAccepted(), totalPurchaseAccepted); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user), _purchaseAmount); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user, 0), _purchaseAmount); vm.stopPrank(); } uint256 purchaseExpiry = AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseExpiry(); @@ -2226,7 +2360,7 @@ contract AelinUpFrontDealClaimTest is Test, AelinTestUtils, IAelinUpFrontDeal, I } address user = makeAddr(i); vm.startPrank(user); - AelinUpFrontDeal(dealAddressAllowDeallocation).purchaserClaim(); + AelinUpFrontDeal(dealAddressAllowDeallocation).purchaserClaim(0); assertEq(MockERC721(dealAddressAllowDeallocation).balanceOf(user), 1, "vestingTokenBalance"); assertEq(MockERC721(dealAddressAllowDeallocation).ownerOf(i - 1), user, "vestingTokenOwnerOf"); vm.stopPrank(); diff --git a/test/tests/AelinUpFrontDeal.init.t.sol b/test/tests/AelinUpFrontDeal.init.t.sol index 1984353f..0cef6e8d 100644 --- a/test/tests/AelinUpFrontDeal.init.t.sol +++ b/test/tests/AelinUpFrontDeal.init.t.sol @@ -26,6 +26,7 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { address dealAddressNftGating1155; address dealAddressLowDecimals; address dealAddressNftGating721IdRanges; + address dealAddressMultipleVestingSchedules; function setUp() public { AelinAllowList.InitData memory allowListEmpty; @@ -40,6 +41,8 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { IAelinUpFrontDeal.UpFrontDealData memory dealData = getDealData(); IAelinUpFrontDeal.UpFrontDealConfig memory dealConfig = getDealConfig(); IAelinUpFrontDeal.UpFrontDealConfig memory dealConfigAllowDeallocation = getDealConfigAllowDeallocation(); + IAelinUpFrontDeal.UpFrontDealConfig + memory dealConfigMultipleVestingSchedules = getDealConfigMultipleVestingSchedules(); IAelinUpFrontDeal.UpFrontDealData memory dealDataLowDecimals = getDealData(); dealDataLowDecimals.underlyingDealToken = address(underlyingDealTokenLowDecimals); @@ -114,6 +117,13 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { allowListEmpty ); + dealAddressMultipleVestingSchedules = upFrontDealFactory.createUpFrontDeal( + dealData, + dealConfigMultipleVestingSchedules, + nftCollectionRulesEmpty, + allowListEmpty + ); + vm.stopPrank(); vm.startPrank(dealHolderAddress); @@ -143,6 +153,9 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { underlyingDealToken.approve(address(dealAddressNftGating721IdRanges), type(uint256).max); AelinUpFrontDeal(dealAddressNftGating721IdRanges).depositUnderlyingTokens(1e35); + underlyingDealToken.approve(address(dealAddressMultipleVestingSchedules), type(uint256).max); + AelinUpFrontDeal(dealAddressMultipleVestingSchedules).depositUnderlyingTokens(1e35); + vm.stopPrank(); } @@ -255,18 +268,78 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); dealConfig.purchaseDuration = 10 days; - dealConfig.vestingCliffPeriod = _vestingCliffPeriod; - dealConfig.vestingPeriod = _vestingPeriod; + dealConfig.vestingSchedules[0].vestingCliffPeriod = _vestingCliffPeriod; + dealConfig.vestingSchedules[0].vestingPeriod = _vestingPeriod; vm.expectRevert("max 5 year cliff"); upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); - dealConfig.vestingCliffPeriod = 365 days; + dealConfig.vestingSchedules[0].vestingCliffPeriod = 365 days; vm.expectRevert("max 5 year vesting"); upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); vm.stopPrank(); } + function test_CreateUpFrontDeal_RevertWhen_WrongVestingDurationsAcrossVestingSchedules( + uint256 _vestingCliffPeriod, + uint256 _vestingPeriod + ) public { + vm.startPrank(dealCreatorAddress); + vm.assume(_vestingCliffPeriod > 1825 days); + vm.assume(_vestingPeriod > 1825 days); + + AelinAllowList.InitData memory allowListEmpty; + IAelinUpFrontDeal.UpFrontDealData memory dealData = getDealData(); + IAelinUpFrontDeal.UpFrontDealConfig memory dealConfig = getDealConfig(); + + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](2); + + //These vesting periods are fine + vestingSchedules[0].purchaseTokenPerDealToken = 3e18; + vestingSchedules[0].vestingCliffPeriod = 60 days; + vestingSchedules[0].vestingPeriod = 365 days; + + //These aren't + vestingSchedules[1].purchaseTokenPerDealToken = 3e18; + vestingSchedules[1].vestingCliffPeriod = _vestingCliffPeriod; + vestingSchedules[1].vestingPeriod = _vestingPeriod; + + dealConfig.vestingSchedules = vestingSchedules; + + vm.expectRevert("max 5 year cliff"); + upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); + + dealConfig.vestingSchedules[1].vestingCliffPeriod = 365 days; + vm.expectRevert("max 5 year vesting"); + upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); + + vm.stopPrank(); + } + + function test_CreateUpFrontDeal_RevertWhen_IncorrectVestingSchedulesLength() public { + vm.startPrank(dealCreatorAddress); + + AelinAllowList.InitData memory allowListEmpty; + IAelinUpFrontDeal.UpFrontDealData memory dealData = getDealData(); + IAelinUpFrontDeal.UpFrontDealConfig memory dealConfig = getDealConfig(); + + //First no vesting schedules + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules; + dealConfig.vestingSchedules = vestingSchedules; + + vm.expectRevert("no vesting schedules"); + upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); + + //Then too many + vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](11); + dealConfig.vestingSchedules = vestingSchedules; + + vm.expectRevert("too many vesting schedules"); + upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); + + vm.stopPrank(); + } + function test_CreateUpFrontDeal_RevertWhen_WrongDealSetup() public { vm.startPrank(dealCreatorAddress); @@ -279,15 +352,47 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); dealConfig.underlyingDealTokenTotal = 100; - dealConfig.purchaseTokenPerDealToken = 0; + dealConfig.vestingSchedules[0].purchaseTokenPerDealToken = 0; vm.expectRevert("invalid deal price"); upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); - dealConfig.purchaseTokenPerDealToken = 1; - dealConfig.underlyingDealTokenTotal = 1; - vm.expectRevert("intended raise too small"); + dealConfig.vestingSchedules[0].purchaseTokenPerDealToken = 1; + dealConfig.underlyingDealTokenTotal = 1e28; + vm.expectRevert("raise min > deal total"); + upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); + + vm.stopPrank(); + } + + function test_CreateUpFrontDeal_RevertWhen_WrongDealSetupAcrossMultipleVestingSchedules() public { + vm.startPrank(dealCreatorAddress); + + AelinAllowList.InitData memory allowListEmpty; + IAelinUpFrontDeal.UpFrontDealData memory dealData = getDealData(); + IAelinUpFrontDeal.UpFrontDealConfig memory dealConfig = getDealConfig(); + + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](3); + + vestingSchedules[0].purchaseTokenPerDealToken = 3e18; + vestingSchedules[0].vestingCliffPeriod = 60 days; + vestingSchedules[0].vestingPeriod = 365 days; + + vestingSchedules[1].purchaseTokenPerDealToken = 3e18; + vestingSchedules[1].vestingCliffPeriod = 60 days; + vestingSchedules[1].vestingPeriod = 365 days; + + //Invalid price + vestingSchedules[2].purchaseTokenPerDealToken = 0; + vestingSchedules[2].vestingCliffPeriod = 60 days; + vestingSchedules[2].vestingPeriod = 365 days; + + dealConfig.vestingSchedules = vestingSchedules; + + dealConfig.underlyingDealTokenTotal = 100; + vm.expectRevert("invalid deal price"); upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); + dealConfig.vestingSchedules[2].purchaseTokenPerDealToken = 1; dealConfig.underlyingDealTokenTotal = 1e28; vm.expectRevert("raise min > deal total"); upFrontDealFactory.createUpFrontDeal(dealData, dealConfig, nftCollectionRulesEmpty, allowListEmpty); @@ -529,8 +634,8 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).tokenCount(), 0); // underlying hasn't been deposited yet so deal has't started assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).purchaseExpiry(), 0); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiry(), 0); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiry(), 0); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiries(0), 0); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiries(0), 0); // deal data (tempString, , , , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealData(); assertEq(tempString, "DEAL"); @@ -547,25 +652,26 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { (, , , , , , tempUint, , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealData(); assertEq(tempUint, 1e18); // deal config - (tempUint, , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (tempUint, , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); assertEq(tempUint, 1e35); - (, tempUint, , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); - assertEq(tempUint, 3e18); - (, , tempUint, , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (, tempUint, , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); assertEq(tempUint, 1e28); - (, , , tempUint, , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (, , tempUint, ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); assertEq(tempUint, 10 days); - (, , , , tempUint, , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); - assertEq(tempUint, 365 days); - (, , , , , tempUint, ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); - assertEq(tempUint, 60 days); - (, , , , , , tempBool) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (, , , tempBool) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); assertFalse(tempBool); + // vesting schedule + (tempUint, , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).getVestingScheduleDetails(0); + assertEq(tempUint, 3e18); + (, tempUint, ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).getVestingScheduleDetails(0); + assertEq(tempUint, 60 days); + (, , tempUint) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).getVestingScheduleDetails(0); + assertEq(tempUint, 365 days); // test allow list - (, , , tempBool) = AelinUpFrontDeal(dealAddressNoDeallocation).getAllowList(address(0)); + (, , , tempBool) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).getAllowList(address(0)); assertFalse(tempBool); // test nft gating - (, tempBool) = AelinUpFrontDeal(dealAddressNoDeallocation).getNftGatingDetails(address(0), 0); + (, tempBool) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).getNftGatingDetails(address(0), 0); assertFalse(tempBool); } @@ -586,8 +692,8 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { assertEq(AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).tokenCount(), 0); // underlying hasn't been deposited yet so deal has't started assertEq(AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).purchaseExpiry(), 0); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).vestingCliffExpiry(), 0); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).vestingExpiry(), 0); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).vestingCliffExpiries(0), 0); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).vestingExpiries(0), 0); // deal data (tempString, , , , , , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealData(); assertEq(tempString, "DEAL"); @@ -604,20 +710,21 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { (, , , , , , tempUint, , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealData(); assertEq(tempUint, 1e18); // deal config - (tempUint, , , , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); + (tempUint, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); assertEq(tempUint, 1e35); - (, tempUint, , , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); - assertEq(tempUint, 3e18); - (, , tempUint, , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); + (, tempUint, , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); assertEq(tempUint, 0); - (, , , tempUint, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); + (, , tempUint, ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); assertEq(tempUint, 10 days); - (, , , , tempUint, , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); - assertEq(tempUint, 365 days); - (, , , , , tempUint, ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); - assertEq(tempUint, 60 days); - (, , , , , , tempBool) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); + (, , , tempBool) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).dealConfig(); assertTrue(tempBool); + // vesting schedule + (tempUint, , ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).getVestingScheduleDetails(0); + assertEq(tempUint, 3e18); + (, tempUint, ) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).getVestingScheduleDetails(0); + assertEq(tempUint, 60 days); + (, , tempUint) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).getVestingScheduleDetails(0); + assertEq(tempUint, 365 days); // test allow list (, , , tempBool) = AelinUpFrontDeal(dealAddressAllowDeallocationNoDeposit).getAllowList(address(0)); assertFalse(tempBool); @@ -643,9 +750,9 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).tokenCount(), 0); // underlying has deposited so deal has started assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseExpiry(), block.timestamp + 10 days); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiry(), block.timestamp + 10 days + 60 days); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiries(0), block.timestamp + 10 days + 60 days); assertEq( - AelinUpFrontDeal(dealAddressNoDeallocation).vestingExpiry(), + AelinUpFrontDeal(dealAddressNoDeallocation).vestingExpiries(0), block.timestamp + 10 days + 60 days + 365 days ); // deal data @@ -664,20 +771,21 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { (, , , , , , tempUint, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealData(); assertEq(tempUint, 1e18); // deal config - (tempUint, , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (tempUint, , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); assertEq(tempUint, 1e35); - (, tempUint, , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); - assertEq(tempUint, 3e18); - (, , tempUint, , , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (, tempUint, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); assertEq(tempUint, 1e28); - (, , , tempUint, , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (, , tempUint, ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); assertEq(tempUint, 10 days); - (, , , , tempUint, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); - assertEq(tempUint, 365 days); - (, , , , , tempUint, ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); - assertEq(tempUint, 60 days); - (, , , , , , tempBool) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (, , , tempBool) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); assertFalse(tempBool); + // vesting schedule + (tempUint, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); + assertEq(tempUint, 3e18); + (, tempUint, ) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); + assertEq(tempUint, 60 days); + (, , tempUint) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); + assertEq(tempUint, 365 days); // test allow list (, , , tempBool) = AelinUpFrontDeal(dealAddressNoDeallocation).getAllowList(address(0)); assertFalse(tempBool); @@ -705,8 +813,8 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { assertEq(deal.tokenCount(), 0); // underlying has deposited so deal has started assertEq(deal.purchaseExpiry(), block.timestamp + 10 days); - assertEq(deal.vestingCliffExpiry(), block.timestamp + 10 days + 60 days); - assertEq(deal.vestingExpiry(), block.timestamp + 10 days + 60 days + 365 days); + assertEq(deal.vestingCliffExpiries(0), block.timestamp + 10 days + 60 days); + assertEq(deal.vestingExpiries(0), block.timestamp + 10 days + 60 days + 365 days); // deal data (tempString, , , , , , , , ) = deal.dealData(); assertEq(tempString, "DEAL"); @@ -723,20 +831,21 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { (, , , , , , tempUint, , ) = deal.dealData(); assertEq(tempUint, 1e18); // deal config - (tempUint, , , , , , ) = deal.dealConfig(); + (tempUint, , , ) = deal.dealConfig(); assertEq(tempUint, 1e35); - (, tempUint, , , , , ) = deal.dealConfig(); - assertEq(tempUint, 3e18); - (, , tempUint, , , , ) = deal.dealConfig(); + (, tempUint, , ) = deal.dealConfig(); assertEq(tempUint, 1e28); - (, , , tempUint, , , ) = deal.dealConfig(); + (, , tempUint, ) = deal.dealConfig(); assertEq(tempUint, 10 days); - (, , , , tempUint, , ) = deal.dealConfig(); - assertEq(tempUint, 365 days); - (, , , , , tempUint, ) = deal.dealConfig(); - assertEq(tempUint, 60 days); - (, , , , , , tempBool) = deal.dealConfig(); + (, , , tempBool) = deal.dealConfig(); assertFalse(tempBool); + // vesting schedule + (tempUint, , ) = deal.getVestingScheduleDetails(0); + assertEq(tempUint, 3e18); + (, tempUint, ) = deal.getVestingScheduleDetails(0); + assertEq(tempUint, 60 days); + (, , tempUint) = deal.getVestingScheduleDetails(0); + assertEq(tempUint, 365 days); // test allow list (, , , tempBool) = deal.getAllowList(address(0)); assertFalse(tempBool); @@ -763,9 +872,9 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).tokenCount(), 0); // underlying hasn't been deposited yet so deal has't started assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseExpiry(), block.timestamp + 10 days); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiry(), block.timestamp + 10 days + 60 days); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).vestingCliffExpiries(0), block.timestamp + 10 days + 60 days); assertEq( - AelinUpFrontDeal(dealAddressNoDeallocation).vestingExpiry(), + AelinUpFrontDeal(dealAddressNoDeallocation).vestingExpiries(0), block.timestamp + 10 days + 60 days + 365 days ); // deal data @@ -784,20 +893,21 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { (, , , , , , tempUint, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealData(); assertEq(tempUint, 1e18); // deal config - (tempUint, , , , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (tempUint, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); assertEq(tempUint, 1e35); - (, tempUint, , , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); - assertEq(tempUint, 3e18); - (, , tempUint, , , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (, tempUint, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); assertEq(tempUint, 0); - (, , , tempUint, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (, , tempUint, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); assertEq(tempUint, 10 days); - (, , , , tempUint, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); - assertEq(tempUint, 365 days); - (, , , , , tempUint, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); - assertEq(tempUint, 60 days); - (, , , , , , tempBool) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (, , , tempBool) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); assertTrue(tempBool); + // vesting schedule + (tempUint, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).getVestingScheduleDetails(0); + assertEq(tempUint, 3e18); + (, tempUint, ) = AelinUpFrontDeal(dealAddressAllowDeallocation).getVestingScheduleDetails(0); + assertEq(tempUint, 60 days); + (, , tempUint) = AelinUpFrontDeal(dealAddressAllowDeallocation).getVestingScheduleDetails(0); + assertEq(tempUint, 365 days); // test allow list (, , , tempBool) = AelinUpFrontDeal(dealAddressAllowDeallocation).getAllowList(address(0)); assertFalse(tempBool); @@ -821,7 +931,7 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { assertEq(AelinUpFrontDeal(dealAddressAllowList).tokenCount(), 0); // underlying has deposited so deal has started assertEq(AelinUpFrontDeal(dealAddressAllowList).purchaseExpiry(), block.timestamp + 10 days); - assertEq(AelinUpFrontDeal(dealAddressAllowList).vestingCliffExpiry(), block.timestamp + 10 days + 60 days); + assertEq(AelinUpFrontDeal(dealAddressAllowList).vestingCliffExpiries(0), block.timestamp + 10 days + 60 days); // test allow list address[] memory tempAddressArray; uint256[] memory tempUintArray; @@ -854,6 +964,71 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { assertFalse(tempBool); } + function test_CreateUpFrontDeal_MultipleVestingSchedules() public { + string memory tempString; + address tempAddress; + uint256 tempUint; + bool tempBool; + + AelinUpFrontDeal deal = AelinUpFrontDeal(dealAddressMultipleVestingSchedules); + // balance + assertEq(underlyingDealToken.balanceOf(address(dealAddressMultipleVestingSchedules)), 1e35); + // deal contract storage + assertEq(deal.dealFactory(), address(upFrontDealFactory)); + assertEq(deal.name(), "aeUpFrontDeal-DEAL"); + assertEq(deal.symbol(), "aeUD-DEAL"); + assertEq(deal.dealStart(), block.timestamp); + assertEq(deal.aelinEscrowLogicAddress(), address(testEscrow)); + assertEq(deal.aelinTreasuryAddress(), aelinTreasury); + assertEq(deal.tokenCount(), 0); + // underlying has deposited so deal has started + assertEq(deal.purchaseExpiry(), block.timestamp + 10 days); + // deal data + (tempString, , , , , , , , ) = deal.dealData(); + assertEq(tempString, "DEAL"); + (, tempString, , , , , , , ) = deal.dealData(); + assertEq(tempString, "DEAL"); + (, , tempAddress, , , , , , ) = deal.dealData(); + assertEq(tempAddress, address(purchaseToken)); + (, , , tempAddress, , , , , ) = deal.dealData(); + assertEq(tempAddress, address(underlyingDealToken)); + (, , , , tempAddress, , , , ) = deal.dealData(); + assertEq(tempAddress, dealHolderAddress); + (, , , , , tempAddress, , , ) = deal.dealData(); + assertEq(tempAddress, dealCreatorAddress); + (, , , , , , tempUint, , ) = deal.dealData(); + assertEq(tempUint, 1e18); + // deal config + (tempUint, , , ) = deal.dealConfig(); + assertEq(tempUint, 1e35); + (, tempUint, , ) = deal.dealConfig(); + assertEq(tempUint, 1e28); + (, , tempUint, ) = deal.dealConfig(); + assertEq(tempUint, 10 days); + (, , , tempBool) = deal.dealConfig(); + assertFalse(tempBool); + + // vesting schedules + for (uint256 i; i < 10; i++) { + (tempUint, , ) = deal.getVestingScheduleDetails(i); + assertEq(tempUint, 3e18 + (1e18 * i)); + (, tempUint, ) = deal.getVestingScheduleDetails(i); + assertEq(tempUint, 60 days + (10 days * i)); + (, , tempUint) = deal.getVestingScheduleDetails(i); + assertEq(tempUint, 365 days + (10 days * i)); + assertEq(deal.vestingCliffExpiries(i), block.timestamp + 10 days + 60 days + (10 days * i)); + assertEq(deal.vestingExpiries(i), block.timestamp + 10 days + 60 days + 365 days + (20 days * i)); + } + + // test allow list + (, , , tempBool) = deal.getAllowList(address(0)); + assertFalse(tempBool); + // test nft gating + (, tempBool) = deal.getNftGatingDetails(address(0), 0); + assertFalse(tempBool); + assertEq(underlyingDealTokenLowDecimals.decimals(), 2); + } + function test_CreateUpFrontDeal_NftGating721() public { address tempAddress; uint256 tempUint; @@ -870,7 +1045,7 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { assertEq(AelinUpFrontDeal(dealAddressNftGating721).tokenCount(), 0); // underlying has deposited so deal has started assertEq(AelinUpFrontDeal(dealAddressNftGating721).purchaseExpiry(), block.timestamp + 10 days); - assertEq(AelinUpFrontDeal(dealAddressNftGating721).vestingCliffExpiry(), block.timestamp + 10 days + 60 days); + assertEq(AelinUpFrontDeal(dealAddressNftGating721).vestingCliffExpiries(0), block.timestamp + 10 days + 60 days); // test allow list (, , , tempBool) = AelinUpFrontDeal(dealAddressNftGating721).getAllowList(address(0)); assertFalse(tempBool); @@ -906,7 +1081,7 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { // underlying has deposited so deal has started assertEq(AelinUpFrontDeal(dealAddressNftGating721IdRanges).purchaseExpiry(), block.timestamp + 10 days); assertEq( - AelinUpFrontDeal(dealAddressNftGating721IdRanges).vestingCliffExpiry(), + AelinUpFrontDeal(dealAddressNftGating721IdRanges).vestingCliffExpiries(0), block.timestamp + 10 days + 60 days ); // test allow list @@ -953,7 +1128,7 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { assertEq(AelinUpFrontDeal(dealAddressNftGating1155).tokenCount(), 0); // underlying has deposited so deal has started assertEq(AelinUpFrontDeal(dealAddressNftGating1155).purchaseExpiry(), block.timestamp + 10 days); - assertEq(AelinUpFrontDeal(dealAddressNftGating1155).vestingCliffExpiry(), block.timestamp + 10 days + 60 days); + assertEq(AelinUpFrontDeal(dealAddressNftGating1155).vestingCliffExpiries(0), block.timestamp + 10 days + 60 days); // test allow list (, , , tempBool) = AelinUpFrontDeal(dealAddressNftGating1155).getAllowList(address(0)); assertFalse(tempBool); @@ -989,5 +1164,6 @@ contract AelinUpFrontDealInitTest is Test, AelinTestUtils, IAelinUpFrontDeal { vm.assume(_testAddress != address(0)); vm.prank(_testAddress); assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).claimableUnderlyingTokens(_tokenId), 0); + vm.stopPrank(); } } diff --git a/test/tests/AelinUpFrontDeal.purchase.t.sol b/test/tests/AelinUpFrontDeal.purchase.t.sol index d7442aac..0149b883 100644 --- a/test/tests/AelinUpFrontDeal.purchase.t.sol +++ b/test/tests/AelinUpFrontDeal.purchase.t.sol @@ -29,6 +29,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal address dealAddressNftGating721; address dealAddressNftGating1155; address dealAddressNftGating721IdRanges; + address dealAddressMultipleVestingSchedules; function setUp() public { AelinAllowList.InitData memory allowListEmpty; @@ -43,6 +44,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal IAelinUpFrontDeal.UpFrontDealData memory dealData = getDealData(); IAelinUpFrontDeal.UpFrontDealConfig memory dealConfig = getDealConfig(); IAelinUpFrontDeal.UpFrontDealConfig memory dealConfigAllowDeallocation = getDealConfigAllowDeallocation(); + IAelinUpFrontDeal.UpFrontDealConfig + memory dealConfigMultipleVestingSchedules = getDealConfigMultipleVestingSchedules(); AelinNftGating.NftCollectionRules[] memory nftCollectionRules721 = getERC721Collection(); AelinNftGating.NftCollectionRules[] memory nftCollectionRules1155 = getERC1155Collection(); @@ -106,6 +109,13 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal allowListEmpty ); + dealAddressMultipleVestingSchedules = upFrontDealFactory.createUpFrontDeal( + dealData, + dealConfigMultipleVestingSchedules, + nftCollectionRulesEmpty, + allowListEmpty + ); + vm.stopPrank(); vm.startPrank(dealHolderAddress); @@ -131,12 +141,15 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal underlyingDealToken.approve(address(dealAddressNftGating721IdRanges), type(uint256).max); AelinUpFrontDeal(dealAddressNftGating721IdRanges).depositUnderlyingTokens(1e35); + underlyingDealToken.approve(address(dealAddressMultipleVestingSchedules), type(uint256).max); + AelinUpFrontDeal(dealAddressMultipleVestingSchedules).depositUnderlyingTokens(1e35); + vm.stopPrank(); } /*////////////////////////////////////////////////////////////// - depositUnderlyingTokens() - //////////////////////////////////////////////////////////////*/ + depositUnderlyingTokens() + //////////////////////////////////////////////////////////////*/ function testFuzz_DepositUnderlyingTokens_RevertWhen_NotHolder(address _testAddress, uint256 _depositAmount) public { vm.assume(_testAddress != dealHolderAddress); @@ -175,7 +188,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal underlyingDealToken.approve(address(dealAddressNoDeallocationNoDeposit), type(uint256).max); // first deposit - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); (bool success, uint256 result) = SafeMath.tryAdd(_firstDepositAmount, _secondDepositAmount); vm.assume(success); vm.assume(result >= underlyingDealTokenTotal); @@ -187,8 +200,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal uint256 balanceAfterDeposit = underlyingDealToken.balanceOf(dealAddressNoDeallocationNoDeposit); assertEq(balanceAfterDeposit, balanceBeforeDeposit + _firstDepositAmount); assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).purchaseExpiry(), 0); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiry(), 0); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiry(), 0); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiries(0), 0); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiries(0), 0); // second deposit balanceBeforeDeposit = balanceAfterDeposit; @@ -199,11 +212,11 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal assertEq(balanceAfterDeposit, balanceBeforeDeposit + _secondDepositAmount); assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).purchaseExpiry(), block.timestamp + 10 days); assertEq( - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiry(), + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiries(0), block.timestamp + 10 days + 60 days ); assertEq( - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiry(), + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiries(0), block.timestamp + 10 days + 60 days + 365 days ); @@ -213,7 +226,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal function testFuzz_DepositUnderlyingTokens_FullDeposit(uint256 _depositAmount) public { vm.startPrank(dealHolderAddress); - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); uint256 balanceBeforeDeposit = underlyingDealToken.balanceOf(address(dealAddressNoDeallocationNoDeposit)); vm.assume(_depositAmount >= underlyingDealTokenTotal - balanceBeforeDeposit); @@ -227,11 +240,11 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal assertEq(balanceAfterDeposit, balanceBeforeDeposit + _depositAmount); assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).purchaseExpiry(), block.timestamp + 10 days); assertEq( - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiry(), + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiries(0), block.timestamp + 10 days + 60 days ); assertEq( - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiry(), + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiries(0), block.timestamp + 10 days + 60 days + 365 days ); @@ -247,7 +260,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal function testFuzz_DepositUnderlyingTokens_DepositByDirectTransfer(address _depositor, uint256 _depositAmount) public { vm.assume(_depositor != dealHolderAddress); vm.assume(_depositor != address(0)); - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); uint256 balanceBeforeDeposit = underlyingDealToken.balanceOf(address(dealAddressNoDeallocationNoDeposit)); vm.assume(_depositAmount >= underlyingDealTokenTotal - balanceBeforeDeposit); vm.startPrank(_depositor); @@ -263,8 +276,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal // deposit is still not complete assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).purchaseExpiry(), 0); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiry(), 0); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiry(), 0); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiries(0), 0); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiries(0), 0); // depositUnderlyingTokens() still needs to be called vm.stopPrank(); @@ -276,11 +289,11 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal // deposit is now flagged as completed assertEq(AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).purchaseExpiry(), block.timestamp + 10 days); assertEq( - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiry(), + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingCliffExpiries(0), block.timestamp + 10 days + 60 days ); assertEq( - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiry(), + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).vestingExpiries(0), block.timestamp + 10 days + 60 days + 365 days ); @@ -348,8 +361,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal } /*////////////////////////////////////////////////////////////// - vouch() - //////////////////////////////////////////////////////////////*/ + vouch() + //////////////////////////////////////////////////////////////*/ function testFuzz_Vouch(address _attestant) public { vm.startPrank(_attestant); @@ -360,8 +373,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal } /*////////////////////////////////////////////////////////////// - disavow() - //////////////////////////////////////////////////////////////*/ + disavow() + //////////////////////////////////////////////////////////////*/ function testFuzz_Disavow(address _attestant) public { vm.startPrank(_attestant); @@ -372,8 +385,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal } /*////////////////////////////////////////////////////////////// - withdrawExcess() - //////////////////////////////////////////////////////////////*/ + withdrawExcess() + //////////////////////////////////////////////////////////////*/ function testFuzz_WithdrawExcess_RevertWhen_NotHolder(address _initiator) public { vm.assume(_initiator != dealHolderAddress); @@ -384,7 +397,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal } function testFuzz_WithdrawExcess_RevertWhen_NoExcessToWithdraw(uint256 _depositAmount) public { - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); vm.assume(_depositAmount <= underlyingDealTokenTotal); vm.startPrank(dealHolderAddress); deal(address(underlyingDealToken), dealHolderAddress, type(uint256).max); @@ -396,7 +409,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal } function testFuzz_WithdrawExcess(uint256 _depositAmount) public { - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); vm.assume(_depositAmount > underlyingDealTokenTotal); vm.startPrank(dealHolderAddress); @@ -414,14 +427,19 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal } /*////////////////////////////////////////////////////////////// - acceptDeal() + acceptDeal() //////////////////////////////////////////////////////////////*/ function testFuzz_AcceptDeal_RevertWhen_DepositIncomplete(address _user, uint256 _purchaseAmount) public { vm.startPrank(_user); AelinNftGating.NftPurchaseList[] memory nftPurchaseList; vm.expectRevert("underlying deposit incomplete"); - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).acceptDeal( + nftPurchaseList, + merkleDataEmpty, + _purchaseAmount, + 0 + ); vm.stopPrank(); } @@ -432,7 +450,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.startPrank(dealHolderAddress); deal(address(underlyingDealToken), dealHolderAddress, type(uint256).max); underlyingDealToken.approve(address(dealAddressNoDeallocationNoDeposit), type(uint256).max); - (uint256 underlyingDealTokenTotal, , , , , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).dealConfig(); AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).depositUnderlyingTokens(underlyingDealTokenTotal); vm.stopPrank(); @@ -441,13 +459,23 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.warp(purchaseExpiry + 1000); vm.startPrank(_user); vm.expectRevert("not in purchase window"); - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).acceptDeal( + nftPurchaseList, + merkleDataEmpty, + _purchaseAmount, + 0 + ); // try on a contract that was deposited during intialize purchaseExpiry = AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).purchaseExpiry(); vm.warp(purchaseExpiry + 1000); vm.expectRevert("not in purchase window"); - AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNoDeallocationNoDeposit).acceptDeal( + nftPurchaseList, + merkleDataEmpty, + _purchaseAmount, + 0 + ); vm.stopPrank(); } @@ -458,7 +486,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user1, tokenAmount); purchaseToken.approve(address(dealAddressNoDeallocation), type(uint256).max); vm.expectRevert("not enough purchaseToken"); - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, tokenAmount + 1); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, tokenAmount + 1, 0); vm.stopPrank(); } @@ -469,7 +497,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal purchaseToken.approve(address(dealAddressAllowList), type(uint256).max); (, , uint256 allocatedAmount, ) = AelinUpFrontDeal(dealAddressAllowList).getAllowList(user1); vm.expectRevert("more than allocation"); - AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, allocatedAmount + 1); + AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, allocatedAmount + 1, 0); vm.stopPrank(); } @@ -479,16 +507,15 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressAllowList), type(uint256).max); vm.expectRevert("purchase amount too small"); - AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, 0); + AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, 0, 0); vm.stopPrank(); } function test_AcceptDeal_RevertWhen_AmountOverTotal() public { uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); AelinNftGating.NftPurchaseList[] memory nftPurchaseList; - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal( - dealAddressNoDeallocation - ).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); uint256 raiseAmount = (underlyingDealTokenTotal * purchaseTokenPerDealToken) / 10 ** underlyingTokenDecimals; @@ -500,14 +527,14 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal uint256 expectedPoolShareAmount = ((raiseAmount + 1e18) * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; assertGt(expectedPoolShareAmount, underlyingDealTokenTotal); vm.expectRevert("purchased amount > total"); - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, raiseAmount + 1e18); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, raiseAmount + 1e18, 0); // User 1 now deposits less than the total purchase amount uint256 purchaseAmount1 = raiseAmount - 2e18; expectedPoolShareAmount = (purchaseAmount1 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, purchaseAmount1, purchaseAmount1, expectedPoolShareAmount, expectedPoolShareAmount); - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount1); + emit AcceptDeal(user1, 0, purchaseAmount1, purchaseAmount1, expectedPoolShareAmount, expectedPoolShareAmount); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount1, 0); vm.stopPrank(); // User 2 now deposits more than the total purchase amount @@ -519,7 +546,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal purchaseTokenPerDealToken; assertGt(totalPoolShares, underlyingDealTokenTotal); vm.expectRevert("purchased amount > total"); - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount2); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount2, 0); vm.stopPrank(); } @@ -530,7 +557,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user4, type(uint256).max); purchaseToken.approve(address(dealAddressAllowList), type(uint256).max); vm.expectRevert("more than allocation"); - AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -541,7 +568,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressAllowList), type(uint256).max); vm.expectRevert("pool does not have an NFT list"); - AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -552,7 +579,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressNftGating721), type(uint256).max); vm.expectRevert("collection should not be null"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -562,7 +589,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressNftGating721), type(uint256).max); vm.expectRevert("must provide purchase list"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -573,7 +600,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressNftGating721), type(uint256).max); vm.expectRevert("collection not in the pool"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -591,7 +618,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal MockERC721(collection721_1).mint(user2, 1); MockERC721(collection721_1).mint(user2, 2); vm.expectRevert("has to be the token owner"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -607,7 +634,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal MockERC721(collection721_1).mint(user2, 1); MockERC721(collection721_1).mint(user2, 2); vm.expectRevert("ERC721: invalid token ID"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -625,7 +652,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal MockERC721(collection721_1).mint(user2, 1); MockERC721(collection721_1).mint(user2, 2); vm.expectRevert("ERC721: invalid token ID"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -643,15 +670,15 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal MockERC721(collection721_1).mint(user1, 14); MockERC721(collection721_1).mint(user1, 15); vm.expectRevert("tokenId not in range"); - AelinUpFrontDeal(dealAddressNftGating721IdRanges).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating721IdRanges).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } function testFuzz_AcceptDeal_RevertWhen_ERC721OverAllowance(uint256 _purchaseAmount) public { uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal( - dealAddressNoDeallocation - ).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); + uint256 raiseAmount = (underlyingDealTokenTotal * purchaseTokenPerDealToken) / 10 ** underlyingTokenDecimals; vm.assume(_purchaseAmount > raiseAmount); @@ -669,7 +696,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.startPrank(user1); vm.expectRevert("purchase amount greater than max"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -687,7 +714,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal MockERC1155(collection1155_1).mint(user2, 1, 1, ""); MockERC1155(collection1155_1).mint(user2, 2, 1, ""); vm.expectRevert("erc1155 balance too low"); - AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -705,7 +732,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal MockERC1155(collection1155_1).mint(user1, 1, 1, ""); MockERC1155(collection1155_1).mint(user1, 2, 1, ""); vm.expectRevert("erc1155 balance too low"); - AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -722,7 +749,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal MockERC1155(collection1155_1).mint(user1, 10, 1, ""); MockERC1155(collection1155_1).mint(user1, 11, 1, ""); vm.expectRevert("tokenId not in the pool"); - AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -737,7 +764,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal MockERC1155(collection1155_1).mint(user1, 10, 1, ""); MockERC1155(collection1155_1).mint(user1, 11, 1, ""); vm.expectRevert("tokenId not in the pool"); - AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); vm.stopPrank(); } @@ -787,7 +814,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user, type(uint256).max); purchaseToken.approve(address(merkleDealAddress), type(uint256).max); vm.expectRevert("purchasing more than allowance"); - AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 300000000000000000001); + AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 300000000000000000001, 0); vm.stopPrank(); } @@ -836,7 +863,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user, type(uint256).max); purchaseToken.approve(address(merkleDealAddress), type(uint256).max); vm.expectRevert("MerkleTree.sol: Invalid proof."); - AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 100); + AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 100, 0); vm.stopPrank(); } @@ -887,7 +914,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), _user, type(uint256).max); purchaseToken.approve(address(merkleDealAddress), type(uint256).max); vm.expectRevert("cant purchase others tokens"); - AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 101); + AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 101, 0); vm.stopPrank(); } @@ -935,9 +962,9 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.startPrank(user); deal(address(purchaseToken), user, type(uint256).max); purchaseToken.approve(address(merkleDealAddress), type(uint256).max); - AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 100); + AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 100, 0); vm.expectRevert("Already purchased tokens"); - AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 100); + AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 100, 0); vm.stopPrank(); } @@ -945,9 +972,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.assume(_purchaseAmount > 0); AelinNftGating.NftPurchaseList[] memory nftPurchaseList; uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal( - dealAddressNoDeallocation - ).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressNoDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNoDeallocation).getVestingScheduleDetails(0); uint256 raiseAmount = (underlyingDealTokenTotal * purchaseTokenPerDealToken) / 10 ** underlyingTokenDecimals; vm.assume(_purchaseAmount <= raiseAmount); uint256 firstPurchase = _purchaseAmount / 4; @@ -963,12 +989,12 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal purchaseToken.approve(address(dealAddressNoDeallocation), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, firstPurchase, firstPurchase, poolSharesAmount, poolSharesAmount); - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, firstPurchase); + emit AcceptDeal(user1, 0, firstPurchase, firstPurchase, poolSharesAmount, poolSharesAmount); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, firstPurchase, 0); assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).totalPoolShares(), poolSharesAmount); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).poolSharesPerUser(user1), poolSharesAmount); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).poolSharesPerUser(user1, 0), poolSharesAmount); assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).totalPurchasingAccepted(), firstPurchase); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseTokensPerUser(user1), firstPurchase); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseTokensPerUser(user1, 0), firstPurchase); // we compute the numbers for the second deposit (same user) uint256 poolSharesAmount2 = (secondPurchase * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; @@ -980,16 +1006,23 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.expectEmit(true, false, false, true); emit AcceptDeal( user1, + 0, secondPurchase, firstPurchase + secondPurchase, poolSharesAmount2, poolSharesAmount + poolSharesAmount2 ); - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, secondPurchase); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, secondPurchase, 0); assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).totalPoolShares(), poolSharesAmount + poolSharesAmount2); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).poolSharesPerUser(user1), poolSharesAmount + poolSharesAmount2); + assertEq( + AelinUpFrontDeal(dealAddressNoDeallocation).poolSharesPerUser(user1, 0), + poolSharesAmount + poolSharesAmount2 + ); assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).totalPurchasingAccepted(), firstPurchase + secondPurchase); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseTokensPerUser(user1), firstPurchase + secondPurchase); + assertEq( + AelinUpFrontDeal(dealAddressNoDeallocation).purchaseTokensPerUser(user1, 0), + firstPurchase + secondPurchase + ); // now with do the same but for a new user vm.stopPrank(); @@ -1001,18 +1034,18 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal purchaseToken.approve(address(dealAddressNoDeallocation), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user2, thirdPurchase, thirdPurchase, poolSharesAmount3, poolSharesAmount3); - AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, thirdPurchase); + emit AcceptDeal(user2, 0, thirdPurchase, thirdPurchase, poolSharesAmount3, poolSharesAmount3); + AelinUpFrontDeal(dealAddressNoDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, thirdPurchase, 0); assertEq( AelinUpFrontDeal(dealAddressNoDeallocation).totalPoolShares(), poolSharesAmount + poolSharesAmount2 + poolSharesAmount3 ); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).poolSharesPerUser(user2), poolSharesAmount3); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).poolSharesPerUser(user2, 0), poolSharesAmount3); assertEq( AelinUpFrontDeal(dealAddressNoDeallocation).totalPurchasingAccepted(), firstPurchase + secondPurchase + thirdPurchase ); - assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseTokensPerUser(user2), thirdPurchase); + assertEq(AelinUpFrontDeal(dealAddressNoDeallocation).purchaseTokensPerUser(user2, 0), thirdPurchase); vm.stopPrank(); } @@ -1024,9 +1057,10 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal ) public { AelinNftGating.NftPurchaseList[] memory nftPurchaseList; uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal( - dealAddressAllowDeallocation - ).dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressAllowDeallocation).getVestingScheduleDetails( + 0 + ); uint256 raiseAmount = (underlyingDealTokenTotal * purchaseTokenPerDealToken) / 10 ** underlyingTokenDecimals; vm.assume(_firstPurchase > 0); vm.assume(_secondPurchase > 0); @@ -1046,12 +1080,12 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal purchaseToken.approve(address(dealAddressAllowDeallocation), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, _firstPurchase, _firstPurchase, poolSharesAmount, poolSharesAmount); - AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _firstPurchase); + emit AcceptDeal(user1, 0, _firstPurchase, _firstPurchase, poolSharesAmount, poolSharesAmount); + AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _firstPurchase, 0); assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).totalPoolShares(), poolSharesAmount); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user1), poolSharesAmount); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user1, 0), poolSharesAmount); assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).totalPurchasingAccepted(), _firstPurchase); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user1), _firstPurchase); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user1, 0), _firstPurchase); // we compute the numbers for the second deposit (same user) uint256 poolSharesAmount2 = (_secondPurchase * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; @@ -1063,20 +1097,21 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.expectEmit(true, false, false, true); emit AcceptDeal( user1, + 0, _secondPurchase, _firstPurchase + _secondPurchase, poolSharesAmount2, poolSharesAmount + poolSharesAmount2 ); - AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _secondPurchase); + AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _secondPurchase, 0); assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).totalPoolShares(), poolSharesAmount + poolSharesAmount2); assertEq( - AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user1), + AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user1, 0), poolSharesAmount + poolSharesAmount2 ); assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).totalPurchasingAccepted(), _firstPurchase + _secondPurchase); assertEq( - AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user1), + AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user1, 0), _firstPurchase + _secondPurchase ); @@ -1090,18 +1125,18 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal purchaseToken.approve(address(dealAddressAllowDeallocation), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user2, _thirdPurchase, _thirdPurchase, poolSharesAmount3, poolSharesAmount3); - AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _thirdPurchase); + emit AcceptDeal(user2, 0, _thirdPurchase, _thirdPurchase, poolSharesAmount3, poolSharesAmount3); + AelinUpFrontDeal(dealAddressAllowDeallocation).acceptDeal(nftPurchaseList, merkleDataEmpty, _thirdPurchase, 0); assertEq( AelinUpFrontDeal(dealAddressAllowDeallocation).totalPoolShares(), poolSharesAmount + poolSharesAmount2 + poolSharesAmount3 ); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user2), poolSharesAmount3); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).poolSharesPerUser(user2, 0), poolSharesAmount3); assertEq( AelinUpFrontDeal(dealAddressAllowDeallocation).totalPurchasingAccepted(), _firstPurchase + _secondPurchase + _thirdPurchase ); - assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user2), _thirdPurchase); + assertEq(AelinUpFrontDeal(dealAddressAllowDeallocation).purchaseTokensPerUser(user2, 0), _thirdPurchase); vm.stopPrank(); } @@ -1121,7 +1156,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.assume(_purchaseAmount3 <= tempAllocatedAmount); uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal(dealAddressAllowList).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressAllowList).getVestingScheduleDetails(0); uint256 poolSharesAmount1 = (_purchaseAmount1 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; vm.assume(poolSharesAmount1 > 0); uint256 poolSharesAmount2 = (_purchaseAmount2 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; @@ -1134,12 +1169,12 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user1, type(uint256).max); purchaseToken.approve(address(dealAddressAllowList), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, _purchaseAmount1, _purchaseAmount1, poolSharesAmount1, poolSharesAmount1); - AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount1); + emit AcceptDeal(user1, 0, _purchaseAmount1, _purchaseAmount1, poolSharesAmount1, poolSharesAmount1); + AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount1, 0); assertEq(AelinUpFrontDeal(dealAddressAllowList).totalPoolShares(), poolSharesAmount1); - assertEq(AelinUpFrontDeal(dealAddressAllowList).poolSharesPerUser(user1), poolSharesAmount1); + assertEq(AelinUpFrontDeal(dealAddressAllowList).poolSharesPerUser(user1, 0), poolSharesAmount1); assertEq(AelinUpFrontDeal(dealAddressAllowList).totalPurchasingAccepted(), _purchaseAmount1); - assertEq(AelinUpFrontDeal(dealAddressAllowList).purchaseTokensPerUser(user1), _purchaseAmount1); + assertEq(AelinUpFrontDeal(dealAddressAllowList).purchaseTokensPerUser(user1, 0), _purchaseAmount1); vm.stopPrank(); // second user deposit @@ -1147,12 +1182,12 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user2, type(uint256).max); purchaseToken.approve(address(dealAddressAllowList), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user2, _purchaseAmount2, _purchaseAmount2, poolSharesAmount2, poolSharesAmount2); - AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount2); + emit AcceptDeal(user2, 0, _purchaseAmount2, _purchaseAmount2, poolSharesAmount2, poolSharesAmount2); + AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount2, 0); assertEq(AelinUpFrontDeal(dealAddressAllowList).totalPoolShares(), poolSharesAmount1 + poolSharesAmount2); - assertEq(AelinUpFrontDeal(dealAddressAllowList).poolSharesPerUser(user2), poolSharesAmount2); + assertEq(AelinUpFrontDeal(dealAddressAllowList).poolSharesPerUser(user2, 0), poolSharesAmount2); assertEq(AelinUpFrontDeal(dealAddressAllowList).totalPurchasingAccepted(), _purchaseAmount1 + _purchaseAmount2); - assertEq(AelinUpFrontDeal(dealAddressAllowList).purchaseTokensPerUser(user2), _purchaseAmount2); + assertEq(AelinUpFrontDeal(dealAddressAllowList).purchaseTokensPerUser(user2, 0), _purchaseAmount2); vm.stopPrank(); // third user deposit @@ -1160,25 +1195,25 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal deal(address(purchaseToken), user3, type(uint256).max); purchaseToken.approve(address(dealAddressAllowList), type(uint256).max); vm.expectEmit(true, false, false, true); - emit AcceptDeal(user3, _purchaseAmount3, _purchaseAmount3, poolSharesAmount3, poolSharesAmount3); - AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount3); + emit AcceptDeal(user3, 0, _purchaseAmount3, _purchaseAmount3, poolSharesAmount3, poolSharesAmount3); + AelinUpFrontDeal(dealAddressAllowList).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount3, 0); assertEq( AelinUpFrontDeal(dealAddressAllowList).totalPoolShares(), poolSharesAmount1 + poolSharesAmount2 + poolSharesAmount3 ); - assertEq(AelinUpFrontDeal(dealAddressAllowList).poolSharesPerUser(user3), poolSharesAmount3); + assertEq(AelinUpFrontDeal(dealAddressAllowList).poolSharesPerUser(user3, 0), poolSharesAmount3); assertEq( AelinUpFrontDeal(dealAddressAllowList).totalPurchasingAccepted(), _purchaseAmount1 + _purchaseAmount2 + _purchaseAmount3 ); - assertEq(AelinUpFrontDeal(dealAddressAllowList).purchaseTokensPerUser(user3), _purchaseAmount3); + assertEq(AelinUpFrontDeal(dealAddressAllowList).purchaseTokensPerUser(user3, 0), _purchaseAmount3); vm.stopPrank(); } function test_AcceptDeal_ERC721() public { // user setup uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal(dealAddressNftGating721).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNftGating721).getVestingScheduleDetails(0); MockERC721(collection721_1).mint(user1, 1); MockERC721(collection721_1).mint(user1, 2); @@ -1245,8 +1280,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal nftPurchaseList[0].collectionAddress = address(collection721_1); nftPurchaseList[0].tokenIds = tokenIdsArray; vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, (2 * purchaseCollection1), (2 * purchaseCollection1), poolSharesAmount, poolSharesAmount); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, (2 * purchaseCollection1)); + emit AcceptDeal(user1, 0, (2 * purchaseCollection1), (2 * purchaseCollection1), poolSharesAmount, poolSharesAmount); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, (2 * purchaseCollection1), 0); // user1 now purchases again using his last token poolSharesAmount = (purchaseCollection1 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; @@ -1259,12 +1294,13 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.expectEmit(true, false, false, true); emit AcceptDeal( user1, + 0, purchaseCollection1, (2 * purchaseCollection1) + purchaseCollection1, poolSharesAmount, totalPoolShares ); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseCollection1); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseCollection1, 0); vm.stopPrank(); // case 2: [collection2] user2 max out their wallet allocation (purchaseAmountPerToken = false) @@ -1281,18 +1317,18 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal // balance(user2) * purchaseCollection2 as user2 can't buy more than the allocation amount for collection2 vm.expectRevert("purchase amount greater than max"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, 6 * purchaseCollection2); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, 6 * purchaseCollection2, 0); // we then make user2 buy the exact amount poolSharesAmount = (purchaseCollection2 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; totalPoolShares = poolSharesAmount; vm.expectEmit(true, false, false, true); - emit AcceptDeal(user2, purchaseCollection2, purchaseCollection2, poolSharesAmount, poolSharesAmount); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseCollection2); + emit AcceptDeal(user2, 0, purchaseCollection2, purchaseCollection2, poolSharesAmount, poolSharesAmount); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseCollection2, 0); // user2 can't reuse the same tokens if they want to purchase again vm.expectRevert("tokenId already used"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseCollection2); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseCollection2, 0); // case 3: [collection1] user2 comes back and max out their allocation (purchaseAmountPerToken = true) nftPurchaseList = new AelinNftGating.NftPurchaseList[](1); @@ -1307,16 +1343,17 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.expectEmit(true, false, false, true); emit AcceptDeal( user2, + 0, (2 * purchaseCollection1), (2 * purchaseCollection1 + purchaseCollection2), poolSharesAmount, poolSharesAmount + totalPoolShares ); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, (2 * purchaseCollection1)); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, (2 * purchaseCollection1), 0); // user2 can't reuse the same tokens if they want to purchase again vm.expectRevert("tokenId already used"); - AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, (2 * purchaseCollection1)); + AelinUpFrontDeal(dealAddressNftGating721).acceptDeal(nftPurchaseList, merkleDataEmpty, (2 * purchaseCollection1), 0); vm.stopPrank(); //checks post-purchase @@ -1349,7 +1386,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal function test_AcceptDeal_ERC1155() public { // nft gating setup uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal(dealAddressNftGating1155).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNftGating1155).getVestingScheduleDetails(0); AelinNftGating.NftPurchaseList[] memory nftPurchaseList = new AelinNftGating.NftPurchaseList[](2); // we mint some tokens @@ -1384,8 +1421,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal // user1 buys the tokens vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, purchaseAmount, purchaseAmount, poolSharesAmount, poolSharesAmount); - AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount); + emit AcceptDeal(user1, 0, purchaseAmount, purchaseAmount, poolSharesAmount, poolSharesAmount); + AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseAmount, 0); // we mint new tokens for both collections MockERC1155(address(collection1155_1)).mint(user1, 1, 100, ""); @@ -1403,12 +1440,13 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal vm.expectEmit(true, false, false, true); emit AcceptDeal( user1, + 0, newPurchaseAmount, purchaseAmount + newPurchaseAmount, newPoolShareAmount, newPoolShareAmount + poolSharesAmount ); - AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, newPurchaseAmount); + AelinUpFrontDeal(dealAddressNftGating1155).acceptDeal(nftPurchaseList, merkleDataEmpty, newPurchaseAmount, 0); // if we only use collection 2, it reverts nftPurchaseList = new AelinNftGating.NftPurchaseList[](1); @@ -1443,10 +1481,13 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal assertTrue(hasNftList); assertEq(AelinUpFrontDeal(dealAddressNftGating1155).totalPoolShares(), newPoolShareAmount + poolSharesAmount); - assertEq(AelinUpFrontDeal(dealAddressNftGating1155).poolSharesPerUser(user1), newPoolShareAmount + poolSharesAmount); + assertEq( + AelinUpFrontDeal(dealAddressNftGating1155).poolSharesPerUser(user1, 0), + newPoolShareAmount + poolSharesAmount + ); assertEq(AelinUpFrontDeal(dealAddressNftGating1155).totalPurchasingAccepted(), purchaseAmount + newPurchaseAmount); assertEq( - AelinUpFrontDeal(dealAddressNftGating1155).purchaseTokensPerUser(user1), + AelinUpFrontDeal(dealAddressNftGating1155).purchaseTokensPerUser(user1, 0), purchaseAmount + newPurchaseAmount ); @@ -1456,7 +1497,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal function test_AcceptDeal_ERC721IdRanges() public { // user setup uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal(dealAddressNftGating721IdRanges).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNftGating721IdRanges) + .getVestingScheduleDetails(0); MockERC721(collection721_1).mint(user1, 1); MockERC721(collection721_1).mint(user1, 2); @@ -1503,14 +1545,24 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal //Second token Id out of range should fail vm.expectRevert("tokenId not in range"); - AelinUpFrontDeal(dealAddressNftGating721IdRanges).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseCollection1); + AelinUpFrontDeal(dealAddressNftGating721IdRanges).acceptDeal( + nftPurchaseList, + merkleDataEmpty, + purchaseCollection1, + 0 + ); //Second token Id in range should succeed tokenIdsArray[1] = 2; nftPurchaseList[0].tokenIds = tokenIdsArray; vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, purchaseCollection1, purchaseCollection1, poolSharesAmount, poolSharesAmount); - AelinUpFrontDeal(dealAddressNftGating721IdRanges).acceptDeal(nftPurchaseList, merkleDataEmpty, purchaseCollection1); + emit AcceptDeal(user1, 0, purchaseCollection1, purchaseCollection1, poolSharesAmount, poolSharesAmount); + AelinUpFrontDeal(dealAddressNftGating721IdRanges).acceptDeal( + nftPurchaseList, + merkleDataEmpty, + purchaseCollection1, + 0 + ); vm.stopPrank(); //checks post-purchase @@ -1531,7 +1583,8 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal function test_AcceptDeal_ERC721IdRanges_Multiple() public { // user setup uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal(dealAddressNftGating721IdRanges).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(dealAddressNftGating721IdRanges) + .getVestingScheduleDetails(0); MockERC721(collection721_1).mint(user1, 1e20); MockERC721(collection721_1).mint(user1, 1e20 + 1); @@ -1585,11 +1638,12 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal nftPurchaseList[0].tokenIds = tokenIdsArray; vm.expectEmit(true, false, false, true); - emit AcceptDeal(user1, (3 * purchaseCollection1), (3 * purchaseCollection1), poolSharesAmount, poolSharesAmount); + emit AcceptDeal(user1, 0, (3 * purchaseCollection1), (3 * purchaseCollection1), poolSharesAmount, poolSharesAmount); AelinUpFrontDeal(dealAddressNftGating721IdRanges).acceptDeal( nftPurchaseList, merkleDataEmpty, - (3 * purchaseCollection1) + (3 * purchaseCollection1), + 0 ); vm.stopPrank(); @@ -1614,6 +1668,70 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal assertTrue(hasNftList); } + function testFuzz_AcceptDeal_MultipleVestingSchedules(uint256 _purchaseAmount1, uint256 _purchaseAmount2) public { + vm.assume(_purchaseAmount1 > 0); + vm.assume(_purchaseAmount2 > 0); + + AelinNftGating.NftPurchaseList[] memory nftPurchaseList; + uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); + + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(dealAddressMultipleVestingSchedules).dealConfig(); + (uint256 purchaseTokenPerDealToken1, , ) = AelinUpFrontDeal(dealAddressMultipleVestingSchedules) + .getVestingScheduleDetails(0); + (uint256 purchaseTokenPerDealToken2, , ) = AelinUpFrontDeal(dealAddressMultipleVestingSchedules) + .getVestingScheduleDetails(1); + + uint256 raiseAmount1 = (underlyingDealTokenTotal * purchaseTokenPerDealToken1) / 10 ** underlyingTokenDecimals; + vm.assume(_purchaseAmount1 <= raiseAmount1); + uint256 raiseAmount2 = (underlyingDealTokenTotal * purchaseTokenPerDealToken2) / 10 ** underlyingTokenDecimals; + vm.assume(_purchaseAmount2 <= raiseAmount2); + + uint256 poolSharesAmount1 = (_purchaseAmount1 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken1; + vm.assume(poolSharesAmount1 > 0); + uint256 poolSharesAmount2 = (_purchaseAmount2 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken2; + vm.assume(poolSharesAmount2 > 0); + + //Accept deal with first vesting schedule and price + vm.startPrank(user1); + deal(address(purchaseToken), user1, type(uint256).max); + purchaseToken.approve(address(dealAddressMultipleVestingSchedules), type(uint256).max); + + vm.expectEmit(true, false, false, true); + emit AcceptDeal(user1, 0, _purchaseAmount1, _purchaseAmount1, poolSharesAmount1, poolSharesAmount1); + AelinUpFrontDeal(dealAddressMultipleVestingSchedules).acceptDeal( + nftPurchaseList, + merkleDataEmpty, + _purchaseAmount1, + 0 + ); + assertEq(AelinUpFrontDeal(dealAddressMultipleVestingSchedules).totalPoolShares(), poolSharesAmount1); + assertEq(AelinUpFrontDeal(dealAddressMultipleVestingSchedules).poolSharesPerUser(user1, 0), poolSharesAmount1); + assertEq(AelinUpFrontDeal(dealAddressMultipleVestingSchedules).totalPurchasingAccepted(), _purchaseAmount1); + assertEq(AelinUpFrontDeal(dealAddressMultipleVestingSchedules).purchaseTokensPerUser(user1, 0), _purchaseAmount1); + + //Then second with different schedule and price + vm.expectEmit(true, false, false, true); + emit AcceptDeal(user1, 1, _purchaseAmount2, _purchaseAmount2, poolSharesAmount2, poolSharesAmount2); + AelinUpFrontDeal(dealAddressMultipleVestingSchedules).acceptDeal( + nftPurchaseList, + merkleDataEmpty, + _purchaseAmount2, + 1 + ); + assertEq( + AelinUpFrontDeal(dealAddressMultipleVestingSchedules).totalPoolShares(), + poolSharesAmount1 + poolSharesAmount2 + ); + assertEq(AelinUpFrontDeal(dealAddressMultipleVestingSchedules).poolSharesPerUser(user1, 1), poolSharesAmount2); + assertEq( + AelinUpFrontDeal(dealAddressMultipleVestingSchedules).totalPurchasingAccepted(), + _purchaseAmount1 + _purchaseAmount2 + ); + assertEq(AelinUpFrontDeal(dealAddressMultipleVestingSchedules).purchaseTokensPerUser(user1, 1), _purchaseAmount2); + + vm.stopPrank(); + } + function test_AcceptDeal_MerkleAllowance() public { AelinAllowList.InitData memory allowListInitEmpty; AelinNftGating.NftPurchaseList[] memory nftPurchaseList; @@ -1659,7 +1777,7 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal // user accepts deal uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal(merkleDealAddress).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(merkleDealAddress).getVestingScheduleDetails(0); address user = address(merkleData.account); vm.startPrank(user); deal(address(purchaseToken), user, type(uint256).max); @@ -1668,12 +1786,12 @@ contract AelinUpFrontDealPurchaseTest is Test, AelinTestUtils, IAelinUpFrontDeal uint256 poolSharesAmount = (100 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken; vm.expectEmit(true, false, false, true); - emit AcceptDeal(user, 100, 100, poolSharesAmount, poolSharesAmount); - AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 100); + emit AcceptDeal(user, 0, 100, 100, poolSharesAmount, poolSharesAmount); + AelinUpFrontDeal(merkleDealAddress).acceptDeal(nftPurchaseList, merkleData, 100, 0); assertEq(AelinUpFrontDeal(merkleDealAddress).totalPoolShares(), poolSharesAmount); - assertEq(AelinUpFrontDeal(merkleDealAddress).poolSharesPerUser(user), poolSharesAmount); + assertEq(AelinUpFrontDeal(merkleDealAddress).poolSharesPerUser(user, 0), poolSharesAmount); assertEq(AelinUpFrontDeal(merkleDealAddress).totalPurchasingAccepted(), 100); - assertEq(AelinUpFrontDeal(merkleDealAddress).purchaseTokensPerUser(user), 100); + assertEq(AelinUpFrontDeal(merkleDealAddress).purchaseTokensPerUser(user, 0), 100); vm.stopPrank(); } } diff --git a/test/tests/AelinUpFrontDealFactory.t.sol b/test/tests/AelinUpFrontDealFactory.t.sol index 620d8a49..abcd6513 100644 --- a/test/tests/AelinUpFrontDealFactory.t.sol +++ b/test/tests/AelinUpFrontDealFactory.t.sol @@ -10,7 +10,7 @@ import {AelinUpFrontDeal} from "contracts/AelinUpFrontDeal.sol"; import {AelinAllowList} from "contracts/libraries/AelinAllowList.sol"; import {AelinNftGating} from "contracts/libraries/AelinNftGating.sol"; -contract AelinUpFronDealFactoryTest is Test, AelinTestUtils { +contract AelinUpFronDealFactoryTest is Test, AelinTestUtils, IAelinUpFrontDeal { address public upfronDealAddress; address public poolAddressWith721; address public poolAddressWithAllowList; @@ -38,41 +38,6 @@ contract AelinUpFronDealFactoryTest is Test, AelinTestUtils { bool allowDeallocation; } - enum UpFrontDealVarsNftCollection { - ERC721, - ERC1155, - NONE - } - - enum NftCollectionType { - ERC1155, - ERC721 - } - - event CreateUpFrontDeal( - address indexed dealAddress, - string name, - string symbol, - address purchaseToken, - address underlyingDealToken, - address indexed holder, - address indexed sponsor, - uint256 sponsorFee, - bytes32 merkleRoot, - string ipfsHash - ); - - event CreateUpFrontDealConfig( - address indexed dealAddress, - uint256 underlyingDealTokenTotal, - uint256 purchaseTokenPerDealToken, - uint256 purchaseRaiseMinimum, - uint256 purchaseDuration, - uint256 vestingPeriod, - uint256 vestingCliffPeriod, - bool allowDeallocation - ); - function setUp() public {} /*////////////////////////////////////////////////////////////// @@ -138,14 +103,18 @@ contract AelinUpFronDealFactoryTest is Test, AelinTestUtils { uint256 _vestingCliffPeriod, bool _allowDeallocation ) public pure returns (IAelinUpFrontDeal.UpFrontDealConfig memory) { + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](1); + + vestingSchedules[0].purchaseTokenPerDealToken = _purchaseTokenPerDealToken; + vestingSchedules[0].vestingCliffPeriod = _vestingCliffPeriod; + vestingSchedules[0].vestingPeriod = _vestingPeriod; + return IAelinUpFrontDeal.UpFrontDealConfig({ underlyingDealTokenTotal: _underlyingDealTokenTotal, - purchaseTokenPerDealToken: _purchaseTokenPerDealToken, purchaseRaiseMinimum: _purchaseRaiseMinimum, purchaseDuration: _purchaseDuration, - vestingPeriod: _vestingPeriod, - vestingCliffPeriod: _vestingCliffPeriod, + vestingSchedules: vestingSchedules, allowDeallocation: _allowDeallocation }); } @@ -185,6 +154,10 @@ contract AelinUpFronDealFactoryTest is Test, AelinTestUtils { return boundedVars; } + /*////////////////////////////////////////////////////////////// + tests + //////////////////////////////////////////////////////////////*/ + function testFuzz_AelinUpFrontDealFactory_RevertWhen_AelinDealLogicAddressNull( uint256 _sponsorFee, uint256 _underlyingDealTokenTotal, @@ -403,11 +376,9 @@ contract AelinUpFronDealFactoryTest is Test, AelinTestUtils { emit CreateUpFrontDealConfig( address(0), boundedVars.underlyingDealTokenTotal, - boundedVars.purchaseTokenPerDealToken, boundedVars.purchaseRaiseMinimum, boundedVars.purchaseDuration, - boundedVars.vestingPeriod, - boundedVars.vestingCliffPeriod, + upFrontDealVars.dealConfig.vestingSchedules, _allowDeallocation ); factory.createUpFrontDeal( diff --git a/test/tests/AelinVestingToken.t.sol b/test/tests/AelinVestingToken.t.sol index 17d1a8cf..14aa0088 100644 --- a/test/tests/AelinVestingToken.t.sol +++ b/test/tests/AelinVestingToken.t.sol @@ -6,8 +6,8 @@ import {AelinTestUtils} from "../utils/AelinTestUtils.sol"; import {AelinVestingToken} from "contracts/AelinVestingToken.sol"; contract DerivedAelinVestingToken is AelinVestingToken { - function mintVestingToken(address _to, uint256 _amount, uint256 _timestamp) public { - _mintVestingToken(_to, _amount, _timestamp); + function mintVestingToken(address _to, uint256 _amount, uint256 _timestamp, uint256 _vestingIndex) public { + _mintVestingToken(_to, _amount, _timestamp, _vestingIndex); } } @@ -16,7 +16,13 @@ contract AelinVestingTokenTest is Test, AelinTestUtils { uint256 public constant MAX_UINT_SAFE = 10_000_000_000_000_000_000_000_000; - event VestingTokenMinted(address indexed user, uint256 indexed tokenId, uint256 amount, uint256 lastClaimedAt); + event VestingTokenMinted( + address indexed user, + uint256 indexed tokenId, + uint256 amount, + uint256 lastClaimedAt, + uint256 vestingIndex + ); function setUp() public { vestingToken = new DerivedAelinVestingToken(); @@ -28,7 +34,10 @@ contract AelinVestingTokenTest is Test, AelinTestUtils { uint256 _amount3, uint256 _timestamp1, uint256 _timestamp2, - uint256 _timestamp3 + uint256 _timestamp3, + uint256 _vestingIndex1, + uint256 _vestingIndex2, + uint256 _vestingIndex3 ) public { _amount1 = bound(_amount1, 1, MAX_UINT_SAFE); _amount2 = bound(_amount2, 1, MAX_UINT_SAFE); @@ -36,10 +45,13 @@ contract AelinVestingTokenTest is Test, AelinTestUtils { _timestamp1 = bound(_timestamp1, 1, MAX_UINT_SAFE); _timestamp2 = bound(_timestamp2, 1, MAX_UINT_SAFE); _timestamp3 = bound(_timestamp3, 1, MAX_UINT_SAFE); + _vestingIndex1 = bound(_vestingIndex1, 0, 9); + _vestingIndex2 = bound(_vestingIndex2, 0, 9); + _vestingIndex3 = bound(_vestingIndex3, 0, 9); - vestingToken.mintVestingToken(user1, _amount1, _timestamp1); - vestingToken.mintVestingToken(user2, _amount2, _timestamp2); - vestingToken.mintVestingToken(user3, _amount3, _timestamp3); + vestingToken.mintVestingToken(user1, _amount1, _timestamp1, _vestingIndex1); + vestingToken.mintVestingToken(user2, _amount2, _timestamp2, _vestingIndex2); + vestingToken.mintVestingToken(user3, _amount3, _timestamp3, _vestingIndex3); } function testFuzz_TransferVestingShare_RevertWhen_NotOwner( @@ -49,11 +61,24 @@ contract AelinVestingTokenTest is Test, AelinTestUtils { uint256 _timestamp1, uint256 _timestamp2, uint256 _timestamp3, + uint256 _vestingIndex1, + uint256 _vestingIndex2, + uint256 _vestingIndex3, address _to, uint256 _tokenId, uint256 _shareAmount ) public { - mintTokens(_amount1, _amount2, _amount3, _timestamp1, _timestamp2, _timestamp3); + mintTokens( + _amount1, + _amount2, + _amount3, + _timestamp1, + _timestamp2, + _timestamp3, + _vestingIndex1, + _vestingIndex2, + _vestingIndex3 + ); vm.assume(_tokenId == 0 || _tokenId == 1 || _tokenId == 2); vm.assume(user1 != vestingToken.ownerOf(_tokenId)); @@ -70,10 +95,23 @@ contract AelinVestingTokenTest is Test, AelinTestUtils { uint256 _timestamp1, uint256 _timestamp2, uint256 _timestamp3, + uint256 _vestingIndex1, + uint256 _vestingIndex2, + uint256 _vestingIndex3, address _to, uint256 _tokenId ) public { - mintTokens(_amount1, _amount2, _amount3, _timestamp1, _timestamp2, _timestamp3); + mintTokens( + _amount1, + _amount2, + _amount3, + _timestamp1, + _timestamp2, + _timestamp3, + _vestingIndex1, + _vestingIndex2, + _vestingIndex3 + ); vm.assume(_tokenId == 0 || _tokenId == 1 || _tokenId == 2); vm.startPrank(vestingToken.ownerOf(_tokenId)); @@ -89,14 +127,27 @@ contract AelinVestingTokenTest is Test, AelinTestUtils { uint256 _timestamp1, uint256 _timestamp2, uint256 _timestamp3, + uint256 _vestingIndex1, + uint256 _vestingIndex2, + uint256 _vestingIndex3, address _to, uint256 _tokenId, uint256 _shareAmount ) public { - mintTokens(_amount1, _amount2, _amount3, _timestamp1, _timestamp2, _timestamp3); + mintTokens( + _amount1, + _amount2, + _amount3, + _timestamp1, + _timestamp2, + _timestamp3, + _vestingIndex1, + _vestingIndex2, + _vestingIndex3 + ); vm.assume(_tokenId == 0 || _tokenId == 1 || _tokenId == 2); - (uint256 share, ) = vestingToken.vestingDetails(_tokenId); + (uint256 share, , ) = vestingToken.vestingDetails(_tokenId); vm.assume(_shareAmount > share); @@ -113,30 +164,44 @@ contract AelinVestingTokenTest is Test, AelinTestUtils { uint256 _timestamp1, uint256 _timestamp2, uint256 _timestamp3, + uint256 _vestingIndex1, + uint256 _vestingIndex2, + uint256 _vestingIndex3, address _to, uint256 _tokenId, uint256 _shareAmount ) public { - mintTokens(_amount1, _amount2, _amount3, _timestamp1, _timestamp2, _timestamp3); + mintTokens( + _amount1, + _amount2, + _amount3, + _timestamp1, + _timestamp2, + _timestamp3, + _vestingIndex1, + _vestingIndex2, + _vestingIndex3 + ); vm.assume(_tokenId == 0 || _tokenId == 1 || _tokenId == 2); vm.assume(_to != address(0)); uint256 prevTokenCount = vestingToken.tokenCount(); - (uint256 share, uint256 lastClaimedAt) = vestingToken.vestingDetails(_tokenId); + (uint256 share, uint256 lastClaimedAt, uint256 vestingIndex) = vestingToken.vestingDetails(_tokenId); vm.assume(_shareAmount > 0 && _shareAmount < share); vm.startPrank(vestingToken.ownerOf(_tokenId)); vm.expectEmit(true, true, true, true, address(vestingToken)); - emit VestingTokenMinted(_to, prevTokenCount, _shareAmount, lastClaimedAt); + emit VestingTokenMinted(_to, prevTokenCount, _shareAmount, lastClaimedAt, vestingIndex); vestingToken.transferVestingShare(_to, _tokenId, _shareAmount); vm.stopPrank(); - (uint newShare, uint256 newLastClaimedAt) = vestingToken.vestingDetails(prevTokenCount); + (uint newShare, uint256 newLastClaimedAt, uint256 newVestingIndex) = vestingToken.vestingDetails(prevTokenCount); assertEq(vestingToken.ownerOf(prevTokenCount), _to, "ownership transfered correctly"); assertEq(vestingToken.tokenCount(), prevTokenCount + 1, "new vesting token minted"); assertEq(newShare, _shareAmount, "share amount is correct"); assertEq(lastClaimedAt, newLastClaimedAt, "last claimed at is correct"); + assertEq(vestingIndex, newVestingIndex, "vesting index is correct"); } } diff --git a/test/utils/AelinTestUtils.sol b/test/utils/AelinTestUtils.sol index c7365edc..00f0c441 100644 --- a/test/utils/AelinTestUtils.sol +++ b/test/utils/AelinTestUtils.sol @@ -102,14 +102,18 @@ contract AelinTestUtils is Test { uint256 _vestingCliffPeriod, bool _allowDeallocation ) public pure returns (IAelinUpFrontDeal.UpFrontDealConfig memory) { + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](1); + + vestingSchedules[0].purchaseTokenPerDealToken = _purchaseTokenPerDealToken; + vestingSchedules[0].vestingCliffPeriod = _vestingCliffPeriod; + vestingSchedules[0].vestingPeriod = _vestingPeriod; + return IAelinUpFrontDeal.UpFrontDealConfig({ underlyingDealTokenTotal: _underlyingDealTokenTotal, - purchaseTokenPerDealToken: _purchaseTokenPerDealToken, purchaseRaiseMinimum: _purchaseRaiseMinimum, purchaseDuration: _purchaseDuration, - vestingPeriod: _vestingPeriod, - vestingCliffPeriod: _vestingCliffPeriod, + vestingSchedules: vestingSchedules, allowDeallocation: _allowDeallocation }); } @@ -203,28 +207,52 @@ contract AelinTestUtils is Test { }); } + function getDealConfigMultipleVestingSchedules() public pure returns (IAelinUpFrontDeal.UpFrontDealConfig memory) { + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](10); + + for (uint256 i; i < 10; i++) { + vestingSchedules[i].purchaseTokenPerDealToken = 3e18 + (1e18 * i); + vestingSchedules[i].vestingCliffPeriod = 60 days + (10 days * i); + vestingSchedules[i].vestingPeriod = 365 days + (10 days * i); + } + + IAelinUpFrontDeal.UpFrontDealConfig memory dealConfigMultipleVestingSchedules = getDealConfig(); + + dealConfigMultipleVestingSchedules.vestingSchedules = vestingSchedules; + + return dealConfigMultipleVestingSchedules; + } + function getDealConfigAllowDeallocation() public pure returns (IAelinUpFrontDeal.UpFrontDealConfig memory) { + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](1); + + vestingSchedules[0].purchaseTokenPerDealToken = 3e18; + vestingSchedules[0].vestingCliffPeriod = 60 days; + vestingSchedules[0].vestingPeriod = 365 days; + return IAelinUpFrontDeal.UpFrontDealConfig({ underlyingDealTokenTotal: 1e35, - purchaseTokenPerDealToken: 3e18, purchaseRaiseMinimum: 0, purchaseDuration: 10 days, - vestingPeriod: 365 days, - vestingCliffPeriod: 60 days, + vestingSchedules: vestingSchedules, allowDeallocation: true }); } function getDealConfig() public pure returns (IAelinUpFrontDeal.UpFrontDealConfig memory) { + IAelinUpFrontDeal.VestingSchedule[] memory vestingSchedules = new IAelinUpFrontDeal.VestingSchedule[](1); + + vestingSchedules[0].purchaseTokenPerDealToken = 3e18; + vestingSchedules[0].vestingCliffPeriod = 60 days; + vestingSchedules[0].vestingPeriod = 365 days; + return IAelinUpFrontDeal.UpFrontDealConfig({ underlyingDealTokenTotal: 1e35, - purchaseTokenPerDealToken: 3e18, purchaseRaiseMinimum: 1e28, purchaseDuration: 10 days, - vestingPeriod: 365 days, - vestingCliffPeriod: 60 days, + vestingSchedules: vestingSchedules, allowDeallocation: false }); } @@ -298,15 +326,8 @@ contract AelinTestUtils is Test { function setupAndAcceptDealNoDeallocation(address _dealAddress, uint256 _purchaseAmount, address _user) public { uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - ( - uint256 underlyingDealTokenTotal, - uint256 purchaseTokenPerDealToken, - uint256 purchaseRaiseMinimum, - , - , - , - - ) = AelinUpFrontDeal(_dealAddress).dealConfig(); + (uint256 underlyingDealTokenTotal, uint256 purchaseRaiseMinimum, , ) = AelinUpFrontDeal(_dealAddress).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(_dealAddress).getVestingScheduleDetails(0); vm.assume(_purchaseAmount > purchaseRaiseMinimum); (bool success, ) = SafeMath.tryMul(_purchaseAmount, 10 ** underlyingTokenDecimals); vm.assume(success); @@ -318,7 +339,32 @@ contract AelinTestUtils is Test { deal(address(purchaseToken), _user, type(uint256).max); purchaseToken.approve(_dealAddress, type(uint256).max); AelinNftGating.NftPurchaseList[] memory nftPurchaseList; - AelinUpFrontDeal(_dealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(_dealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); + } + + function setupAndAcceptDealWithMultipleVesting(address _dealAddress, uint256 _purchaseAmount1, address _user) public { + uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); + (uint256 underlyingDealTokenTotal, uint256 purchaseRaiseMinimum, , ) = AelinUpFrontDeal(_dealAddress).dealConfig(); + (uint256 purchaseTokenPerDealToken1, , ) = AelinUpFrontDeal(_dealAddress).getVestingScheduleDetails(0); + (uint256 purchaseTokenPerDealToken2, , ) = AelinUpFrontDeal(_dealAddress).getVestingScheduleDetails(0); + vm.assume(_purchaseAmount1 > purchaseRaiseMinimum); + + bool success = false; + (success, ) = SafeMath.tryMul(_purchaseAmount1, 10 ** underlyingTokenDecimals); + vm.assume(success); + + uint256 poolSharesAmount1 = (_purchaseAmount1 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken1; + uint256 poolSharesAmount2 = (_purchaseAmount1 * 10 ** underlyingTokenDecimals) / purchaseTokenPerDealToken2; + vm.assume(poolSharesAmount1 > 0); + vm.assume(poolSharesAmount2 > 0); + vm.assume(poolSharesAmount1 + poolSharesAmount2 < underlyingDealTokenTotal); + + // user accepts the deal twice with different vesting schedules + deal(address(purchaseToken), _user, type(uint256).max); + purchaseToken.approve(_dealAddress, type(uint256).max); + AelinNftGating.NftPurchaseList[] memory nftPurchaseList; + AelinUpFrontDeal(_dealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount1, 0); + AelinUpFrontDeal(_dealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount1, 1); } function setupAndAcceptDealWithDeallocation( @@ -328,8 +374,8 @@ contract AelinTestUtils is Test { bool isOverSubscribed ) public returns (uint256) { uint8 underlyingTokenDecimals = underlyingDealToken.decimals(); - (uint256 underlyingDealTokenTotal, uint256 purchaseTokenPerDealToken, , , , , ) = AelinUpFrontDeal(_dealAddress) - .dealConfig(); + (uint256 underlyingDealTokenTotal, , , ) = AelinUpFrontDeal(_dealAddress).dealConfig(); + (uint256 purchaseTokenPerDealToken, , ) = AelinUpFrontDeal(_dealAddress).getVestingScheduleDetails(0); (bool success, ) = SafeMath.tryMul(_purchaseAmount, 10 ** underlyingTokenDecimals); vm.assume(success); (success, ) = SafeMath.tryMul(_purchaseAmount, underlyingDealTokenTotal); @@ -346,7 +392,7 @@ contract AelinTestUtils is Test { deal(address(purchaseToken), _user, type(uint256).max); purchaseToken.approve(address(_dealAddress), type(uint256).max); AelinNftGating.NftPurchaseList[] memory nftPurchaseList; - AelinUpFrontDeal(_dealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount); + AelinUpFrontDeal(_dealAddress).acceptDeal(nftPurchaseList, merkleDataEmpty, _purchaseAmount, 0); return (underlyingDealTokenTotal); } @@ -354,7 +400,7 @@ contract AelinTestUtils is Test { // purchase period is now over reachPurchaseExpiry(_dealAddress); // purchaser claim - AelinUpFrontDeal(_dealAddress).purchaserClaim(); + AelinUpFrontDeal(_dealAddress).purchaserClaim(0); } function reachPurchaseExpiry(address _dealAddress) public { @@ -362,11 +408,11 @@ contract AelinTestUtils is Test { } function reachVestingCliffExpiry(address _dealAddress) public { - vm.warp(AelinUpFrontDeal(_dealAddress).vestingCliffExpiry() + 1 days); + vm.warp(AelinUpFrontDeal(_dealAddress).vestingCliffExpiries(0) + 1 days); } function reachVestingPeriod(address _dealAddress) public { - vm.warp(AelinUpFrontDeal(_dealAddress).vestingCliffExpiry() + 1 days); + vm.warp(AelinUpFrontDeal(_dealAddress).vestingCliffExpiries(0) + 1 days); } function makeAddr(uint256 num) internal returns (address addr) {