diff --git a/contracts/Foundry.sol b/contracts/Foundry.sol index a3b5cadc..8446732c 100644 --- a/contracts/Foundry.sol +++ b/contracts/Foundry.sol @@ -70,7 +70,6 @@ contract Foundry is IFoundry, Ownable, Initializable { if (block.timestamp > meToken_.endTime) { hub_ = hub.getDetails(meToken_.targetHubId); meToken_ = meTokenRegistry.finishResubscribe(_meToken); - hub_ = hub.getDetails(meToken_.hubId); } else if (block.timestamp > meToken_.startTime) { // Handle migration actions if needed IMigration(meToken_.migration).poke(_meToken); @@ -81,7 +80,7 @@ contract Foundry is IFoundry, Ownable, Initializable { uint256 fee = (_assetsDeposited * fees.mintFee()) / PRECISION; uint256 assetsDepositedAfterFees = _assetsDeposited - fee; - uint256 meTokensMinted = calculateMeTokensMinted( + uint256 meTokensMinted = _calculateMeTokensMinted( _meToken, assetsDepositedAfterFees ); @@ -102,7 +101,7 @@ contract Foundry is IFoundry, Ownable, Initializable { asset = targetHub_.asset; } - vault.handleDeposit(asset, _assetsDeposited, fee, msg.sender); + vault.handleDeposit(msg.sender, asset, _assetsDeposited, fee); meTokenRegistry.updateBalancePooled( true, @@ -169,14 +168,13 @@ contract Foundry is IFoundry, Ownable, Initializable { ) { hub_ = hub.getDetails(meToken_.targetHubId); meToken_ = meTokenRegistry.finishResubscribe(_meToken); - hub_ = hub.getDetails(meToken_.hubId); } // Calculate how many tokens are returned - uint256 rawAssetsReturned = calculateRawAssetsReturned( + uint256 rawAssetsReturned = _calculateRawAssetsReturned( _meToken, _meTokensBurned ); - uint256 assetsReturned = calculateActualAssetsReturned( + uint256 assetsReturned = _calculateActualAssetsReturned( msg.sender, _meToken, _meTokensBurned, @@ -215,7 +213,8 @@ contract Foundry is IFoundry, Ownable, Initializable { ); } - uint256 fee = assetsReturned * feeRate; + uint256 fee = (assetsReturned * feeRate) / PRECISION; + assetsReturned = assetsReturned - fee; IVault vault = IVault(hub_.vault); address asset = hub_.asset; @@ -230,7 +229,7 @@ contract Foundry is IFoundry, Ownable, Initializable { asset = targetHub_.asset; } - vault.handleWithdrawal(asset, assetsReturned, fee, _recipient); + vault.handleWithdrawal(_recipient, asset, assetsReturned, fee); emit Burn( _meToken, @@ -238,33 +237,15 @@ contract Foundry is IFoundry, Ownable, Initializable { msg.sender, _recipient, _meTokensBurned, - assetsReturned - fee - ); - } - - function calculateAssetsReturned( - address _sender, - address _meToken, - uint256 _meTokensBurned - ) external view returns (uint256 assetsReturned) { - uint256 rawAssetsReturned = calculateRawAssetsReturned( - _meToken, - _meTokensBurned - ); - assetsReturned = calculateActualAssetsReturned( - _sender, - _meToken, - _meTokensBurned, - rawAssetsReturned + assetsReturned ); } // NOTE: for now this does not include fees - function calculateMeTokensMinted(address _meToken, uint256 _assetsDeposited) - public - view - returns (uint256 meTokensMinted) - { + function _calculateMeTokensMinted( + address _meToken, + uint256 _assetsDeposited + ) private view returns (uint256 meTokensMinted) { Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); // gas savings @@ -326,10 +307,10 @@ contract Foundry is IFoundry, Ownable, Initializable { } } - function calculateRawAssetsReturned( + function _calculateRawAssetsReturned( address _meToken, uint256 _meTokensBurned - ) public view returns (uint256 rawAssetsReturned) { + ) private view returns (uint256 rawAssetsReturned) { Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); @@ -397,12 +378,12 @@ contract Foundry is IFoundry, Ownable, Initializable { } /// @dev applies refundRatio - function calculateActualAssetsReturned( + function _calculateActualAssetsReturned( address _sender, address _meToken, uint256 _meTokensBurned, uint256 rawAssetsReturned - ) public view returns (uint256 actualAssetsReturned) { + ) private view returns (uint256 actualAssetsReturned) { Details.MeToken memory meToken_ = meTokenRegistry.getDetails(_meToken); Details.Hub memory hub_ = hub.getDetails(meToken_.hubId); // If msg.sender == owner, give owner the sell rate. - all of tokens returned plus a % diff --git a/contracts/Hub.sol b/contracts/Hub.sol index 9af2a1af..1ad28ec3 100644 --- a/contracts/Hub.sol +++ b/contracts/Hub.sol @@ -207,17 +207,17 @@ contract Hub is IHub, Ownable, Initializable { } /// @inheritdoc IHub - function getWarmup() external view override returns (uint256) { + function warmup() external view override returns (uint256) { return _warmup; } /// @inheritdoc IHub - function getDuration() external view override returns (uint256) { + function duration() external view override returns (uint256) { return _duration; } /// @inheritdoc IHub - function getCooldown() external view override returns (uint256) { + function cooldown() external view override returns (uint256) { return _cooldown; } diff --git a/contracts/interfaces/ICurve.sol b/contracts/interfaces/ICurve.sol index 781035cc..24e779c0 100644 --- a/contracts/interfaces/ICurve.sol +++ b/contracts/interfaces/ICurve.sol @@ -1,31 +1,44 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; -/// @title Curve Interface +/// @title Generic Curve interface /// @author Carl Farterson (@carlfarterson) /// @dev Required for all Curves interface ICurve { - event Updated(uint256 indexed hubId); + /// @notice Event when curveDetails are updated from target values to actual values + event Updated(uint256 indexed _hubId); /// @notice Given a hub, baseX, baseY and connector weight, add the configuration to the /// BancorZero Curve registry /// @dev Curve need to be encoded as the Hub may register Curves for different curves /// that may contain different Curve arguments - /// @param _hubId unique hub identifier - /// @param _encodedDetails encoded Curve arguments + /// @param _hubId unique hub identifier + /// @param _encodedDetails encoded Curve arguments function register(uint256 _hubId, bytes calldata _encodedDetails) external; - /// @notice TODO - /// @param _hubId unique hub identifier - /// @param _encodedDetails encoded target Curve arguments + /// @notice Initialize reconfiguring curveDetails for a hub + /// @param _hubId unique hub identifier + /// @param _encodedDetails encoded target Curve arguments function initReconfigure(uint256 _hubId, bytes calldata _encodedDetails) external; + /// @notice Finish reconfiguring curveDetails for a hub + /// @param _hubId uinque hub identifier + function finishReconfigure(uint256 _hubId) external; + + /// @notice Get curveDetails for a hub + /// @return curveDetails (TODO: curve w/ more than 4 curveDetails) function getDetails(uint256 _hubId) external view returns (uint256[4] memory); + /// @notice Calculate meTokens minted based on a curve's active details + /// @param _assetsDeposited Amount of assets deposited to the hub + /// @param _hubId unique hub identifier + /// @param _supply current meToken supply + /// @param _balancePooled area under curve + /// @return meTokensMinted amount of MeTokens minted function viewMeTokensMinted( uint256 _assetsDeposited, uint256 _hubId, @@ -33,6 +46,12 @@ interface ICurve { uint256 _balancePooled ) external view returns (uint256 meTokensMinted); + /// @notice Calculate assets returned based on a curve's active details + /// @param _meTokensBurned Amount of assets deposited to the hub + /// @param _hubId unique hub identifier + /// @param _supply current meToken supply + /// @param _balancePooled area under curve + /// @return assetsReturned amount of assets returned function viewAssetsReturned( uint256 _meTokensBurned, uint256 _hubId, @@ -40,6 +59,12 @@ interface ICurve { uint256 _balancePooled ) external view returns (uint256 assetsReturned); + /// @notice Calculate meTokens minted based on a curve's target details + /// @param _assetsDeposited Amount of assets deposited to the hub + /// @param _hubId unique hub identifier + /// @param _supply current meToken supply + /// @param _balancePooled area under curve + /// @return meTokensMinted amount of MeTokens minted function viewTargetMeTokensMinted( uint256 _assetsDeposited, uint256 _hubId, @@ -47,12 +72,16 @@ interface ICurve { uint256 _balancePooled ) external view returns (uint256 meTokensMinted); + /// @notice Calculate assets returned based on a curve's target details + /// @param _meTokensBurned Amount of assets deposited to the hub + /// @param _hubId unique hub identifier + /// @param _supply current meToken supply + /// @param _balancePooled area under curve + /// @return assetsReturned amount of assets returned function viewTargetAssetsReturned( uint256 _meTokensBurned, uint256 _hubId, uint256 _supply, uint256 _balancePooled ) external view returns (uint256 assetsReturned); - - function finishReconfigure(uint256 id) external; } diff --git a/contracts/interfaces/IFees.sol b/contracts/interfaces/IFees.sol index 80bd9429..0e579ef8 100644 --- a/contracts/interfaces/IFees.sol +++ b/contracts/interfaces/IFees.sol @@ -1,28 +1,50 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +/// @title MeTokens protocol fee interface +/// @author Carl Farterson (@carlfarterson) interface IFees { - function setBurnBuyerFee(uint256 amount) external; + /// @notice Set meToken protocol BurnBuyer fee + /// @param _fee new fee + function setBurnBuyerFee(uint256 _fee) external; - function setBurnOwnerFee(uint256 amount) external; + /// @notice Set meToken protocol BurnOwner fee + /// @param _fee new fee + function setBurnOwnerFee(uint256 _fee) external; - function setTransferFee(uint256 amount) external; + /// @notice Set meToken protocol Transfer fee + /// @param _fee new fee + function setTransferFee(uint256 _fee) external; - function setInterestFee(uint256 amount) external; + /// @notice Set meToken protocol Interest fee + /// @param _fee new fee + function setInterestFee(uint256 _fee) external; - function setYieldFee(uint256 amount) external; - - function setOwner(address _owner) external; + /// @notice Set meToken protocol Yield fee + /// @param _fee new fee + function setYieldFee(uint256 _fee) external; + /// @notice Get mint fee + /// @return uint256 _mintFee function mintFee() external view returns (uint256); + /// @notice Get burnBuyer fee + /// @return uint256 _burnBuyerFee function burnBuyerFee() external view returns (uint256); + /// @notice Get burnOwner fee + /// @return uint256 _burnOwnerFee function burnOwnerFee() external view returns (uint256); + /// @notice Get transfer fee + /// @return uint256 _transferFee function transferFee() external view returns (uint256); + /// @notice Get interest fee + /// @return uint256 _interestFee function interestFee() external view returns (uint256); + /// @notice Get yield fee + /// @return uint256 _yieldFee function yieldFee() external view returns (uint256); } diff --git a/contracts/interfaces/IFoundry.sol b/contracts/interfaces/IFoundry.sol index b808356a..6455ccc7 100644 --- a/contracts/interfaces/IFoundry.sol +++ b/contracts/interfaces/IFoundry.sol @@ -1,31 +1,55 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +/// @title MeTokens foundry interface +/// @author Carl Farterson (@carlfarterson) interface IFoundry { + /// @notice Event of minting a meToken + /// @param _meToken address of meToken minted + /// @param _asset address of asset deposited + /// @param _depositor address to deposit asset + /// @param _recipient address to receive minted meTokens + /// @param _assetsDeposited amount of assets deposited + /// @param _meTokensMinted amount of meTokens minted event Mint( - address meToken, - address token, - address depositor, - address recipient, - uint256 assetsDeposited, - uint256 meTokensMinted + address _meToken, + address _asset, + address _depositor, + address _recipient, + uint256 _assetsDeposited, + uint256 _meTokensMinted ); + /// @notice Event of burning a meToken + /// @param _meToken address of meToken burned + /// @param _asset address of asset returned + /// @param _burner address to burn meTokens + /// @param _recipient address to receive underlying asset + /// @param _meTokensBurned amount of meTokens to burn + /// @param _assetsReturned amount of assets event Burn( - address meToken, - address token, - address burner, - address recipient, - uint256 meTokensBurned, - uint256 assetsReturned + address _meToken, + address _asset, + address _burner, + address _recipient, + uint256 _meTokensBurned, + uint256 _assetsReturned ); + /// @notice Mint a meToken by depositing the underlying asset + /// @param _meToken address of meToken to mint + /// @param _assetsDeposited amount of assets to deposit + /// @param _recipient address to receive minted meTokens function mint( address _meToken, uint256 _assetsDeposited, address _recipient ) external; + /// @notice Burn a meToken to receive the underlying asset + /// @param _meToken address of meToken to burn + /// @param _meTokensBurned amount of meTokens to burn + /// @param _recipient address to receive the underlying assets function burn( address _meToken, uint256 _meTokensBurned, diff --git a/contracts/interfaces/IHub.sol b/contracts/interfaces/IHub.sol index 9995c704..5561679d 100644 --- a/contracts/interfaces/IHub.sol +++ b/contracts/interfaces/IHub.sol @@ -1,11 +1,21 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; -import "../libs/Details.sol"; import "./IVault.sol"; import "./ICurve.sol"; +import "../libs/Details.sol"; +/// @title MeTokens hub interface +/// @author Carl Farterson (@carlfarterson) interface IHub { + /// @notice Event of registering a hub + /// @param _owner address to own hub + /// @param _asset address of underlying asset + /// @param _vault address of vault + /// @param _curve address of curve + /// @param _refundRatio rate to refund burners + /// @param _encodedCurveDetails additional encoded curve details + /// @param _encodedVaultArgs additional encoded vault arguments event Register( address _owner, address _asset, @@ -15,21 +25,49 @@ interface IHub { bytes _encodedCurveDetails, bytes _encodedVaultArgs ); + + /// @notice Event of initializing a hub update + /// @notice _id unique hub identifier + /// @notice _targetCurve address of target curve + /// @notice _targetRefundRatio target rate to refund burners + /// @notice _encodedCurveDetails additional encoded curve details + /// @notice _reconfigure boolean to show if we're changing the + /// curveDetails but not the curve address + /// @notice _startTime timestamp to start updating + /// @notice _endTime timestamp to end updating + /// @notice _endCooldown timestamp to allow another update event InitUpdate( uint256 _id, address _targetCurve, uint256 _targetRefundRatio, bytes _encodedCurveDetails, - bool reconfigure, - uint256 startTime, - uint256 endTime, - uint256 endCooldown + bool _reconfigure, + uint256 _startTime, + uint256 _endTime, + uint256 _endCooldown ); + + /// @notice Event of canceling a hub update + /// @param _id unique hub identifier event CancelUpdate(uint256 _id); + /// @notice Event of transfering hub ownership + /// @param _id unique hub identifier + /// @param _newOwner address to own the hub event TransferHubOwnership(uint256 _id, address _newOwner); + + /// @notice Event of finishing a hub update + /// @param _id unique hub identifier event FinishUpdate(uint256 _id); + /// @notice Register a new hub + /// @param _owner address to own hub + /// @param _asset address of vault asset + /// @param _vault address of vault + /// @param _curve address of curve + /// @param _refundRatio rate to refund burners + /// @param _encodedCurveDetails additional encoded curve details + /// @param _encodedVaultArgs additional encoded vault arguments function register( address _owner, address _asset, @@ -40,6 +78,11 @@ interface IHub { bytes memory _encodedVaultArgs ) external; + /// @notice Intialize a hub update + /// @param _id unique hub identifier + /// @param _targetCurve address of target curve + /// @param _targetRefundRatio target rate to refund burners + /// @param _encodedCurveDetails additional encoded curve details function initUpdate( uint256 _id, address _targetCurve, @@ -47,34 +90,34 @@ interface IHub { bytes memory _encodedCurveDetails ) external; + /// @notice Cancel a hub update + /// @dev Can only be called before _startTime + /// @param _id unique hub identifier function cancelUpdate(uint256 _id) external; - /// @notice Function to end the update, setting the target values of the hub, - /// as well as modifying a hubs' status to ACTIVE - /// @param id Unique hub identifier - function finishUpdate(uint256 id) external returns (Details.Hub memory); - - /// @notice TODO - /// @param id Unique hub identifier - /// @return hub_ Details of hub - function getDetails(uint256 id) - external - view - returns (Details.Hub memory hub_); - - /// @notice TODO - /// @return count of hubs created + /// @notice Finish updating a hub + /// @param _id unique hub identifier + /// @return details of hub + function finishUpdate(uint256 _id) external returns (Details.Hub memory); + + /// @notice Get the details of a hub + /// @param _id unique hub identifier + /// @return details of hub + function getDetails(uint256 _id) external view returns (Details.Hub memory); + + /// @notice Counter of hubs registered + /// @return uint256 function count() external view returns (uint256); - function getWarmup() external view returns (uint256); + function warmup() external view returns (uint256); function setWarmup(uint256 warmup_) external; - function getDuration() external view returns (uint256); + function duration() external view returns (uint256); function setDuration(uint256 duration_) external; - function getCooldown() external view returns (uint256); + function cooldown() external view returns (uint256); function setCooldown(uint256 cooldown_) external; } diff --git a/contracts/interfaces/IMeToken.sol b/contracts/interfaces/IMeToken.sol index ef53cb3f..6eab8b96 100644 --- a/contracts/interfaces/IMeToken.sol +++ b/contracts/interfaces/IMeToken.sol @@ -1,7 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +/// @title meToken interface +/// @author Carl Farterson (@carlfarterson) +/// @dev Required for all meTokens interface IMeToken { + // TODO: are these needed, or can we do IERC20? function initialize( string calldata name, address owner, @@ -11,8 +15,4 @@ interface IMeToken { function mint(address to, uint256 amount) external; function burn(address from, uint256 amount) external; - - function canMigrate() external view returns (bool); - - function switchUpdating() external returns (bool); } diff --git a/contracts/interfaces/IMeTokenFactory.sol b/contracts/interfaces/IMeTokenFactory.sol index 103d0ccb..9e97c586 100644 --- a/contracts/interfaces/IMeTokenFactory.sol +++ b/contracts/interfaces/IMeTokenFactory.sol @@ -1,11 +1,18 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +/// @title meToken factory interface +/// @author Carl Farterson (@carlfarterson) interface IMeTokenFactory { + /// @notice Create a meToken + /// @param _name name of meToken + /// @param _symbol symbol of meToken + /// @param _foundry address of foundry + /// @param _meTokenRegistry address of meTokenRegistry function create( - string calldata name, - string calldata symbol, - address foundry, - address meTokenRegistry + string calldata _name, + string calldata _symbol, + address _foundry, + address _meTokenRegistry ) external returns (address); } diff --git a/contracts/interfaces/IMeTokenRegistry.sol b/contracts/interfaces/IMeTokenRegistry.sol index 2726ce5b..6130f3c7 100644 --- a/contracts/interfaces/IMeTokenRegistry.sol +++ b/contracts/interfaces/IMeTokenRegistry.sol @@ -3,42 +3,91 @@ pragma solidity ^0.8.0; import "../libs/Details.sol"; +/// @title meToken registry interface +/// @author Carl Farterson (@carlfarterson) interface IMeTokenRegistry { + /// @notice Event of subscribing (creating) a new meToken + /// @param _meToken address of created meToken + /// @param _owner address of meToken owner + /// @param _minted amount of meToken minted to owner + /// @param _asset address of underlying asset + /// @param _assetsDeposited amount of assets deposited + /// @param _name name of meToken + /// @param _symbol symbol of meToken + /// @param _hubId unique hub identifier event Subscribe( address indexed _meToken, address indexed _owner, uint256 _minted, - address _collateralToken, - uint256 _collateralDeposited, + address _asset, + uint256 _assetsDeposited, string _name, string _symbol, uint256 _hubId ); + + /// @notice Event of initializing a meToken subscription to a different hub + /// @param _meToken address of meToken + /// @param _targetHubId target hub to suscribe to + /// @param _migration address of migration vault + /// @param _encodedMigrationArgs additional encoded migration vault arguments event InitResubscribe( address indexed _meToken, uint256 _targetHubId, address _migration, bytes _encodedMigrationArgs ); + /// @notice Event of canceling a meToken resubscription + /// @param _meToken address of meToken event CancelResubscribe(address indexed _meToken); + + /// @notice Event of finishing a meToken resubscription + /// @param _meToken address of meToken event FinishResubscribe(address indexed _meToken); + + /// @notice Event of updating a meToken's balancePooled and balanceLocked + /// @param _meToken address of meToken + /// @param _newBalance rate to multiply balances by event UpdateBalances(address _meToken, uint256 _newBalance); + + /// @notice Event of transfering meToken ownership to a new owner + /// @param _from address of current meToken owner + /// @param _to address to own the meToken + /// @param _meToken address of meToken event TransferMeTokenOwnership( address _from, address _to, address _meToken ); + + /// @notice Event of cancelling the transfer of meToken ownership + /// @param _from address of current meToken owner + /// @param _meToken address of meToken event CancelTransferMeTokenOwnership(address _from, address _meToken); - event ClaimMeTokenOwnership(address _from, address _to, address _meToken); - event UpdateBalancePooled(bool add, address _meToken, uint256 _amount); - event UpdateBalanceLocked(bool add, address _meToken, uint256 _amount); + /// @notice Event of claiming the transfer of meToken ownership + /// @param _from address of current meToken owner + /// @param _to address to own the meToken + /// @param _meToken address of meToken + event ClaimMeTokenOwnership(address _from, address _to, address _meToken); - /// @notice TODO - /// @param _name TODO - /// @param _symbol TODO - /// @param _hubId TODO - /// @param _assetsDeposited TODO + /// @notice Event of updating a meToken's balancePooled + /// @param _add boolean that is true if adding to balance, false if subtracting + /// @param _meToken address of meToken + /// @param _amount amount to add/subtract + event UpdateBalancePooled(bool _add, address _meToken, uint256 _amount); + + /// @notice Event of updating a meToken's balanceLocked + /// @param _add boolean that is true if adding to balance, false if subtracting + /// @param _meToken address of meToken + /// @param _amount amount to add/subtract + event UpdateBalanceLocked(bool _add, address _meToken, uint256 _amount); + + /// @notice Create and subscribe a meToken to a hub + /// @param _name name of meToken + /// @param _symbol symbol of meToken + /// @param _hubId initial hub to subscribe to + /// @param _assetsDeposited amount of assets deposited at meToken initialization function subscribe( string calldata _name, string calldata _symbol, @@ -46,11 +95,11 @@ interface IMeTokenRegistry { uint256 _assetsDeposited ) external; - /// @notice TODO - /// @param _meToken TODO - /// @param _targetHubId TODO - /// @param _migration TODO - /// @param _encodedMigrationArgs TODO + /// @notice Initialize a meToken resubscription to a new hub + /// @param _meToken address of meToken + /// @param _targetHubId hub which meToken is resubscribing to + /// @param _migration address of migration vault + /// @param _encodedMigrationArgs additional encoded migration vault arguments function initResubscribe( address _meToken, uint256 _targetHubId, @@ -58,73 +107,74 @@ interface IMeTokenRegistry { bytes memory _encodedMigrationArgs ) external; - /// @notice TODO - /// @param _meToken TODO + /// @notice Cancel a meToken resubscription + /// @dev can only be done during the warmup period + /// @param _meToken address of meToken function cancelResubscribe(address _meToken) external; - /// @notice TODO - /// @param _meToken TODO - /// @return TODO + /// @notice Finish a meToken's resubscription to a new hub + /// @param _meToken address of meToken + /// @return details of meToken function finishResubscribe(address _meToken) external returns (Details.MeToken memory); - /// @notice TODO - /// @param _meToken TODO - /// @param _newBalance TODO + /// @notice Update a meToken's balanceLocked and balancePooled + /// @param _meToken address of meToken + /// @param _newBalance rate to multiply balances by function updateBalances(address _meToken, uint256 _newBalance) external; - /// @notice TODO - /// @param add TODO - /// @param _meToken TODO - /// @param _amount TODO + /// @notice Update a meToken's balancePooled + /// @param _add boolean that is true if adding to balance, false if subtracting + /// @param _meToken address of meToken + /// @param _amount amount to add/subtract function updateBalancePooled( - bool add, + bool _add, address _meToken, uint256 _amount ) external; - /// @notice TODO - /// @param add TODO - /// @param _meToken TODO - /// @param _amount TODO + /// @notice Update a meToken's balanceLocked + /// @param _add boolean that is true if adding to balance, false if subtracting + /// @param _meToken address of meToken + /// @param _amount amount to add/subtract function updateBalanceLocked( - bool add, + bool _add, address _meToken, uint256 _amount ) external; - /// @notice TODO - /// @param _newOwner TODO + /// @notice Transfer meToken ownership to a new owner + /// @param _newOwner address to claim meToken ownership of msg.sender function transferMeTokenOwnership(address _newOwner) external; - /// @notice TODO + /// @notice Cancel the transfer of meToken ownership function cancelTransferMeTokenOwnership() external; - /// @notice TODO - /// @param _oldOwner TODO - function claimMeTokenOwnership(address _oldOwner) external; + /// @notice Claim the transfer of meToken ownership + /// @param _from address of current meToken owner + function claimMeTokenOwnership(address _from) external; - /// @notice TODO - /// @param _owner TODO - /// @return TODO + /// @notice View to return address of meToken owned by _owner + /// @param _owner address of meToken owner + /// @return address of meToken function getOwnerMeToken(address _owner) external view returns (address); - /// @notice TODO - /// @param _oldOwner TODO - /// @return TODO - function getPendingOwner(address _oldOwner) external view returns (address); + /// @notice View to see the address to claim meToken ownership from _from + /// @param _from address to transfer meToken ownership + /// @return address of pending meToken owner + function getPendingOwner(address _from) external view returns (address); - /// @notice TODO - /// @param meToken Address of meToken queried - /// @return meToken_ details of the meToken + /// @notice View to get details of a meToken + /// @param meToken address of meToken queried + /// @return meToken_ details of meToken function getDetails(address meToken) external view returns (Details.MeToken memory meToken_); - /// @notice TODO - /// @param _owner TODO - /// @return TODO + /// @notice View to return if an address owns a meToken or not + /// @param _owner address to query + /// @return true if owns a meToken, else false function isOwner(address _owner) external view returns (bool); } diff --git a/contracts/interfaces/IMigration.sol b/contracts/interfaces/IMigration.sol index 8ab5b8de..fc718e9e 100644 --- a/contracts/interfaces/IMigration.sol +++ b/contracts/interfaces/IMigration.sol @@ -1,14 +1,22 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +/// @title Generic migration vault interface +/// @author Carl Farterson (@carlfarterson) interface IMigration { + /// @notice Method to trigger actions from the migration vault if needed + /// @param _meToken address of meToken function poke(address _meToken) external; + /// @notice Method called when a meToken starts resubscribing to a new hub + /// @dev This is called within meTokenRegistry.initResubscribe() + /// @param _meToken address of meToken + /// @param _encodedArgs additional encoded arguments function initMigration(address _meToken, bytes memory _encodedArgs) external; + /// @notice Method to send assets from migration vault to the vault of the + /// target hub + /// @param _meToken address of meToken function finishMigration(address _meToken) external returns (uint256); - - // function isReady() external view returns (bool); - // function hasFinished() external view returns (bool); } diff --git a/contracts/interfaces/IMigrationRegistry.sol b/contracts/interfaces/IMigrationRegistry.sol index 90de2722..4721c5d2 100644 --- a/contracts/interfaces/IMigrationRegistry.sol +++ b/contracts/interfaces/IMigrationRegistry.sol @@ -1,42 +1,57 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +/// @title meToken migration registry interface +/// @author Carl Farterson (@carlfarterson) interface IMigrationRegistry { - event Approve(address initialVault, address targetVault, address migration); + /// @notice Event of approving a meToken migration route + /// @param _initialVault vault for meToken to start migration from + /// @param _targetVault vault for meToken to migrate to + /// @param _migration address of migration vault + event Approve( + address _initialVault, + address _targetVault, + address _migration + ); + + /// @notice Event of unapproving a meToken migration route + /// @param _initialVault vault for meToken to start migration from + /// @param _targetVault vault for meToken to migrate to + /// @param _migration address of migration vault event Unapprove( - address initialVault, - address targetVault, - address migration + address _initialVault, + address _targetVault, + address _migration ); - /// @notice TODO - /// @param initialVault TODO - /// @param targetVault TODO - /// @param migration TODO - function unapprove( - address initialVault, - address targetVault, - address migration + /// @notice Approve a vault migration route + /// @param _initialVault vault for meToken to start migration from + /// @param _targetVault vault for meToken to migrate to + /// @param _migration address of migration vault + function approve( + address _initialVault, + address _targetVault, + address _migration ) external; - /// @notice TODO - /// @param initialVault) TODO - /// @param targetVault TODO - /// @param migration TODO - function approve( - address initialVault, - address targetVault, - address migration + /// @notice Unapprove a vault migration route + /// @param _initialVault vault for meToken to start migration from + /// @param _targetVault vault for meToken to migrate to + /// @param _migration address of migration vault + function unapprove( + address _initialVault, + address _targetVault, + address _migration ) external; - /// @notice TODO - /// @param initialVault TODO - /// @param targetVault TODO - /// @param migration TODO - /// @return bool status of factory + /// @notice View to see if a specific migration route is approved + /// @param _initialVault vault for meToken to start migration from + /// @param _targetVault vault for meToken to migrate to + /// @param _migration address of migration vault + /// @return true if migration route is approved, else false function isApproved( - address initialVault, - address targetVault, - address migration + address _initialVault, + address _targetVault, + address _migration ) external view returns (bool); } diff --git a/contracts/interfaces/IRegistry.sol b/contracts/interfaces/IRegistry.sol index 102df1bc..37c4c652 100644 --- a/contracts/interfaces/IRegistry.sol +++ b/contracts/interfaces/IRegistry.sol @@ -1,13 +1,27 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +/// @title generic registry interface +/// @author Carl Farterson (@carlfarterson) interface IRegistry { + /// @notice Event of approving an address + /// @param _addr address to approve event Approve(address _addr); + + /// @notice Event of unapproving an address + /// @param _addr address to unapprove event Unapprove(address _addr); + /// @notice Approve an address + /// @param _addr address to approve function approve(address _addr) external; + /// @notice Unapprove an address + /// @param _addr address to unapprove function unapprove(address _addr) external; + /// @notice View to see if an address is approved + /// @param _addr address to view + /// @return true if address is approved, else false function isApproved(address _addr) external view returns (bool); } diff --git a/contracts/interfaces/ISingleAssetVault.sol b/contracts/interfaces/ISingleAssetVault.sol index 27a7826a..f2abceb1 100644 --- a/contracts/interfaces/ISingleAssetVault.sol +++ b/contracts/interfaces/ISingleAssetVault.sol @@ -1,8 +1,14 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +/// @title Vanilla erc20 vault interface +/// @author Carl Farterson (@carlfarterson) interface ISingleAssetVault { + /// @notice Event of starting a meTokens' migration to a new vault + /// @param _meToken address of meToken event StartMigration(address _meToken); + /// @notice Start a meTokens' migration to a new vault + /// @param _meToken address of meToken function startMigration(address _meToken) external; } diff --git a/contracts/interfaces/IVault.sol b/contracts/interfaces/IVault.sol index 987fcc3a..82fc25a8 100644 --- a/contracts/interfaces/IVault.sol +++ b/contracts/interfaces/IVault.sol @@ -1,32 +1,79 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; +/// @title generic vault interface +/// @author Carl Farterson (@carlfarterson) abstract contract IVault { + /// @notice Event when an asset is deposited to the vault + /// @param _from address which is depositing the asset + /// @param _asset address of asset + /// @param _depositAmount amount of assets deposited + /// @param _feeAmount amount of fees paid + event HandleDeposit( + address _from, + address _asset, + uint256 _depositAmount, + uint256 _feeAmount + ); + + /// @notice Event when an asset is withdrawn from the vault + /// @param _to address which will receive the asset + /// @param _asset address of asset + /// @param _withdrawalAmount amount of assets withdrawn + /// @param _feeAmount amount of fees paid + event HandleWithdrawal( + address _to, + address _asset, + uint256 _withdrawalAmount, + uint256 _feeAmount + ); + + /// @notice Event when claiming the accrued fees of an asset + /// @param _recipient Recipient of the asset + /// @param _asset address of asset + /// @param _amount amount of asset event Claim(address _recipient, address _asset, uint256 _amount); /// @dev key: addr of asset, value: cumulative fees paid in the asset mapping(address => uint256) public accruedFees; + /// @notice Claim the accrued fees of an asset + /// @param _asset address of asset + /// @param _max true if claiming all accrued fees of the asset, else false + /// @param _amount amount of asset to claim function claim( address _asset, bool _max, uint256 _amount ) external virtual; + /// @notice Deposit an asset to the vault + /// @param _from address which is depositing the asset + /// @param _asset address of asset + /// @param _depositAmount amount of assets deposited + /// @param _feeAmount amount of fees paid function handleDeposit( + address _from, address _asset, uint256 _depositAmount, - uint256 _feeAmount, - address _from + uint256 _feeAmount ) external virtual; + /// @notice Withdraw an asset from the vault + /// @param _to address which will receive the asset + /// @param _asset address of asset + /// @param _withdrawalAmount amount of assets withdrawn function handleWithdrawal( + address _to, address _asset, uint256 _withdrawalAmount, - uint256 _feeAmount, - address _to + uint256 _feeAmount ) external virtual; + /// @notice View to see if an asset with encoded arguments passed + /// when a vault is registered to a new hub + /// @param _asset address of asset + /// @param _encodedArgs additional encoded arguments function isValid(address _asset, bytes memory _encodedArgs) external virtual diff --git a/contracts/migrations/UniswapSingleTransferMigration.sol b/contracts/migrations/UniswapSingleTransferMigration.sol index 718db20e..8a171d9f 100644 --- a/contracts/migrations/UniswapSingleTransferMigration.sol +++ b/contracts/migrations/UniswapSingleTransferMigration.sol @@ -6,7 +6,6 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; - import "../libs/Details.sol"; import "../vaults/Vault.sol"; import "../interfaces/IMigration.sol"; @@ -106,9 +105,6 @@ contract UniswapSingleTransferMigration is if (!usts_.started) { ISingleAssetVault(hub_.vault).startMigration(_meToken); usts_.started = true; - } - - if (!usts_.swapped) { amountOut = _swap(_meToken); } else { // No swap, amountOut = amountIn diff --git a/contracts/registries/MeTokenRegistry.sol b/contracts/registries/MeTokenRegistry.sol index a70ccad2..4fc6626f 100644 --- a/contracts/registries/MeTokenRegistry.sol +++ b/contracts/registries/MeTokenRegistry.sol @@ -320,13 +320,13 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { function setWarmup(uint256 warmup_) external onlyOwner { require(warmup_ != _warmup, "warmup_ == _warmup"); - require(warmup_ + _duration < hub.getWarmup(), "too long"); + require(warmup_ + _duration < hub.warmup(), "too long"); _warmup = warmup_; } function setDuration(uint256 duration_) external onlyOwner { require(duration_ != _duration, "duration_ == _duration"); - require(duration_ + _warmup < hub.getWarmup(), "too long"); + require(duration_ + _warmup < hub.warmup(), "too long"); _duration = duration_; } @@ -365,15 +365,15 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { meToken_ = _meTokens[_meToken]; } - function getWarmup() external view returns (uint256) { + function warmup() external view returns (uint256) { return _warmup; } - function getDuration() external view returns (uint256) { + function duration() external view returns (uint256) { return _duration; } - function getCooldown() external view returns (uint256) { + function cooldown() external view returns (uint256) { return _cooldown; } diff --git a/contracts/vaults/Vault.sol b/contracts/vaults/Vault.sol index 138b27a9..767767b9 100644 --- a/contracts/vaults/Vault.sol +++ b/contracts/vaults/Vault.sol @@ -38,29 +38,31 @@ abstract contract Vault is Ownable, IVault { } function handleDeposit( + address _from, address _asset, uint256 _depositAmount, - uint256 _feeAmount, - address _from + uint256 _feeAmount ) external override { require(msg.sender == foundry, "!foundry"); IERC20(_asset).safeTransferFrom(_from, address(this), _depositAmount); if (_feeAmount > 0) { accruedFees[_asset] += _feeAmount; } + emit HandleDeposit(_from, _asset, _depositAmount, _feeAmount); } function handleWithdrawal( + address _to, address _asset, uint256 _withdrawalAmount, - uint256 _feeAmount, - address _to + uint256 _feeAmount ) external override { require(msg.sender == foundry, "!foundry"); IERC20(_asset).safeTransfer(_to, _withdrawalAmount); if (_feeAmount > 0) { accruedFees[_asset] += _feeAmount; } + emit HandleWithdrawal(_to, _asset, _withdrawalAmount, _feeAmount); } function claim( diff --git a/deploy/VaultRegistry.ts b/deploy/VaultRegistry.ts deleted file mode 100644 index 2f2a1aa6..00000000 --- a/deploy/VaultRegistry.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { DeployFunction } from "hardhat-deploy/types"; - -const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const { deployments, ethers } = hre; - const [admin] = await ethers.getSigners(); - deployments.log("deploy address :", admin.address); - - const opts = { - from: admin.address, - log: true, - }; - - const vault = await deployments.deploy("VaultRegistry", { - args: [], - ...opts, - }); - deployments.log("VaultRegistry deployed at:", vault.address); -}; -export default func; -func.tags = ["VaultRegistry"]; diff --git a/scripts/deploy.ts b/scripts/deploy.ts index d1cb1889..c19add2b 100644 --- a/scripts/deploy.ts +++ b/scripts/deploy.ts @@ -86,10 +86,6 @@ async function main() { const weightedAverage = await deploy("WeightedAverage"); contracts.push(weightedAverage.address); - printLog("Deploying BancorABDK Contract..."); - const BancorABDK = await deploy("BancorABDK"); - contracts.push(BancorABDK.address); - printLog("Deploying CurveRegistry Contract..."); const curveRegistry = await deploy("CurveRegistry"); contracts.push(curveRegistry.address); @@ -114,6 +110,15 @@ async function main() { const hub = await deploy("Hub"); contracts.push(hub.address); + printLog("Deploying BancorABDK Contract..."); + const BancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); + contracts.push(BancorABDK.address); + printLog("Deploying MeTokenFactory Contract..."); const meTokenFactory = await deploy("MeTokenFactory"); contracts.push(meTokenFactory.address); diff --git a/scripts/deploy2.ts b/scripts/deploy2.ts deleted file mode 100644 index 343250d1..00000000 --- a/scripts/deploy2.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { network, run, ethers, getNamedAccounts } from "hardhat"; -import { Hub } from "../artifacts/types/Hub"; -import { VaultRegistry } from "../artifacts/types/VaultRegistry"; -import { SingleAssetVault } from "../artifacts/types/SingleAssetVault"; -import fs from "fs"; -import { deploy } from "../test/utils/helpers"; -import { MeTokenFactory } from "../artifacts/types/MeTokenFactory"; -import { MeTokenRegistry } from "../artifacts/types/MeTokenRegistry"; -import { BancorABDK } from "../artifacts/types/BancorABDK"; -import { CurveRegistry } from "../artifacts/types/CurveRegistry"; -import { Foundry } from "../artifacts/types/Foundry"; -import { WeightedAverage } from "../artifacts/types/WeightedAverage"; -import { BigNumber } from "ethers"; -import { MigrationRegistry } from "../artifacts/types/MigrationRegistry"; -import { Fees } from "../artifacts/types/Fees"; -const ETHERSCAN_CHAIN_IDS = [1, 3, 4, 5, 42]; -const SUPPORTED_NETWORK = [1, 3, 4, 100, 31337]; -const deployDir = "deployment"; -const contracts: any[] = []; -const REFUND_RATIO = 50000; -const PRECISION = BigNumber.from(10).pow(18); -const MAX_WEIGHT = 1000000; -const RESERVE_WEIGHT = BigNumber.from(MAX_WEIGHT).div(2).toString(); -const baseY = PRECISION.div(1000).toString(); -const MINT_FEE = 0; -const BURN_BUYER_FEE = 0; -const BURN_OWNER_FEE = 0; -const TRANSFER_FEE = 0; -const INTEREST_FEE = 0; -const YIELD_FEE = 0; -let DAI; -function currencySymbol(chainId: number) { - switch (chainId.toString()) { - case "100": - return "XDAI"; - default: - return "ETH"; - } -} -function currencyAddress(chainId: number) { - switch (chainId.toString()) { - // Ropsten - case "3": - return "0x92d75D18C4A2aDF86365EcFd5219f13AfED5103C"; - - // Rinkeby - case "4": - return "0x92d75D18C4A2aDF86365EcFd5219f13AfED5103C"; - - // Hardhat - case "31337": - return "0x6B175474E89094C44Da98b954EedeAC495271d0F"; - - default: { - throw new Error("Un-supported network"); - } - } -} -function printLog(msg: string) { - console.log(msg); - /* if (process.stdout.isTTY) { - process.stdout.clearLine(-1); - process.stdout.cursorTo(0); - process.stdout.write(msg); - } */ -} - -async function main() { - const [deployer, DAO] = await ethers.getSigners(); - ({ DAI } = await getNamedAccounts()); - - const address = await deployer.getAddress(); - if (!deployer.provider) { - process.exit(1); - } - const { chainId } = await deployer.provider.getNetwork(); - - if (SUPPORTED_NETWORK.indexOf(chainId) === -1) - throw new Error("Un-supported network"); - - console.log("Deploying meTokens on network:", network.name); - console.log("DAI Address:", DAI); - console.log("Account address:", address); - console.log( - "Account balance:", - ethers.utils.formatEther(await deployer.provider.getBalance(address)), - currencySymbol(chainId) - ); - - // printLog("Deploying weightedAverage Contract..."); - // const weightedAverage = await deploy("WeightedAverage"); - // contracts.push(weightedAverage.address); - - // printLog("Deploying BancorABDK Contract..."); - // const BancorABDK = await deploy("BancorABDK"); - // contracts.push(BancorABDK.address); - - // printLog("Deploying CurveRegistry Contract..."); - // const curveRegistry = await deploy("CurveRegistry"); - // contracts.push(curveRegistry.address); - - // printLog("Deploying VaultRegistry Contract..."); - // const vaultRegistry = await deploy("VaultRegistry"); - // contracts.push(vaultRegistry.address); - - // printLog("Deploing MigrationRegistry Contract..."); - // const migrationRegistry = await deploy( - // "MigrationRegistry" - // ); - // contracts.push(migrationRegistry.address); - - printLog("Deploying Foundry Contract..."); - const foundry = await deploy("Foundry", { - WeightedAverage: "0x13e7bF4A65822fC846b07fbad3e806eD1D094b59", - }); - contracts.push(foundry.address); - - printLog("Deploying Hub Contract..."); - const hub = await deploy("Hub"); - contracts.push(hub.address); - - // printLog("Deploying MeTokenFactory Contract..."); - // const meTokenFactory = await deploy("MeTokenFactory"); - // contracts.push(meTokenFactory.address); - - printLog("Deploying MeTokenRegistry Contract..."); - const meTokenRegistry = await deploy( - "MeTokenRegistry", - undefined, - foundry.address, - hub.address, - "0xf4a2AacCB5C9dCa49E6F65d497dED9616d127B92", - "0x14d61228e0648d43DC5f8c148A488c0Ae81ec2CC" - ); - - // printLog("Deploying SingleAssetVault Contract..."); - // const singleAssetVault = await deploy( - // "SingleAssetVault", - // undefined, //no libs - // DAO.address, // DAO - // foundry.address, // foundry - // hub.address, // hub - // meTokenRegistry.address, //IMeTokenRegistry - // migrationRegistry.address //IMigrationRegistry - // ); - - // printLog("Deploying fees Contract..."); - // const fees = await deploy("Fees"); - // contracts.push(fees.address); - - // printLog("Registering Bancor Curve to curve registry..."); - // let tx = await curveRegistry.approve(BancorABDK.address); - // await tx.wait(); - // printLog("Registering vault to vault registry..."); - // tx = await vaultRegistry.approve(singleAssetVault.address); - // await tx.wait(); - - // printLog("Initializing fees..."); - // tx = await fees.initialize( - // MINT_FEE, - // BURN_BUYER_FEE, - // BURN_OWNER_FEE, - // TRANSFER_FEE, - // INTEREST_FEE, - // YIELD_FEE - // ); - // await tx.wait(); - - printLog("Initializing hub Contract..."); - let tx = await hub.initialize( - foundry.address, - "0xCA82C0B535aaD7b26FE6feFE0B3D243ACf180D93", - "0xB8b36dcF76bE040dB276f5353e7cb23C51798811" - ); - await tx.wait(); - - const encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint32"], - [baseY, RESERVE_WEIGHT] - ); - const encodedVaultArgs = ethers.utils.defaultAbiCoder.encode( - ["address"], - [DAI] - ); - - printLog("Registering hub ..."); - tx = await hub.register( - DAI, - "0x1a96C7bB64070f6129a64F981CFCb544D78e7842", - "0x6551A593a18586baeF221355886697cb39410587", - REFUND_RATIO, //refund ratio - encodedCurveDetails, - encodedVaultArgs - ); - await tx.wait(); - - printLog("Initializing foundry Contract..."); - tx = await foundry.initialize( - hub.address, - "0x889356A0325cF68Ea7aAE3554baa003E0297f963", - meTokenRegistry.address - ); - await tx.wait(); - const receipt = await deployer.provider.getTransactionReceipt(tx.hash); - const isEtherscan = ETHERSCAN_CHAIN_IDS.includes(chainId); - if (isEtherscan) { - printLog(`Waiting for Etherscan to index Contracts...`); - await tx.wait(5); - printLog("Verifying Contracts...\n"); - - const TASK_VERIFY = "verify"; - - try { - await run(TASK_VERIFY, { - address: "0x1a96C7bB64070f6129a64F981CFCb544D78e7842", - constructorArgsParams: [ - DAO, // DAO - foundry.address, // foundry - hub.address, // hub - meTokenRegistry.address, //IMeTokenRegistry - "0x14d61228e0648d43DC5f8c148A488c0Ae81ec2CC", //IMigrationRegistry - ], - }); - await run(TASK_VERIFY, { - address: meTokenRegistry.address, - constructorArgsParams: [ - foundry.address, - hub.address, - "0xf4a2AacCB5C9dCa49E6F65d497dED9616d127B92", - "0x14d61228e0648d43DC5f8c148A488c0Ae81ec2CC", - ], - }); - } catch (error) { - console.error( - `Error verifying ${"0x1a96C7bB64070f6129a64F981CFCb544D78e7842"}: `, - error - ); - } - - for (let i = 0; i < contracts.length; ++i) { - try { - await run(TASK_VERIFY, { - address: contracts[i], - constructorArguments: [], - }); - } catch (error) { - console.error(`Error verifying ${contracts[i]}: `, error); - } - } - - console.log("\nVerified Contracts."); - } - printLog("Deployment done !"); - - const deploymentInfo = { - network: network.name, - "Hub Contract Address": hub.address, - // "VaultRegistry Contract Address": vaultRegistry.address, - // "SingleAssetVault Contract Address": singleAssetVault.address, - // "SingleAsset Vault Contract Address": singleAssetVault.address, - // "Fee Contract Address": fees.address, - // "Curve Registry Contract Address": curveRegistry.address, - // "Bancor Curve Contract Address": BancorABDK.address, - "Foundry Contract Address": foundry.address, - // "MeToken Factory Contract Address": meTokenFactory.address, - "MeToken Registry Contract Address": meTokenRegistry.address, - // "WeightedAverage Contract Address": weightedAverage.address, - "Block Number": receipt.blockNumber.toString(), - }; - - if (!fs.existsSync(deployDir)) { - fs.mkdirSync(deployDir); - } - - fs.writeFileSync( - `${deployDir}/script-${network.name}-2.json`, - JSON.stringify(deploymentInfo) - ); - console.log( - `Latest Contract Address written to: ${deployDir}/script-${network.name}-2.json` - ); -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/test/contracts/Foundry.ts b/test/contracts/Foundry.ts index 56ba5191..fcb7e979 100644 --- a/test/contracts/Foundry.ts +++ b/test/contracts/Foundry.ts @@ -24,397 +24,480 @@ import { MeToken } from "../../artifacts/types/MeToken"; import { expect } from "chai"; import { UniswapSingleTransferMigration } from "../../artifacts/types/UniswapSingleTransferMigration"; import { hubSetup } from "../utils/hubSetup"; - -describe("Foundry.sol", () => { - let DAI: string; - let DAIWhale: string; - let daiHolder: Signer; - let dai: ERC20; - let account0: SignerWithAddress; - let account1: SignerWithAddress; - let account2: SignerWithAddress; - let _curve: BancorABDK; - let meTokenRegistry: MeTokenRegistry; - let foundry: Foundry; - let token: ERC20; - let meToken: MeToken; - let tokenHolder: Signer; - let hub: Hub; - let singleAssetVault: SingleAssetVault; - let migrationRegistry: MigrationRegistry; - let curveRegistry: CurveRegistry; - - const hubId = 1; - const name = "Carl meToken"; - const symbol = "CARL"; - const refundRatio = 240000; - const initRefundRatio = 50000; - const PRECISION = ethers.utils.parseEther("1"); - const amount = ethers.utils.parseEther("10"); - const amount1 = ethers.utils.parseEther("100"); - const amount2 = ethers.utils.parseEther("6.9"); - - // TODO: pass in curve arguments to function - // TODO: then loop over array of set of curve arguments - const MAX_WEIGHT = 1000000; - const reserveWeight = MAX_WEIGHT / 2; - const baseY = PRECISION.div(1000); - - // for 1 DAI we get 1000 metokens - // const baseY = ethers.utils.parseEther("1").mul(1000).toString(); - // weight at 50% linear curve - // const reserveWeight = BigNumber.from(MAX_WEIGHT).div(2).toString(); - before(async () => { - ({ DAI, DAIWhale } = await getNamedAccounts()); - const encodedVaultArgs = ethers.utils.defaultAbiCoder.encode( - ["address"], - [DAI] - ); - // TODO: pass in name of curve to deploy, encodedCurveDetails to general func - const encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint32"], - [baseY, reserveWeight] +import { text } from "stream/consumers"; + +const setup = async () => { + describe("Foundry.sol", () => { + let DAI: string; + let DAIWhale: string; + let daiHolder: Signer; + let dai: ERC20; + let account0: SignerWithAddress; + let account1: SignerWithAddress; + let account2: SignerWithAddress; + let _curve: BancorABDK; + let meTokenRegistry: MeTokenRegistry; + let foundry: Foundry; + let token: ERC20; + let meToken: MeToken; + let tokenHolder: Signer; + let hub: Hub; + let singleAssetVault: SingleAssetVault; + let migrationRegistry: MigrationRegistry; + let curveRegistry: CurveRegistry; + + const hubId = 1; + const name = "Carl meToken"; + const symbol = "CARL"; + const refundRatio = 240000; + const initRefundRatio = 50000; + const PRECISION = ethers.utils.parseEther("1"); + const amount = ethers.utils.parseEther("10"); + const amount1 = ethers.utils.parseEther("100"); + const amount2 = ethers.utils.parseEther("6.9"); + const tokenDepositedInETH = 10; + const tokenDeposited = ethers.utils.parseEther( + tokenDepositedInETH.toString() ); - _curve = await deploy("BancorABDK"); - - ({ - token, - tokenHolder, - hub, - foundry, - account0, - account1, - account2, - meTokenRegistry, - curveRegistry, - migrationRegistry, - singleAssetVault, - } = await hubSetup( - encodedCurveDetails, - encodedVaultArgs, - initRefundRatio, - _curve - )); - - // Prefund owner/buyer w/ DAI - dai = token; - await dai.connect(tokenHolder).transfer(account0.address, amount1.mul(10)); - await dai.connect(tokenHolder).transfer(account1.address, amount1.mul(10)); - await dai.connect(tokenHolder).transfer(account2.address, amount1.mul(10)); - let max = ethers.constants.MaxUint256; - await dai.connect(account0).approve(singleAssetVault.address, max); - await dai.connect(account1).approve(singleAssetVault.address, max); - await dai.connect(account2).approve(singleAssetVault.address, max); - await dai.connect(account1).approve(meTokenRegistry.address, max); - // account0 is registering a metoken - const tx = await meTokenRegistry - .connect(account0) - .subscribe(name, symbol, hubId, 0); - const meTokenAddr = await meTokenRegistry.getOwnerMeToken(account0.address); - - meToken = await getContractAt("MeToken", meTokenAddr); - }); - describe("mint()", () => { - it("balanceLocked = 0, balancePooled = 0, mint on meToken creation", async () => { - let expectedMeTokensMinted = await _curve.viewMeTokensMinted( - amount1, - hubId, - 0, - 0 - ); + // TODO: pass in curve arguments to function + // TODO: then loop over array of set of curve arguments + const MAX_WEIGHT = 1000000; + const reserveWeight = MAX_WEIGHT / 2; + const baseY = PRECISION.div(1000); - // Get balances before mint - let minterDaiBalanceBefore = await dai.balanceOf(account1.address); - let vaultDaiBalanceBefore = await dai.balanceOf(singleAssetVault.address); - // let expectedAssetsDeposited = await _curve.viewAssetsDeposited( - // expectedMeTokensMinted, - // hubId, - // 0, - // 0 - // ); - const calculated = calculateCollateralToDepositFromZero( - toETHNumber(expectedMeTokensMinted), - toETHNumber(baseY), - reserveWeight / MAX_WEIGHT + // for 1 DAI we get 1000 metokens + // const baseY = ethers.utils.parseEther("1").mul(1000).toString(); + // weight at 50% linear curve + // const reserveWeight = BigNumber.from(MAX_WEIGHT).div(2).toString(); + before(async () => { + ({ DAI, DAIWhale } = await getNamedAccounts()); + const encodedVaultArgs = ethers.utils.defaultAbiCoder.encode( + ["address"], + [DAI] ); - let res = toETHNumber(expectedMeTokensMinted); - res = toETHNumber(baseY); - expect(toETHNumber(amount1)).to.approximately( - calculated, - 0.000000000000000000000001 + // TODO: pass in name of curve to deploy, encodedCurveDetails to general func + const encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint32"], + [baseY, reserveWeight] ); - - // expect(toETHNumber(amount1)).to.approximately( - // toETHNumber(expectedAssetsDeposited), - // 0.000000000000000000000001 - // ); - - // Mint first meTokens to owner account1 - let tx = await meTokenRegistry - .connect(account1) - .subscribe(name, symbol, hubId, amount1); - let meTokenAddr = await meTokenRegistry.getOwnerMeToken(account1.address); - - meToken = await getContractAt("MeToken", meTokenAddr); - - // Compare expected meTokens minted to actual held - let meTokensMinted = await meToken.balanceOf(account1.address); - expect(meTokensMinted).to.equal(expectedMeTokensMinted); - let totalSupply = await meToken.totalSupply(); - expect(totalSupply).to.equal(meTokensMinted); - - // Compare owner dai balance before/after - let minterDaiBalanceAfter = await dai.balanceOf(account1.address); - - expect( - // TODO: how to verify difference of numbers to type of amount1? - minterDaiBalanceBefore.sub(minterDaiBalanceAfter) - ).to.equal(amount1); - - // Expect balance of vault to have increased by assets deposited - let vaultDaiBalanceAfter = await dai.balanceOf(singleAssetVault.address); - expect(vaultDaiBalanceAfter.sub(vaultDaiBalanceBefore)).to.equal(amount1); - }); - - it("balanceLocked = 0, balancePooled = 0, mint after meToken creation", async () => { - let expectedMeTokensMinted = await _curve.viewMeTokensMinted( - amount1, - hubId, - 0, - 0 + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + _curve = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address ); - // let expectedAssetsDeposited = await _curve.viewAssetsDeposited( - // expectedMeTokensMinted, - // hubId, - // 0, - // 0 - // ); - // Get balances before mint - let minterDaiBalanceBefore = await dai.balanceOf(account2.address); - let vaultDaiBalanceBefore = await dai.balanceOf(singleAssetVault.address); - - // Create meToken w/o issuing supply and account2 as the owner - const tx = await meTokenRegistry - .connect(account2) - .subscribe(name, symbol, hubId, 0); + ({ + token, + tokenHolder, + account0, + account1, + account2, + meTokenRegistry, + curveRegistry, + migrationRegistry, + singleAssetVault, + } = await hubSetup( + encodedCurveDetails, + encodedVaultArgs, + initRefundRatio, + hub, + foundry, + _curve + )); + + // Prefund owner/buyer w/ DAI + dai = token; + await dai + .connect(tokenHolder) + .transfer(account0.address, amount1.mul(10)); + await dai + .connect(tokenHolder) + .transfer(account1.address, amount1.mul(10)); + await dai + .connect(tokenHolder) + .transfer(account2.address, amount1.mul(10)); + const max = ethers.constants.MaxUint256; + await dai.connect(account0).approve(singleAssetVault.address, max); + await dai.connect(account1).approve(singleAssetVault.address, max); + await dai.connect(account2).approve(singleAssetVault.address, max); + await dai.connect(account1).approve(meTokenRegistry.address, max); + // account0 is registering a metoken + await meTokenRegistry.connect(account0).subscribe(name, symbol, hubId, 0); const meTokenAddr = await meTokenRegistry.getOwnerMeToken( - account2.address + account0.address ); meToken = await getContractAt("MeToken", meTokenAddr); - - // Mint meToken - await foundry - .connect(account2) - .mint(meToken.address, amount1, account2.address); - // Compare expected meTokens minted to actual held - const meTokensMinted = await meToken.balanceOf(account2.address); - expect(meTokensMinted).to.equal(expectedMeTokensMinted); - const totalSupply = await meToken.totalSupply(); - expect(totalSupply).to.equal(meTokensMinted); - // Compare buyer dai balance before/after - let minterDaiBalanceAfter = await dai.balanceOf(account2.address); - expect(minterDaiBalanceBefore.sub(minterDaiBalanceAfter)).to.equal( - amount1 - ); - - // Expect balance of vault to have increased by assets deposited - let vaultDaiBalanceAfter = await dai.balanceOf(singleAssetVault.address); - expect(vaultDaiBalanceAfter.sub(vaultDaiBalanceBefore)).to.equal(amount1); - // expect(toETHNumber(amount1)).to.be.approximately( - // toETHNumber(expectedAssetsDeposited), - // 0.000000000000000001 - // ); }); - it("balanceLocked = 0, balancePooled > 0", async () => { - // TODO + describe("mint()", () => { + it("balanceLocked = 0, balancePooled = 0, mint on meToken creation", async () => { + let expectedMeTokensMinted = await _curve.viewMeTokensMinted( + amount1, + hubId, + 0, + 0 + ); + + // Get balances before mint + let minterDaiBalanceBefore = await dai.balanceOf(account1.address); + let vaultDaiBalanceBefore = await dai.balanceOf( + singleAssetVault.address + ); + // let expectedAssetsDeposited = await _curve.viewAssetsDeposited( + // expectedMeTokensMinted, + // hubId, + // 0, + // 0 + // ); + const calculated = calculateCollateralToDepositFromZero( + toETHNumber(expectedMeTokensMinted), + toETHNumber(baseY), + reserveWeight / MAX_WEIGHT + ); + let res = toETHNumber(expectedMeTokensMinted); + res = toETHNumber(baseY); + expect(toETHNumber(amount1)).to.approximately( + calculated, + 0.000000000000000000000001 + ); + + // expect(toETHNumber(amount1)).to.approximately( + // toETHNumber(expectedAssetsDeposited), + // 0.000000000000000000000001 + // ); + + // Mint first meTokens to owner account1 + let tx = await meTokenRegistry + .connect(account1) + .subscribe(name, symbol, hubId, amount1); + let meTokenAddr = await meTokenRegistry.getOwnerMeToken( + account1.address + ); + + meToken = await getContractAt("MeToken", meTokenAddr); + + // Compare expected meTokens minted to actual held + let meTokensMinted = await meToken.balanceOf(account1.address); + expect(meTokensMinted).to.equal(expectedMeTokensMinted); + let totalSupply = await meToken.totalSupply(); + expect(totalSupply).to.equal(meTokensMinted); + + // Compare owner dai balance before/after + let minterDaiBalanceAfter = await dai.balanceOf(account1.address); + + expect( + // TODO: how to verify difference of numbers to type of amount1? + minterDaiBalanceBefore.sub(minterDaiBalanceAfter) + ).to.equal(amount1); + + // Expect balance of vault to have increased by assets deposited + let vaultDaiBalanceAfter = await dai.balanceOf( + singleAssetVault.address + ); + expect(vaultDaiBalanceAfter.sub(vaultDaiBalanceBefore)).to.equal( + amount1 + ); + }); + + it("balanceLocked = 0, balancePooled = 0, mint after meToken creation", async () => { + let expectedMeTokensMinted = await _curve.viewMeTokensMinted( + amount1, + hubId, + 0, + 0 + ); + // let expectedAssetsDeposited = await _curve.viewAssetsDeposited( + // expectedMeTokensMinted, + // hubId, + // 0, + // 0 + // ); + // Get balances before mint + let minterDaiBalanceBefore = await dai.balanceOf(account2.address); + let vaultDaiBalanceBefore = await dai.balanceOf( + singleAssetVault.address + ); + + // Create meToken w/o issuing supply and account2 as the owner + const tx = await meTokenRegistry + .connect(account2) + .subscribe(name, symbol, hubId, 0); + const meTokenAddr = await meTokenRegistry.getOwnerMeToken( + account2.address + ); + + meToken = await getContractAt("MeToken", meTokenAddr); + + // Mint meToken + await foundry + .connect(account2) + .mint(meToken.address, amount1, account2.address); + // Compare expected meTokens minted to actual held + const meTokensMinted = await meToken.balanceOf(account2.address); + expect(meTokensMinted).to.equal(expectedMeTokensMinted); + const totalSupply = await meToken.totalSupply(); + expect(totalSupply).to.equal(meTokensMinted); + // Compare buyer dai balance before/after + let minterDaiBalanceAfter = await dai.balanceOf(account2.address); + expect(minterDaiBalanceBefore.sub(minterDaiBalanceAfter)).to.equal( + amount1 + ); + + // Expect balance of vault to have increased by assets deposited + let vaultDaiBalanceAfter = await dai.balanceOf( + singleAssetVault.address + ); + expect(vaultDaiBalanceAfter.sub(vaultDaiBalanceBefore)).to.equal( + amount1 + ); + // expect(toETHNumber(amount1)).to.be.approximately( + // toETHNumber(expectedAssetsDeposited), + // 0.000000000000000001 + // ); + }); + + it("balanceLocked = 0, balancePooled > 0", async () => { + // burn all. balanceLocked = 0, balancePooled = 0 + await foundry + .connect(account2) + .burn( + meToken.address, + await meToken.balanceOf(account2.address), + account2.address + ); + + // when mints + await foundry + .connect(account1) + .mint(meToken.address, tokenDeposited, account1.address); + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + expect(meTokenRegistryDetails.balanceLocked).to.equal(0); + expect(meTokenRegistryDetails.balancePooled).to.be.gt(0); + }); + + it("balanceLocked > 0, balancePooled = 0", async () => { + // when buyer burns + await foundry + .connect(account1) + .burn( + meToken.address, + await meToken.balanceOf(account1.address), + account1.address + ); + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + expect(meTokenRegistryDetails.balancePooled).to.equal(0); + expect(meTokenRegistryDetails.balanceLocked).to.be.gt(0); + }); + + it("balanceLocked > 0, balancePooled > 0", async () => { + // when mints + await foundry + .connect(account1) + .mint(meToken.address, tokenDeposited, account1.address); + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + expect(meTokenRegistryDetails.balanceLocked).to.be.gt(0); + expect(meTokenRegistryDetails.balancePooled).to.be.gt(0); + }); }); - it("balanceLocked > 0, balancePooled = 0", async () => { - // TODO - }); - - it("balanceLocked > 0, balancePooled > 0", async () => { - // TODO - }); - }); - - describe("burn()", () => { - it("balanceLocked = 0, ending supply = 0, buyer", async () => { - // TODO + describe("burn()", () => { + it("balanceLocked > 0, ending supply = 0, buyer", async () => { + await foundry + .connect(account1) + .burn( + meToken.address, + await meToken.balanceOf(account1.address), + account1.address + ); + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + expect(await meToken.totalSupply()).to.be.equal(0); + expect(meTokenRegistryDetails.balanceLocked).to.be.gt(0); + expect(meTokenRegistryDetails.balancePooled).to.be.equal(0); + }); + it("balanceLocked = 0, ending supply = 0, owner", async () => { + // mint some to owner + await foundry + .connect(account2) + .mint(meToken.address, tokenDeposited, account2.address); + + await foundry + .connect(account2) + .burn( + meToken.address, + await meToken.balanceOf(account2.address), + account2.address + ); + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + expect(await meToken.totalSupply()).to.be.equal(0); + expect(meTokenRegistryDetails.balanceLocked).to.be.equal(0); + expect(meTokenRegistryDetails.balancePooled).to.be.equal(0); + }); + it("balanceLocked > 0, ending supply > 0, buyer", async () => { + // mint some to buyer + await foundry + .connect(account1) + .mint(meToken.address, tokenDeposited, account1.address); + + // mint some to owner + await foundry + .connect(account2) + .mint(meToken.address, tokenDeposited, account2.address); + + // burn buyer shares + await foundry + .connect(account1) + .burn( + meToken.address, + await meToken.balanceOf(account1.address), + account1.address + ); + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + expect(await meToken.totalSupply()).to.be.gt(0); + expect(meTokenRegistryDetails.balanceLocked).to.be.gt(0); + expect(meTokenRegistryDetails.balancePooled).to.be.gt(0); + }); + it("balanceLocked = 0, ending supply > 0, owner", async () => { + // burn all owner shares + await foundry + .connect(account2) + .burn( + meToken.address, + await meToken.balanceOf(account2.address), + account2.address + ); + + // mint some to owner + await foundry + .connect(account2) + .mint(meToken.address, tokenDeposited, account2.address); + + // burn some owner shares + await foundry + .connect(account2) + .burn( + meToken.address, + (await meToken.balanceOf(account2.address)).div(2), + account2.address + ); + + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + expect(await meToken.totalSupply()).to.be.gt(0); + expect(meTokenRegistryDetails.balanceLocked).to.equal(0); + expect(meTokenRegistryDetails.balancePooled).to.be.gt(0); + }); + it("balanceLocked > 0, ending supply = 0, owner", async () => { + // burn from buyer + await foundry + .connect(account2) + .burn( + meToken.address, + await meToken.balanceOf(account2.address), + account2.address + ); + + // mint some to buyer + await foundry + .connect(account1) + .mint(meToken.address, tokenDeposited, account1.address); + + // burn from buyer + await foundry + .connect(account1) + .burn( + meToken.address, + await meToken.balanceOf(account1.address), + account1.address + ); + + const meTokenRegistryDetails = await meTokenRegistry.getDetails( + meToken.address + ); + expect(await meToken.totalSupply()).to.be.equal(0); + expect(meTokenRegistryDetails.balanceLocked).to.be.gt(0); + expect(meTokenRegistryDetails.balancePooled).to.be.equal(0); + }); + it("balanceLocked > 0, ending supply > 0, owner", async () => { + // mint some to buyer + await foundry + .connect(account1) + .mint(meToken.address, tokenDeposited, account1.address); + }); + after(async () => { + await foundry + .connect(account1) + .burn( + meToken.address, + await meToken.balanceOf(account1.address), + account1.address + ); + }); }); - it("balanceLocked = 0, ending supply = 0, owner", async () => { - // TODO - }); - it("balanceLocked = 0, ending supply > 0, buyer", async () => { - // TODO - }); - it("balanceLocked = 0, ending supply > 0, owner", async () => { - // TODO - }); - it("balanceLocked > 0, ending supply = 0, buyer", async () => { - // TODO - }); - it("balanceLocked > 0, ending supply = 0, owner", async () => { - // TODO - }); - it("balanceLocked > 0, ending supply > 0, buyer", async () => { - // TODO - }); - it("balanceLocked > 0, ending supply > 0, owner", async () => { - // TODO - }); - }); - - it("mint() from buyer should work", async () => { - // metoken should be registered - expect(await meToken.name()).to.equal(name); - expect(await meToken.symbol()).to.equal(symbol); - expect(await meToken.decimals()).to.equal(18); - expect(await meToken.totalSupply()).to.equal(0); - - const balBefore = await dai.balanceOf(account0.address); - const tokenBalBefore = await meToken.balanceOf(account2.address); - const meTokenDetails = await meTokenRegistry.getDetails(meToken.address); - // gas savings - const totalSupply = await meToken.totalSupply(); - - // mint - const meTokensMinted = await _curve.viewMeTokensMinted( - amount, - hubId, - totalSupply, - meTokenDetails.balancePooled - ); - await foundry.mint(meToken.address, amount, account2.address); - - const tokenBalAfter = await meToken.balanceOf(account2.address); - const balAfter = await dai.balanceOf(account0.address); - expect(balBefore.sub(balAfter)).equal(amount); - expect(tokenBalAfter.sub(tokenBalBefore)).equal(meTokensMinted); - - const hubDetail = await hub.getDetails(hubId); - const balVault = await dai.balanceOf(hubDetail.vault); - expect(balVault).equal(amount); - - // assert token infos - const meTokenAddr = await meTokenRegistry.getOwnerMeToken(account0.address); - expect(meTokenAddr).to.equal(meToken.address); - // should be greater than 0 - expect(await meToken.totalSupply()).to.equal( - totalSupply.add(meTokensMinted) - ); - }); - it("burn() from buyer should work", async () => { - const balBefore = await meToken.balanceOf(account2.address); - const balDaiBefore = await dai.balanceOf(account2.address); - await foundry - .connect(account2) - .burn(meToken.address, balBefore, account2.address); - const balAfter = await meToken.balanceOf(account2.address); - const balDaiAfter = await dai.balanceOf(account2.address); - expect(balAfter).equal(0); - expect(await meToken.totalSupply()).to.equal(0); - - const calculatedCollateralReturned = amount - .mul(initRefundRatio) - .div(MAX_WEIGHT); - - expect(balDaiAfter.sub(balDaiBefore)).equal(calculatedCollateralReturned); - }); - describe("during migration", () => { - before(async () => { - // migrate hub - // refund ratio stays the same - const targetRefundRatio = 200000; - const newCurve = await deploy("BancorABDK"); + it("mint() from buyer should work", async () => { + // metoken should be registered + expect(await meToken.name()).to.equal(name); + expect(await meToken.symbol()).to.equal(symbol); + expect(await meToken.decimals()).to.equal(18); + expect(await meToken.totalSupply()).to.equal(0); - await curveRegistry.approve(newCurve.address); - // for 1 DAI we get 1 metokens - const baseY = PRECISION.toString(); - // weight at 10% quadratic curve - const reserveWeight = BigNumber.from(MAX_WEIGHT).div(4).toString(); + const balBefore = await dai.balanceOf(account0.address); + const tokenBalBefore = await meToken.balanceOf(account2.address); + const meTokenDetails = await meTokenRegistry.getDetails(meToken.address); + // gas savings + const totalSupply = await meToken.totalSupply(); - const encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint32"], - [baseY, reserveWeight] - ); - const migrationFactory = await deploy( - "UniswapSingleTransferMigration", - undefined, //no libs - account1.address, // DAO - foundry.address, // foundry - hub.address, // hub - meTokenRegistry.address, //IMeTokenRegistry - migrationRegistry.address //IMigrationRegistry - ); - const block = await ethers.provider.getBlock("latest"); - // earliestSwapTime 10 hour - const earliestSwapTime = block.timestamp + 600 * 60; - const encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( - ["uint256"], - [earliestSwapTime] - ); - // 10 hour - await hub.setDuration(600 * 60); - await hub.setWarmup(60 * 60); - await hub.setCooldown(60 * 60); - // vault stays the same - await hub.initUpdate( + // mint + const meTokensMinted = await _curve.viewMeTokensMinted( + amount, hubId, - newCurve.address, - targetRefundRatio, - encodedCurveDetails + totalSupply, + meTokenDetails.balancePooled ); - }); - it("mint() Should work the same right after the migration ", async () => { - // metoken should be registered - let hubDetail = await hub.getDetails(hubId); - expect(hubDetail.reconfigure).to.be.false; - expect(hubDetail.updating).to.be.true; - - const amount = ethers.utils.parseEther("100"); - const balTokenBefore = await meToken.balanceOf(account2.address); - await dai.connect(tokenHolder).transfer(account2.address, amount); - const balBefore = await dai.balanceOf(account2.address); - const balVaultBefore = await dai.balanceOf(hubDetail.vault); - const totSupplyBefore = await meToken.totalSupply(); - const tokenBalBefore = await meToken.balanceOf(account2.address); + await foundry.mint(meToken.address, amount, account2.address); - await foundry - .connect(account2) - .mint(meToken.address, amount, account2.address); - const balAfter = await dai.balanceOf(account2.address); - const balTokenAfter = await meToken.balanceOf(account2.address); - expect(balTokenAfter).to.be.gt(balTokenBefore); + const tokenBalAfter = await meToken.balanceOf(account2.address); + const balAfter = await dai.balanceOf(account0.address); expect(balBefore.sub(balAfter)).equal(amount); + expect(tokenBalAfter.sub(tokenBalBefore)).equal(meTokensMinted); - hubDetail = await hub.getDetails(hubId); - const balVaultAfter = await dai.balanceOf(hubDetail.vault); - expect(balVaultAfter.sub(balVaultBefore)).equal(amount); + const hubDetail = await hub.getDetails(hubId); + const balVault = await dai.balanceOf(hubDetail.vault); + expect(balVault).equal(amount); // assert token infos const meTokenAddr = await meTokenRegistry.getOwnerMeToken( - account2.address + account0.address ); expect(meTokenAddr).to.equal(meToken.address); // should be greater than 0 - const totSupplyAfter = await meToken.totalSupply(); - const tokenBalAfter = await meToken.balanceOf(account2.address); - expect(tokenBalAfter).to.be.gt(tokenBalBefore); - expect(totSupplyAfter.sub(totSupplyBefore)).to.equal( - tokenBalAfter.sub(tokenBalBefore) + expect(await meToken.totalSupply()).to.equal( + totalSupply.add(meTokensMinted) ); }); - it("burn() Should work", async () => { + it("burn() from buyer should work", async () => { const balBefore = await meToken.balanceOf(account2.address); const balDaiBefore = await dai.balanceOf(account2.address); - - const hubDetail = await hub.getDetails(hubId); - const balVaultBefore = await dai.balanceOf(hubDetail.vault); await foundry .connect(account2) .burn(meToken.address, balBefore, account2.address); @@ -422,41 +505,157 @@ describe("Foundry.sol", () => { const balDaiAfter = await dai.balanceOf(account2.address); expect(balAfter).equal(0); expect(await meToken.totalSupply()).to.equal(0); - expect(balDaiAfter).to.be.gt(balDaiBefore); - const balVaultAfter = await dai.balanceOf(hubDetail.vault); - expect(balVaultBefore.sub(balVaultAfter)).equal( - balDaiAfter.sub(balDaiBefore) - ); - }); - it("mint() Should work after some time during the migration ", async () => { - // metoken should be registered - let block = await ethers.provider.getBlock("latest"); - await mineBlock(block.timestamp + 60 * 60); - - const hubDetail = await hub.getDetails(hubId); - block = await ethers.provider.getBlock("latest"); - expect(hubDetail.startTime).to.be.lt(block.timestamp); - const balVaultBefore = await dai.balanceOf(hubDetail.vault); - const balBefore = await dai.balanceOf(account2.address); + const calculatedCollateralReturned = amount + .mul(initRefundRatio) + .div(MAX_WEIGHT); - await foundry - .connect(account2) - .mint(meToken.address, amount, account2.address); - const balAfter = await dai.balanceOf(account2.address); - expect(balBefore.sub(balAfter)).equal(amount); + expect(balDaiAfter.sub(balDaiBefore)).equal(calculatedCollateralReturned); + }); - const balVaultAfter = await dai.balanceOf(hubDetail.vault); - expect(balVaultAfter).equal(balVaultBefore.add(amount)); - // assert token infos - const meTokenAddr = await meTokenRegistry.getOwnerMeToken( - account2.address - ); - expect(meTokenAddr).to.equal(meToken.address); - // should be greater than 0 - expect(await meToken.totalSupply()).to.equal( - await meToken.balanceOf(account2.address) - ); + describe("during migration", () => { + before(async () => { + // migrate hub + // refund ratio stays the same + const targetRefundRatio = 200000; + + const newCurve = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); + + await curveRegistry.approve(newCurve.address); + // for 1 DAI we get 1 metokens + const baseY = PRECISION.toString(); + // weight at 10% quadratic curve + const reserveWeight = BigNumber.from(MAX_WEIGHT).div(4).toString(); + + const encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint32"], + [baseY, reserveWeight] + ); + const migrationFactory = await deploy( + "UniswapSingleTransferMigration", + undefined, //no libs + account1.address, // DAO + foundry.address, // foundry + hub.address, // hub + meTokenRegistry.address, //IMeTokenRegistry + migrationRegistry.address //IMigrationRegistry + ); + const block = await ethers.provider.getBlock("latest"); + // earliestSwapTime 10 hour + const earliestSwapTime = block.timestamp + 600 * 60; + const encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256"], + [earliestSwapTime] + ); + // 10 hour + await hub.setDuration(600 * 60); + await hub.setWarmup(60 * 60); + await hub.setCooldown(60 * 60); + // vault stays the same + await hub.initUpdate( + hubId, + newCurve.address, + targetRefundRatio, + encodedCurveDetails + ); + }); + it("mint() Should work the same right after the migration ", async () => { + // metoken should be registered + let hubDetail = await hub.getDetails(hubId); + expect(hubDetail.reconfigure).to.be.false; + expect(hubDetail.updating).to.be.true; + + const amount = ethers.utils.parseEther("100"); + const balTokenBefore = await meToken.balanceOf(account2.address); + await dai.connect(tokenHolder).transfer(account2.address, amount); + const balBefore = await dai.balanceOf(account2.address); + const balVaultBefore = await dai.balanceOf(hubDetail.vault); + const totSupplyBefore = await meToken.totalSupply(); + const tokenBalBefore = await meToken.balanceOf(account2.address); + + await foundry + .connect(account2) + .mint(meToken.address, amount, account2.address); + const balAfter = await dai.balanceOf(account2.address); + const balTokenAfter = await meToken.balanceOf(account2.address); + expect(balTokenAfter).to.be.gt(balTokenBefore); + expect(balBefore.sub(balAfter)).equal(amount); + + hubDetail = await hub.getDetails(hubId); + const balVaultAfter = await dai.balanceOf(hubDetail.vault); + expect(balVaultAfter.sub(balVaultBefore)).equal(amount); + + // assert token infos + const meTokenAddr = await meTokenRegistry.getOwnerMeToken( + account2.address + ); + expect(meTokenAddr).to.equal(meToken.address); + // should be greater than 0 + const totSupplyAfter = await meToken.totalSupply(); + const tokenBalAfter = await meToken.balanceOf(account2.address); + expect(tokenBalAfter).to.be.gt(tokenBalBefore); + expect(totSupplyAfter.sub(totSupplyBefore)).to.equal( + tokenBalAfter.sub(tokenBalBefore) + ); + }); + it("burn() Should work", async () => { + const balBefore = await meToken.balanceOf(account2.address); + const balDaiBefore = await dai.balanceOf(account2.address); + + const hubDetail = await hub.getDetails(hubId); + const balVaultBefore = await dai.balanceOf(hubDetail.vault); + await foundry + .connect(account2) + .burn(meToken.address, balBefore, account2.address); + const balAfter = await meToken.balanceOf(account2.address); + const balDaiAfter = await dai.balanceOf(account2.address); + expect(balAfter).equal(0); + expect(await meToken.totalSupply()).to.equal(0); + expect(balDaiAfter).to.be.gt(balDaiBefore); + + const balVaultAfter = await dai.balanceOf(hubDetail.vault); + expect(balVaultBefore.sub(balVaultAfter)).equal( + balDaiAfter.sub(balDaiBefore) + ); + }); + it("mint() Should work after some time during the migration ", async () => { + // metoken should be registered + let block = await ethers.provider.getBlock("latest"); + await mineBlock(block.timestamp + 60 * 60); + + const hubDetail = await hub.getDetails(hubId); + block = await ethers.provider.getBlock("latest"); + expect(hubDetail.startTime).to.be.lt(block.timestamp); + const balVaultBefore = await dai.balanceOf(hubDetail.vault); + const balBefore = await dai.balanceOf(account2.address); + + await foundry + .connect(account2) + .mint(meToken.address, amount, account2.address); + const balAfter = await dai.balanceOf(account2.address); + expect(balBefore.sub(balAfter)).equal(amount); + + const balVaultAfter = await dai.balanceOf(hubDetail.vault); + expect(balVaultAfter).equal(balVaultBefore.add(amount)); + // assert token infos + const meTokenAddr = await meTokenRegistry.getOwnerMeToken( + account2.address + ); + expect(meTokenAddr).to.equal(meToken.address); + // should be greater than 0 + expect(await meToken.totalSupply()).to.equal( + await meToken.balanceOf(account2.address) + ); + }); }); }); +}; + +setup().then(() => { + run(); }); diff --git a/test/contracts/Hub.ts b/test/contracts/Hub.ts index c4d9f279..ee6ad51e 100644 --- a/test/contracts/Hub.ts +++ b/test/contracts/Hub.ts @@ -14,6 +14,7 @@ import { ERC20 } from "../../artifacts/types/ERC20"; import { Signer } from "@ethersproject/abstract-signer"; import { MeTokenRegistry } from "../../artifacts/types/MeTokenRegistry"; import { MeToken } from "../../artifacts/types/MeToken"; +import { WeightedAverage } from "../../artifacts/types/WeightedAverage"; /* const paginationFactory = await ethers.getContractFactory("Pagination", {}); @@ -75,12 +76,21 @@ const setup = async () => { ["uint256", "uint32"], [baseY, reserveWeight] ); - curve = await deploy("BancorABDK"); + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + curve = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); + ({ token, tokenHolder, - hub, - foundry, account0, account1, account2, @@ -88,7 +98,7 @@ const setup = async () => { vaultRegistry, curveRegistry, singleAssetVault, - } = await hubSetupWithoutRegister(curve)); + } = await hubSetupWithoutRegister(hub, foundry, curve)); }); describe("Initial state", () => { @@ -99,9 +109,9 @@ const setup = async () => { expect(await hub.curveRegistry()).to.be.equal(curveRegistry.address); expect(await hub.owner()).to.be.equal(account0.address); expect(await hub.count()).to.be.equal(0); - expect(await hub.getWarmup()).to.be.equal(0); - expect(await hub.getDuration()).to.be.equal(0); - expect(await hub.getCooldown()).to.be.equal(0); + expect(await hub.warmup()).to.be.equal(0); + expect(await hub.duration()).to.be.equal(0); + expect(await hub.cooldown()).to.be.equal(0); const details = await hub.getDetails(0); expect(details.active).to.be.equal(false); expect(details.owner).to.be.equal(ethers.constants.AddressZero); @@ -265,14 +275,14 @@ const setup = async () => { await expect(tx).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should revert to setWarmup if same as before", async () => { - const oldWarmup = await hub.getWarmup(); + const oldWarmup = await hub.warmup(); const tx = hub.setWarmup(oldWarmup); await expect(tx).to.be.revertedWith("warmup_ == _warmup"); }); it("should be able to setWarmup", async () => { const tx = await hub.setWarmup(duration); await tx.wait(); - expect(await hub.getWarmup()).to.be.equal(duration); + expect(await hub.warmup()).to.be.equal(duration); }); }); @@ -282,14 +292,14 @@ const setup = async () => { await expect(tx).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should revert to setDuration if same as before", async () => { - const oldWarmup = await hub.getDuration(); + const oldWarmup = await hub.duration(); const tx = hub.setDuration(oldWarmup); await expect(tx).to.be.revertedWith("duration_ == _duration"); }); it("should be able to setDuration", async () => { const tx = await hub.setDuration(duration); await tx.wait(); - expect(await hub.getDuration()).to.be.equal(duration); + expect(await hub.duration()).to.be.equal(duration); }); }); @@ -299,14 +309,14 @@ const setup = async () => { await expect(tx).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should revert to setCooldown if same as before", async () => { - const oldWarmup = await hub.getCooldown(); + const oldWarmup = await hub.cooldown(); const tx = hub.setCooldown(oldWarmup); await expect(tx).to.be.revertedWith("cooldown_ == _cooldown"); }); it("should be able to setCooldown", async () => { const tx = await hub.setCooldown(duration); await tx.wait(); - expect(await hub.getCooldown()).to.be.equal(duration); + expect(await hub.cooldown()).to.be.equal(duration); }); }); @@ -371,7 +381,12 @@ const setup = async () => { ["uint256", "uint32"], [0, 0] ); - const newCurve = await deploy("BancorABDK"); + const newCurve = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); await curveRegistry.approve(newCurve.address); const tx = hub.initUpdate( hubId, @@ -383,7 +398,12 @@ const setup = async () => { }); it("should be able to initUpdate with new refundRatio", async () => { - newCurve = await deploy("BancorABDK"); + newCurve = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); await curveRegistry.approve(newCurve.address); const tx = await hub.initUpdate( hubId, diff --git a/test/contracts/MeTokenFactory.ts b/test/contracts/MeTokenFactory.ts new file mode 100644 index 00000000..0f8127a4 --- /dev/null +++ b/test/contracts/MeTokenFactory.ts @@ -0,0 +1,129 @@ +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { expect } from "chai"; +import { BigNumber } from "ethers"; +import { ethers, getNamedAccounts } from "hardhat"; +import { BancorABDK } from "../../artifacts/types/BancorABDK"; +import { Foundry } from "../../artifacts/types/Foundry"; +import { Hub } from "../../artifacts/types/Hub"; +import { MeToken } from "../../artifacts/types/MeToken"; +import { MeTokenFactory } from "../../artifacts/types/MeTokenFactory"; +import { MeTokenRegistry } from "../../artifacts/types/MeTokenRegistry"; +import { WeightedAverage } from "../../artifacts/types/WeightedAverage"; +import { mineBlock, setAutomine } from "../utils/hardhatNode"; +import { deploy, getContractAt } from "../utils/helpers"; +import { hubSetup } from "../utils/hubSetup"; + +const setup = async () => { + let bancorABDK: BancorABDK; + let meTokenFactory: MeTokenFactory; + let meTokenRegistry: MeTokenRegistry; + let foundry: Foundry; + let account0: SignerWithAddress; + let account1: SignerWithAddress; + + const hubId = 1; + const PRECISION = BigNumber.from(10).pow(18); + const MAX_WEIGHT = 1000000; + const baseY = PRECISION.div(1000); + const refundRatio = 5000; + const reserveWeight = MAX_WEIGHT / 10; + + describe("MeTokenFactory", async () => { + before(async () => { + const { DAI } = await getNamedAccounts(); + const encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint32"], + [baseY, reserveWeight] + ); + const encodedVaultArgs = ethers.utils.defaultAbiCoder.encode( + ["address"], + [DAI] + ); + + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + const hub = await deploy("Hub"); + bancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); + + ({ meTokenFactory, meTokenRegistry, account0, account1 } = await hubSetup( + encodedCurveDetails, + encodedVaultArgs, + refundRatio, + hub, + foundry, + bancorABDK + )); + }); + it("create() with same params always produce different MeTokens", async () => { + const name = "ABCD"; + const symbol = "AD"; + + const expectedAddress1 = await meTokenFactory.callStatic.create( + name, + symbol, + foundry.address, + meTokenRegistry.address + ); + const tx1 = await meTokenFactory.create( + name, + symbol, + foundry.address, + meTokenRegistry.address + ); + await tx1.wait(); + + const expectedAddress2 = await meTokenFactory.callStatic.create( + name, + symbol, + foundry.address, + meTokenRegistry.address + ); + const tx2 = await meTokenFactory.create( + name, + symbol, + foundry.address, + meTokenRegistry.address + ); + await tx2.wait(); + + // check both the expected address are unique + expect(expectedAddress1).to.not.equal(expectedAddress2); + + // check both expected address are correct, by calling any function from it + expect( + await (await getContractAt("MeToken", expectedAddress1)).name() + ).to.equal(name); + expect( + await (await getContractAt("MeToken", expectedAddress2)).name() + ).to.equal(name); + }); + it("create() with same params and timestamp always produce different MeTokens", async () => { + const block = await ethers.provider.getBlock("latest"); + const name = "ABCD"; + const symbol = "AD"; + await setAutomine(false); + await meTokenRegistry.subscribe(name, symbol, hubId, 0); + await meTokenRegistry.connect(account1).subscribe(name, symbol, hubId, 0); + + await mineBlock(block.timestamp + 1); + await setAutomine(true); + + const a0MeToken = await meTokenRegistry.getOwnerMeToken(account0.address); + const a1MeToken = await meTokenRegistry.getOwnerMeToken(account1.address); + + // check both the expected address are unique + expect(a0MeToken).to.not.equal(a1MeToken); + }); + }); +}; + +setup().then(() => { + run(); +}); diff --git a/test/contracts/curves/BancorBancorCurve.ts b/test/contracts/curves/BancorBancorCurve.ts index 9b6d5ad8..86047a41 100644 --- a/test/contracts/curves/BancorBancorCurve.ts +++ b/test/contracts/curves/BancorBancorCurve.ts @@ -47,11 +47,25 @@ describe("BancorPowerCurve", () => { [DAI] ); let token; - bancorPower = await deploy("BancorPower"); + + const weightedAverage = await deploy("WeightedAverage"); + const foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + const hub = await deploy("Hub"); + bancorPower = await deploy( + "BancorPower", + undefined, + hub.address, + foundry.address + ); + ({ token } = await hubSetup( encodedCurveDetails, encodedVaultArgs, 5000, + hub, + foundry, bancorPower )); dai = token; @@ -351,11 +365,25 @@ describe("BancorPowerCurve", () => { ["address"], [DAI] ); - newbancorPower = await deploy("BancorPower"); + const weightedAverage = await deploy("WeightedAverage"); + const foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + const hub = await deploy("Hub"); + + newbancorPower = await deploy( + "BancorPower", + undefined, + hub.address, + foundry.address + ); + ({ token } = await hubSetup( newEncodedCurveDetails, encodedVaultArgs, 5000, + hub, + foundry, newbancorPower )); dai = token; diff --git a/test/contracts/curves/BancorZeroCurve.ts b/test/contracts/curves/BancorZeroCurve.ts index ea2ef71c..74d64f98 100644 --- a/test/contracts/curves/BancorZeroCurve.ts +++ b/test/contracts/curves/BancorZeroCurve.ts @@ -45,13 +45,26 @@ describe("BancorABDK", () => { ["address"], [DAI] ); - bancorABDK = await deploy("BancorABDK"); + let token; + const weightedAverage = await deploy("WeightedAverage"); + const foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + const hub = await deploy("Hub"); + bancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); ({ token } = await hubSetup( encodedCurveDetails, encodedVaultArgs, 5000, + hub, + foundry, bancorABDK )); }); @@ -349,12 +362,26 @@ describe("BancorABDK", () => { ["address"], [DAI] ); - newBancorABDK = await deploy("BancorABDK"); + + const weightedAverage = await deploy("WeightedAverage"); + const foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + const hub = await deploy("Hub"); + + newBancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); ({ token } = await hubSetup( newEncodedCurveDetails, encodedVaultArgs, 5000, + hub, + foundry, newBancorABDK )); }); diff --git a/test/contracts/curves/Curve.ts b/test/contracts/curves/Curve.ts index e496c926..5065e6d1 100644 --- a/test/contracts/curves/Curve.ts +++ b/test/contracts/curves/Curve.ts @@ -12,6 +12,7 @@ import { MeToken } from "../../../artifacts/types/MeToken"; import { expect } from "chai"; import { hubSetup } from "../../utils/hubSetup"; import { BancorABDK } from "../../../artifacts/types/BancorABDK"; +import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; describe("Generic Curve", () => { let DAI: string; @@ -56,19 +57,34 @@ describe("Generic Curve", () => { ["uint256", "uint32"], [baseY, reserveWeight] ); - _curve = await deploy("BancorABDK"); + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + _curve = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); ({ token, tokenHolder, - hub, - foundry, account0, account1, account2, meTokenRegistry, singleAssetVault, - } = await hubSetup(encodedCurveDetails, encodedVaultArgs, 5000, _curve)); + } = await hubSetup( + encodedCurveDetails, + encodedVaultArgs, + 5000, + hub, + foundry, + _curve + )); // Prefund owner/buyer w/ DAI dai = token; diff --git a/test/contracts/curves/allCurves.ts b/test/contracts/curves/allCurves.ts index 9da3697f..10653cee 100644 --- a/test/contracts/curves/allCurves.ts +++ b/test/contracts/curves/allCurves.ts @@ -52,8 +52,25 @@ const setup = async () => { ["address"], [DAI] ); - const bancorABDK = await deploy("BancorABDK"); - const bancorPower = await deploy("BancorPower"); + + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + const bancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); + + const bancorPower = await deploy( + "BancorPower", + undefined, + hub.address, + foundry.address + ); // Setting up curve info to test @@ -108,17 +125,22 @@ const setup = async () => { // Create hub and register first hub ({ token, - hub, curveRegistry, tokenAddr, migrationRegistry, vaultRegistry, - foundry, account0, account1, account2, meTokenRegistry, - } = await hubSetup(encodedCurveDetails1, encodedVaultArgs, 5000, bancorABDK)); + } = await hubSetup( + encodedCurveDetails1, + encodedVaultArgs, + 5000, + hub, + foundry, + bancorABDK + )); let hubArgs: [ Hub, diff --git a/test/contracts/migrations/UniswapSingleTransfer.ts b/test/contracts/migrations/UniswapSingleTransfer.ts index b9e99024..a7adb9d4 100644 --- a/test/contracts/migrations/UniswapSingleTransfer.ts +++ b/test/contracts/migrations/UniswapSingleTransfer.ts @@ -18,6 +18,7 @@ import { hubSetup } from "../../utils/hubSetup"; import { expect } from "chai"; import { Fees } from "../../../artifacts/types/Fees"; import { VaultRegistry } from "../../../artifacts/types/VaultRegistry"; +import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; const setup = async () => { describe("UniswapSingleTransferMigration.sol", () => { @@ -96,13 +97,21 @@ const setup = async () => { ["uint256", "uint24"], [earliestSwapTime, fees] ); + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + curve = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); - curve = await deploy("BancorABDK"); ({ - hub, migrationRegistry, singleAssetVault: initialVault, - foundry, account0, account1, account2, @@ -113,6 +122,8 @@ const setup = async () => { encodedCurveDetails, encodedVaultDAIArgs, refundRatio, + hub, + foundry, curve )); diff --git a/test/contracts/registries/MeTokenRegistry.ts b/test/contracts/registries/MeTokenRegistry.ts index fdad9d52..af19ef3f 100644 --- a/test/contracts/registries/MeTokenRegistry.ts +++ b/test/contracts/registries/MeTokenRegistry.ts @@ -121,18 +121,26 @@ const setup = async () => { ["address"], [DAI] ); - bancorABDK = await deploy("BancorABDK"); + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + bancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); + ({ tokenAddr: DAI, - weightedAverage, meTokenRegistry, meTokenFactory, curveRegistry, vaultRegistry, migrationRegistry, singleAssetVault, - foundry, - hub, token, fee, account0, @@ -145,6 +153,8 @@ const setup = async () => { encodedCurveDetails, encodedVaultArgs, refundRatio, + hub, + foundry, bancorABDK )); @@ -374,7 +384,7 @@ const setup = async () => { await expect(tx).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should revert to setWarmup if same as before", async () => { - const oldWarmup = await meTokenRegistry.getWarmup(); + const oldWarmup = await meTokenRegistry.warmup(); const tx = meTokenRegistry.setWarmup(oldWarmup); await expect(tx).to.be.revertedWith("warmup_ == _warmup"); }); @@ -385,7 +395,7 @@ const setup = async () => { it("should be able to setWarmup", async () => { tx = await meTokenRegistry.setWarmup(warmup); await tx.wait(); - expect(await meTokenRegistry.getWarmup()).to.be.equal(warmup); + expect(await meTokenRegistry.warmup()).to.be.equal(warmup); }); }); @@ -395,7 +405,7 @@ const setup = async () => { await expect(tx).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should revert to setDuration if same as before", async () => { - const oldWarmup = await meTokenRegistry.getDuration(); + const oldWarmup = await meTokenRegistry.duration(); const tx = meTokenRegistry.setDuration(oldWarmup); await expect(tx).to.be.revertedWith("duration_ == _duration"); }); @@ -406,7 +416,7 @@ const setup = async () => { it("should be able to setDuration", async () => { tx = await meTokenRegistry.setDuration(duration); await tx.wait(); - expect(await meTokenRegistry.getDuration()).to.be.equal(duration); + expect(await meTokenRegistry.duration()).to.be.equal(duration); }); }); @@ -416,14 +426,14 @@ const setup = async () => { await expect(tx).to.be.revertedWith("Ownable: caller is not the owner"); }); it("should revert to setCooldown if same as before", async () => { - const oldWarmup = await meTokenRegistry.getCooldown(); + const oldWarmup = await meTokenRegistry.cooldown(); const tx = meTokenRegistry.setCooldown(oldWarmup); await expect(tx).to.be.revertedWith("cooldown_ == _cooldown"); }); it("should be able to setCooldown", async () => { tx = await meTokenRegistry.setCooldown(coolDown); await tx.wait(); - expect(await meTokenRegistry.getCooldown()).to.be.equal(coolDown); + expect(await meTokenRegistry.cooldown()).to.be.equal(coolDown); }); }); diff --git a/test/contracts/vaults/Vault.ts b/test/contracts/vaults/Vault.ts index cab41b66..67bc305f 100644 --- a/test/contracts/vaults/Vault.ts +++ b/test/contracts/vaults/Vault.ts @@ -10,152 +10,282 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { hubSetup } from "../../utils/hubSetup"; import { BancorABDK } from "../../../artifacts/types/BancorABDK"; import { ERC20 } from "../../../artifacts/types/ERC20"; -import { BigNumber, Signer } from "ethers"; +import { BigNumber, ContractTransaction, Signer } from "ethers"; import { MeToken } from "../../../artifacts/types/MeToken"; import { Fees } from "../../../artifacts/types/Fees"; +import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; -describe("Vault.sol", () => { - let token: ERC20; - let vault: SingleAssetVault; - let DAI: string; - let account0: SignerWithAddress; - let account1: SignerWithAddress; - let account2: SignerWithAddress; - let dao: SignerWithAddress; - let migrationRegistry: MigrationRegistry; - let foundry: Foundry; - let hub: Hub; - let meTokenRegistry: MeTokenRegistry; - let curve: BancorABDK; - let tokenHolder: Signer; - let meToken: MeToken; - let fees: Fees; - let accruedFee: BigNumber; - - const amount = ethers.utils.parseEther("100"); - const precision = ethers.utils.parseUnits("1"); - const initRefundRatio = 50000; - const MAX_WEIGHT = 1000000; - const reserveWeight = MAX_WEIGHT / 2; - const baseY = precision.div(1000); - const hubId = 1; - before(async () => { - ({ DAI } = await getNamedAccounts()); - - [account0, account1, account2] = await ethers.getSigners(); - dao = account0; - - const encodedVaultArgs = ethers.utils.defaultAbiCoder.encode( - ["address"], - [DAI] - ); - const encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint32"], - [baseY, reserveWeight] +const setup = async () => { + describe("Vault.sol", () => { + let token: ERC20; + let vault: SingleAssetVault; + let DAI: string; + let account0: SignerWithAddress; + let account1: SignerWithAddress; + let account2: SignerWithAddress; + let dao: SignerWithAddress; + let migrationRegistry: MigrationRegistry; + let foundry: Foundry; + let hub: Hub; + let meTokenRegistry: MeTokenRegistry; + let curve: BancorABDK; + let tokenHolder: Signer; + let meToken: MeToken; + let fees: Fees; + let accruedFee: BigNumber; + let tx: ContractTransaction; + + const precision = ethers.utils.parseUnits("1"); + const initRefundRatio = 50000; + const MAX_WEIGHT = 1000000; + const reserveWeight = MAX_WEIGHT / 2; + const baseY = precision.div(1000); + const hubId = 1; + const tokenDepositedInETH = 10; + const tokenDeposited = ethers.utils.parseEther( + tokenDepositedInETH.toString() ); - curve = await deploy("BancorABDK"); - ({ - token, - tokenHolder, - hub, - foundry, - account0, - account1, - account2, - meTokenRegistry, - migrationRegistry, - singleAssetVault: vault, - fee: fees, - } = await hubSetup( - encodedCurveDetails, - encodedVaultArgs, - initRefundRatio, - curve - )); - - await fees.setMintFee(1e8); - - await token.connect(tokenHolder).transfer(account0.address, amount.mul(3)); - await token.connect(tokenHolder).transfer(account1.address, amount); - await token.connect(tokenHolder).transfer(account2.address, amount); - - await token.approve(meTokenRegistry.address, amount); - await token.approve(vault.address, amount); - const tx = await meTokenRegistry.subscribe("METOKEN", "MT", hubId, amount); - await tx.wait(); - - const meTokenAddr = await meTokenRegistry.getOwnerMeToken(account0.address); - meToken = await getContractAt("MeToken", meTokenAddr); - }); + before(async () => { + ({ DAI } = await getNamedAccounts()); + + [account0, account1, account2] = await ethers.getSigners(); + dao = account0; - describe("Check initial state", () => { - it("check initial state", async () => { - expect(await vault.owner()).to.be.equal(account0.address); - expect(await vault.PRECISION()).to.be.equal(precision); - expect(await vault.dao()).to.be.equal(dao.address); - expect(await vault.foundry()).to.be.equal(foundry.address); - expect(await vault.hub()).to.be.equal(hub.address); - expect(await vault.meTokenRegistry()).to.be.equal( - meTokenRegistry.address + const encodedVaultArgs = ethers.utils.defaultAbiCoder.encode( + ["address"], + [DAI] ); - expect(await vault.migrationRegistry()).to.be.equal( - migrationRegistry.address + const encodedCurveDetails = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint32"], + [baseY, reserveWeight] + ); + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + curve = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address ); - expect(await vault.accruedFees(DAI)).to.be.equal(0); - }); - }); - describe("claim()", () => { - it("Reverts when not called by owner", async () => { - await expect( - vault.connect(account1).claim(DAI, true, 0) - ).to.be.revertedWith("!DAO"); + ({ + token, + tokenHolder, + account0, + account1, + account2, + meTokenRegistry, + migrationRegistry, + singleAssetVault: vault, + fee: fees, + } = await hubSetup( + encodedCurveDetails, + encodedVaultArgs, + initRefundRatio, + hub, + foundry, + curve + )); + + await fees.setMintFee(1e8); + await fees.setBurnOwnerFee(1e8); + + await token + .connect(tokenHolder) + .transfer(account0.address, tokenDeposited.mul(3)); + await token + .connect(tokenHolder) + .transfer(account1.address, tokenDeposited); + await token + .connect(tokenHolder) + .transfer(account2.address, tokenDeposited); + + await token.approve(meTokenRegistry.address, ethers.constants.MaxUint256); + await token.approve(vault.address, ethers.constants.MaxUint256); + const tx = await meTokenRegistry.subscribe("METOKEN", "MT", hubId, 0); + await tx.wait(); + + const meTokenAddr = await meTokenRegistry.getOwnerMeToken( + account0.address + ); + meToken = await getContractAt("MeToken", meTokenAddr); }); - it("should revert when amount is 0", async () => { - await expect(vault.claim(DAI, false, 0)).to.be.revertedWith("amount < 0"); + describe("Check initial state", () => { + it("check initial state", async () => { + expect(await vault.owner()).to.be.equal(account0.address); + expect(await vault.PRECISION()).to.be.equal(precision); + expect(await vault.dao()).to.be.equal(dao.address); + expect(await vault.foundry()).to.be.equal(foundry.address); + expect(await vault.hub()).to.be.equal(hub.address); + expect(await vault.meTokenRegistry()).to.be.equal( + meTokenRegistry.address + ); + expect(await vault.migrationRegistry()).to.be.equal( + migrationRegistry.address + ); + expect(await vault.accruedFees(DAI)).to.be.equal(0); + }); }); - it("should revert when try to claim more than accruedFees[_asset]", async () => { - await foundry.mint(meToken.address, amount, account1.address); - accruedFee = (await fees.mintFee()).mul(amount).div(precision); + describe("handleDeposit()", () => { + it("Reverts when not called by foundry", async () => { + await expect( + vault.handleDeposit(account0.address, token.address, 1, 1) + ).to.be.revertedWith("!foundry"); + }); + it("Transfer asset from recipient to vault", async () => { + const accountTokenBefore = await token.balanceOf(account0.address); + const vaultTokenBefore = await token.balanceOf(vault.address); + + tx = await foundry.mint( + meToken.address, + tokenDeposited, + account0.address + ); + await tx.wait(); - await expect( - vault.claim(DAI, false, accruedFee.add(1)) - ).to.be.revertedWith("amount > accrued fees"); + const accountTokenAfter = await token.balanceOf(account0.address); + const vaultTokenAfter = await token.balanceOf(vault.address); + + accruedFee = (await fees.mintFee()).mul(tokenDeposited).div(precision); + + expect(accountTokenBefore.sub(accountTokenAfter)).to.equal( + tokenDeposited + ); + expect(vaultTokenAfter.sub(vaultTokenBefore)).to.equal(tokenDeposited); + }); + it("Increments accruedFees", async () => { + expect(await vault.accruedFees(token.address)).to.equal(accruedFee); + }); + it("Emits HandleDeposit()", async () => { + await expect(tx) + .to.emit(vault, "HandleDeposit") + .withArgs( + account0.address, + token.address, + tokenDeposited, + accruedFee + ); + }); }); - it("Transfer some accrued fees", async () => { - const amountToClaim = accruedFee.div(2); - const daoBalanceBefore = await token.balanceOf(dao.address); + describe("handleWithdrawal()", () => { + let burnFee: BigNumber; + it("Reverts when not called by foundry", async () => { + await expect( + vault.handleWithdrawal(account0.address, token.address, 1, 1) + ).to.be.revertedWith("!foundry"); + }); + it("Transfer asset from vault to recipient", async () => { + const accountTokenBefore = await token.balanceOf(account0.address); + const vaultTokenBefore = await token.balanceOf(vault.address); - const tx = await vault.claim(DAI, false, amountToClaim); - await tx.wait(); + tx = await foundry.burn( + meToken.address, + await meToken.totalSupply(), + account0.address + ); + await tx.wait(); - await expect(tx) - .to.emit(vault, "Claim") - .withArgs(dao.address, DAI, amountToClaim); + const accountTokenAfter = await token.balanceOf(account0.address); + const vaultTokenAfter = await token.balanceOf(vault.address); + burnFee = (await fees.burnOwnerFee()) + .mul(tokenDeposited.sub(accruedFee)) + .div(precision); - const daoBalanceAfter = await token.balanceOf(dao.address); - accruedFee = accruedFee.sub(amountToClaim); - expect(await vault.accruedFees(DAI)).to.be.equal(accruedFee); - expect(daoBalanceAfter.sub(daoBalanceBefore)).to.be.equal(amountToClaim); + expect(accountTokenAfter.sub(accountTokenBefore)).to.equal( + tokenDeposited.sub(accruedFee).sub(burnFee) + ); + expect(vaultTokenBefore.sub(vaultTokenAfter)).to.equal( + tokenDeposited.sub(accruedFee).sub(burnFee) + ); + accruedFee = accruedFee.add(burnFee); + }); + it("Increments accruedFees", async () => { + expect(await vault.accruedFees(token.address)).to.equal(accruedFee); + }); + it("Emits HandleWithdrawal()", async () => { + await expect(tx) + .to.emit(vault, "HandleWithdrawal") + .withArgs( + account0.address, + token.address, + tokenDeposited.sub(accruedFee), + burnFee + ); + }); }); - it("Transfer all remaining accrued fees", async () => { - const amountToClaim = accruedFee; - const daoBalanceBefore = await token.balanceOf(dao.address); - const tx = await vault.claim(DAI, true, 0); - await tx.wait(); + describe("claim()", () => { + before(async () => { + const tx = await vault.claim(DAI, true, 0); + await tx.wait(); + }); + it("Reverts when not called by owner", async () => { + await expect( + vault.connect(account1).claim(DAI, true, 0) + ).to.be.revertedWith("!DAO"); + }); + + it("should revert when amount is 0", async () => { + await expect(vault.claim(DAI, false, 0)).to.be.revertedWith( + "amount < 0" + ); + }); + + it("should revert when try to claim more than accruedFees[_asset]", async () => { + await foundry.mint(meToken.address, tokenDeposited, account1.address); + accruedFee = (await fees.mintFee()).mul(tokenDeposited).div(precision); + + await expect( + vault.claim(DAI, false, accruedFee.add(1)) + ).to.be.revertedWith("amount > accrued fees"); + }); - await expect(tx) - .to.emit(vault, "Claim") - .withArgs(dao.address, DAI, amountToClaim); + it("Transfer some accrued fees", async () => { + const amountToClaim = accruedFee.div(2); + const daoBalanceBefore = await token.balanceOf(dao.address); - const daoBalanceAfter = await token.balanceOf(dao.address); - accruedFee = accruedFee.sub(amountToClaim); - expect(await vault.accruedFees(DAI)).to.be.equal(accruedFee); - expect(daoBalanceAfter.sub(daoBalanceBefore)).to.be.equal(amountToClaim); + const tx = await vault.claim(DAI, false, amountToClaim); + await tx.wait(); + + await expect(tx) + .to.emit(vault, "Claim") + .withArgs(dao.address, DAI, amountToClaim); + + const daoBalanceAfter = await token.balanceOf(dao.address); + accruedFee = accruedFee.sub(amountToClaim); + expect(await vault.accruedFees(DAI)).to.be.equal(accruedFee); + expect(daoBalanceAfter.sub(daoBalanceBefore)).to.be.equal( + amountToClaim + ); + }); + + it("Transfer all remaining accrued fees", async () => { + const amountToClaim = accruedFee; + const daoBalanceBefore = await token.balanceOf(dao.address); + const tx = await vault.claim(DAI, true, 0); + await tx.wait(); + + await expect(tx) + .to.emit(vault, "Claim") + .withArgs(dao.address, DAI, amountToClaim); + + const daoBalanceAfter = await token.balanceOf(dao.address); + accruedFee = accruedFee.sub(amountToClaim); + expect(await vault.accruedFees(DAI)).to.be.equal(accruedFee); + expect(daoBalanceAfter.sub(daoBalanceBefore)).to.be.equal( + amountToClaim + ); + }); }); }); +}; + +setup().then(() => { + run(); }); diff --git a/test/integration/Hub/UpdateCurveDetails.ts b/test/integration/Hub/UpdateCurveDetails.ts index ebd56c12..f76bfae3 100644 --- a/test/integration/Hub/UpdateCurveDetails.ts +++ b/test/integration/Hub/UpdateCurveDetails.ts @@ -29,6 +29,7 @@ import { } from "../../utils/hardhatNode"; import { ICurve } from "../../../artifacts/types/ICurve"; import { start } from "repl"; +import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; const setup = async () => { describe("Hub - update CurveDetails", () => { let meTokenRegistry: MeTokenRegistry; @@ -75,15 +76,22 @@ const setup = async () => { ["address"], [DAI] ); - bancorABDK = await deploy("BancorABDK"); - + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + bancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); ({ token, - hub, curveRegistry, singleAssetVault, tokenHolder, - foundry, account0, account1, account2, @@ -93,6 +101,8 @@ const setup = async () => { encodedCurveDetails, encodedVaultArgs, refundRatio, + hub, + foundry, bancorABDK )); dai = token; @@ -130,24 +140,24 @@ const setup = async () => { const vaultBalAfter = await token.balanceOf(singleAssetVault.address); expect(vaultBalAfter.sub(vaultBalBefore)).to.equal(tokenDeposited); //setWarmup for 2 days - let warmup = await hub.getWarmup(); + let warmup = await hub.warmup(); expect(warmup).to.equal(0); await hub.setWarmup(172800); - warmup = await hub.getWarmup(); + warmup = await hub.warmup(); expect(warmup).to.equal(172800); - let cooldown = await hub.getCooldown(); + let cooldown = await hub.cooldown(); expect(cooldown).to.equal(0); //setCooldown for 1 day await hub.setCooldown(86400); - cooldown = await hub.getCooldown(); + cooldown = await hub.cooldown(); expect(cooldown).to.equal(86400); - let duration = await hub.getDuration(); + let duration = await hub.duration(); expect(duration).to.equal(0); //setDuration for 1 week await hub.setDuration(604800); - duration = await hub.getDuration(); + duration = await hub.duration(); expect(duration).to.equal(604800); }); @@ -171,7 +181,14 @@ const setup = async () => { ["uint256", "uint32"], [updatedBaseY, updatedReserveWeight] ); - updatedBancorABDK = await deploy("BancorABDK"); + + updatedBancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); + await curveRegistry.approve(updatedBancorABDK.address); await hub.initUpdate( firstHubId, @@ -289,8 +306,6 @@ const setup = async () => { ); expect(vaultBalAfterMint.sub(vaultBalBefore)).to.equal(tokenDeposited); - const rawAssetsReturnedFromFoundry = - await foundry.calculateRawAssetsReturned(meToken.address, balAfter); const balBefore = await meToken.balanceOf(account0.address); const balDaiBefore = await token.balanceOf(account0.address); const vaultBalBeforeBurn = await token.balanceOf( @@ -371,8 +386,6 @@ const setup = async () => { ); expect(vaultBalAfterMint.sub(vaultBalBefore)).to.equal(tokenDeposited); - const rawAssetsReturnedFromFoundry = - await foundry.calculateRawAssetsReturned(meToken.address, balAfter); const balBefore = await meToken.balanceOf(account0.address); const balDaiBefore = await token.balanceOf(account0.address); const vaultBalBeforeBurn = await token.balanceOf( @@ -457,8 +470,6 @@ const setup = async () => { ); expect(vaultBalAfterMint.sub(vaultBalBefore)).to.equal(tokenDeposited); - const rawAssetsReturnedFromFoundry = - await foundry.calculateRawAssetsReturned(meToken.address, balAfter); const balBefore = await meToken.balanceOf(account0.address); const balDaiBefore = await token.balanceOf(account0.address); const vaultBalBeforeBurn = await token.balanceOf( @@ -536,10 +547,6 @@ const setup = async () => { const vaultBalBefore = await token.balanceOf(singleAssetVault.address); await setAutomine(false); const block = await ethers.provider.getBlock("latest"); - const tokenMinted = await foundry.calculateMeTokensMinted( - meToken.address, - tokenDeposited - ); const balBefore = await meToken.balanceOf(account3.address); @@ -590,11 +597,6 @@ const setup = async () => { singleAssetVault.address ); expect(vaultBalAfterMint.sub(vaultBalBefore)).to.equal(tokenDeposited); - - expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( - toETHNumber(tokenMinted), - 0.00001 - ); expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( calcWAvrgRes, 0.00000000000001 @@ -644,10 +646,6 @@ const setup = async () => { const vaultBalBefore = await token.balanceOf(singleAssetVault.address); const balBefore = await meToken.balanceOf(account0.address); - const tokenMinted = await foundry.calculateMeTokensMinted( - meToken.address, - tokenDeposited - ); let meTokenTotalSupply = await meToken.totalSupply(); let meTokenDetails = await meTokenRegistry.getDetails(meToken.address); // the updated curve should be applied @@ -665,10 +663,7 @@ const setup = async () => { calcTargetTokenReturn, 0.0000000000001 ); - expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( - toETHNumber(tokenMinted), - 0.0000000000001 - ); + const vaultBalAfterMint = await token.balanceOf( singleAssetVault.address ); @@ -677,11 +672,6 @@ const setup = async () => { meTokenTotalSupply = await meToken.totalSupply(); meTokenDetails = await meTokenRegistry.getDetails(meToken.address); const metokenToBurn = balAfter.div(2); - const rawAssetsReturnedFromFoundry = - await foundry.calculateRawAssetsReturned( - meToken.address, - metokenToBurn - ); const { active, refundRatio, @@ -755,10 +745,6 @@ const setup = async () => { const vaultBalBefore = await token.balanceOf(singleAssetVault.address); const balBefore = await meToken.balanceOf(account2.address); - const tokenMinted = await foundry.calculateMeTokensMinted( - meToken.address, - tokenDeposited - ); let meTokenTotalSupply = await meToken.totalSupply(); let meTokenDetails = await meTokenRegistry.getDetails(meToken.address); // the updated curve should be applied @@ -774,10 +760,7 @@ const setup = async () => { .mint(meToken.address, tokenDeposited, account2.address); const balDaiAfterMint = await token.balanceOf(account2.address); const balAfter = await meToken.balanceOf(account2.address); - expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( - toETHNumber(tokenMinted), - 0.0000000000001 - ); + expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( calcTargetTokenReturn, 0.0000000000001 @@ -791,11 +774,6 @@ const setup = async () => { meTokenTotalSupply = await meToken.totalSupply(); meTokenDetails = await meTokenRegistry.getDetails(meToken.address); const metokenToBurn = balAfter.div(2); - const rawAssetsReturnedFromFoundry = - await foundry.calculateRawAssetsReturned( - meToken.address, - metokenToBurn - ); const { active, refundRatio, @@ -1028,8 +1006,6 @@ const setup = async () => { tokenDeposited ); - const rawAssetsReturnedFromFoundry = - await foundry.calculateRawAssetsReturned(meToken.address, balAfter); const balBefore = await meToken.balanceOf(account0.address); const balDaiBefore = await token.balanceOf(account0.address); const vaultBalBeforeBurn = await token.balanceOf( @@ -1112,8 +1088,6 @@ const setup = async () => { tokenDeposited ); - const rawAssetsReturnedFromFoundry = - await foundry.calculateRawAssetsReturned(meToken.address, balAfter); const balBefore = await meToken.balanceOf(account0.address); const balDaiBefore = await token.balanceOf(account0.address); const vaultBalBeforeBurn = await token.balanceOf( @@ -1202,8 +1176,6 @@ const setup = async () => { tokenDeposited ); - const rawAssetsReturnedFromFoundry = - await foundry.calculateRawAssetsReturned(meToken.address, balAfter); const balBefore = await meToken.balanceOf(account0.address); const balDaiBefore = await token.balanceOf(account0.address); const vaultBalBeforeBurn = await token.balanceOf( @@ -1286,10 +1258,6 @@ const setup = async () => { ); await setAutomine(false); const block = await ethers.provider.getBlock("latest"); - const tokenMinted = await foundry.calculateMeTokensMinted( - meToken.address, - tokenDeposited - ); const mrd = await meTokenRegistry.getDetails(meToken.address); const hd = await hub.getDetails(mrd.hubId); let balBefore = await meToken.balanceOf(account3.address); @@ -1340,11 +1308,6 @@ const setup = async () => { expect(vaultBalAfterMint.sub(vaultBalBefore)).to.equal( tokenDeposited ); - // not that precise because block timestamp is a little bit different of when we asked for tokenMinted - expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( - toETHNumber(tokenMinted), - 0.01 - ); expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( calcWAvrgRes, 0.0000000000001 @@ -1386,10 +1349,6 @@ const setup = async () => { ); const balBefore = await meToken.balanceOf(account0.address); - const tokenMinted = await foundry.calculateMeTokensMinted( - meToken.address, - tokenDeposited - ); let meTokenTotalSupply = await meToken.totalSupply(); let meTokenDetails = await meTokenRegistry.getDetails( meToken.address @@ -1409,10 +1368,6 @@ const setup = async () => { calcTargetTokenReturn, 0.0000000000001 ); - expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( - toETHNumber(tokenMinted), - 0.0000000000001 - ); const vaultBalAfterMint = await token.balanceOf( singleAssetVault.address ); @@ -1491,10 +1446,6 @@ const setup = async () => { ); const balBefore = await meToken.balanceOf(account2.address); - const tokenMinted = await foundry.calculateMeTokensMinted( - meToken.address, - tokenDeposited - ); let meTokenTotalSupply = await meToken.totalSupply(); let meTokenDetails = await meTokenRegistry.getDetails( meToken.address @@ -1512,10 +1463,7 @@ const setup = async () => { .mint(meToken.address, tokenDeposited, account2.address); const balDaiAfterMint = await token.balanceOf(account2.address); const balAfter = await meToken.balanceOf(account2.address); - expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( - toETHNumber(tokenMinted), - 0.0000000000001 - ); + expect(toETHNumber(balAfter.sub(balBefore))).to.be.approximately( calcTargetTokenReturn, 0.0000000000001 diff --git a/test/integration/Hub/UpdateRefundRatio.ts b/test/integration/Hub/UpdateRefundRatio.ts index 0d8e1280..4c0539bb 100644 --- a/test/integration/Hub/UpdateRefundRatio.ts +++ b/test/integration/Hub/UpdateRefundRatio.ts @@ -1,5 +1,7 @@ import { ethers, getNamedAccounts } from "hardhat"; import { + calculateCollateralReturned, + calculateTokenReturned, deploy, getContractAt, toETHNumber, @@ -16,7 +18,14 @@ import { hubSetup } from "../../utils/hubSetup"; import { MeToken } from "../../../artifacts/types/MeToken"; import { expect } from "chai"; import { SingleAssetVault } from "../../../artifacts/types/SingleAssetVault"; -import { passDays, passHours, passSeconds } from "../../utils/hardhatNode"; +import { + mineBlock, + passDays, + passHours, + passSeconds, + setAutomine, +} from "../../utils/hardhatNode"; +import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; const setup = async () => { describe("Hub - update RefundRatio", () => { let meTokenRegistry: MeTokenRegistry; @@ -33,6 +42,7 @@ const setup = async () => { const one = ethers.utils.parseEther("1"); let baseY: BigNumber; const MAX_WEIGHT = 1000000; + const reserveWeight = MAX_WEIGHT / 2; let encodedCurveDetails: string; let encodedVaultArgs: string; const firstHubId = 1; @@ -42,7 +52,6 @@ const setup = async () => { // TODO: pre-load contracts // NOTE: hub.register() should have already been called baseY = one.mul(1000); - const reserveWeight = MAX_WEIGHT / 2; let DAI; ({ DAI } = await getNamedAccounts()); @@ -54,14 +63,21 @@ const setup = async () => { ["address"], [DAI] ); - bancorABDK = await deploy("BancorABDK"); - + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + bancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); ({ token, - hub, tokenHolder, singleAssetVault, - foundry, account0, account1, account2, @@ -70,6 +86,8 @@ const setup = async () => { encodedCurveDetails, encodedVaultArgs, firstRefundRatio, + hub, + foundry, bancorABDK )); @@ -103,24 +121,24 @@ const setup = async () => { const vaultBalAfter = await token.balanceOf(singleAssetVault.address); expect(vaultBalAfter.sub(vaultBalBefore)).to.equal(tokenDeposited); //setWarmup for 2 days - let warmup = await hub.getWarmup(); + let warmup = await hub.warmup(); expect(warmup).to.equal(0); await hub.setWarmup(172800); - warmup = await hub.getWarmup(); + warmup = await hub.warmup(); expect(warmup).to.equal(172800); - let cooldown = await hub.getCooldown(); + let cooldown = await hub.cooldown(); expect(cooldown).to.equal(0); //setCooldown for 1 day await hub.setCooldown(86400); - cooldown = await hub.getCooldown(); + cooldown = await hub.cooldown(); expect(cooldown).to.equal(86400); - let duration = await hub.getDuration(); + let duration = await hub.duration(); expect(duration).to.equal(0); //setDuration for 1 week await hub.setDuration(604800); - duration = await hub.getDuration(); + duration = await hub.duration(); expect(duration).to.equal(604800); }); @@ -255,20 +273,21 @@ const setup = async () => { expect(vaultBalAfterMint.sub(vaultBalBefore)).to.equal(tokenDeposited); - const meTotSupply = await meToken.totalSupply(); - - const meDetails = await meTokenRegistry.getDetails(meToken.address); - - const tokensReturned = await foundry.calculateRawAssetsReturned( - meToken.address, - balAfter + const meTokenTotalSupply = await meToken.totalSupply(); + const meTokenDetails = await meTokenRegistry.getDetails( + meToken.address ); - const rewardFromLockedPool = one - .mul(balAfter) - .mul(meDetails.balanceLocked) - .div(meTotSupply) - .div(one); + const rawAssetsReturned = calculateCollateralReturned( + toETHNumber(balAfter), + toETHNumber(meTokenTotalSupply), + toETHNumber(meTokenDetails.balancePooled), + reserveWeight / MAX_WEIGHT + ); + const assetsReturned = + rawAssetsReturned + + (toETHNumber(balAfter) / toETHNumber(meTokenTotalSupply)) * + toETHNumber(meTokenDetails.balanceLocked); await foundry .connect(account0) @@ -281,8 +300,9 @@ const setup = async () => { expect(active).to.be.true; expect(updating).to.be.true; - expect(toETHNumber(balDaiAfter.sub(balDaiBefore))).to.equal( - toETHNumber(tokensReturned.add(rewardFromLockedPool)) + expect(toETHNumber(balDaiAfter.sub(balDaiBefore))).to.be.approximately( + assetsReturned, + 1e-15 ); }); @@ -302,20 +322,24 @@ const setup = async () => { .mint(meToken.address, tokenDeposited, account2.address); const balDaiAfterMint = await token.balanceOf(account2.address); const balAfter = await meToken.balanceOf(account2.address); - const vaultBalAfterMint = await token.balanceOf( singleAssetVault.address ); + const meTokenTotalSupply = await meToken.totalSupply(); + const meTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); + expect(vaultBalAfterMint.sub(vaultBalBefore)).to.equal(tokenDeposited); - const tokensReturned = await foundry.calculateRawAssetsReturned( - meToken.address, - balAfter + await setAutomine(false); + const block = await ethers.provider.getBlock("latest"); + const rawAssetsReturned = calculateCollateralReturned( + toETHNumber(balAfter), + toETHNumber(meTokenTotalSupply), + toETHNumber(meTokenDetails.balancePooled), + reserveWeight / MAX_WEIGHT ); - await foundry - .connect(account2) - .burn(meToken.address, balAfter, account2.address); - const balDaiAfterBurn = await token.balanceOf(account2.address); const { active, @@ -323,27 +347,34 @@ const setup = async () => { updating, startTime, endTime, - endCooldown, - reconfigure, targetRefundRatio, } = await hub.getDetails(1); expect(active).to.be.true; expect(updating).to.be.true; - const block = await ethers.provider.getBlock("latest"); - const calcWAvrgRes = weightedAverageSimulation( + const calcWAvgRes = weightedAverageSimulation( refundRatio.toNumber(), targetRefundRatio.toNumber(), startTime.toNumber(), endTime.toNumber(), - block.timestamp + block.timestamp + 1 ); - const calculatedReturn = tokensReturned - .mul(BigNumber.from(Math.floor(calcWAvrgRes))) - .div(BigNumber.from(10 ** 6)); + const calculatedReturn = + (rawAssetsReturned * Math.floor(calcWAvgRes)) / MAX_WEIGHT; + + await foundry + .connect(account2) + .burn(meToken.address, balAfter, account2.address); + + await mineBlock(block.timestamp + 1); + await setAutomine(true); - // we get the calcWAvrgRes percentage of the tokens returned by the Metokens burn - expect(balDaiAfterBurn.sub(balDaiAfterMint)).to.equal(calculatedReturn); + const balDaiAfterBurn = await token.balanceOf(account2.address); + + // we get the calcWAvgRes percentage of the tokens returned by the Metokens burn + expect( + toETHNumber(balDaiAfterBurn.sub(balDaiAfterMint)) + ).to.approximately(calculatedReturn, 1e-15); }); }); @@ -410,20 +441,21 @@ const setup = async () => { expect(vaultBalAfterMint.sub(vaultBalBefore)).to.equal(tokenDeposited); - const meTotSupply = await meToken.totalSupply(); - - const meDetails = await meTokenRegistry.getDetails(meToken.address); - - const tokensReturned = await foundry.calculateRawAssetsReturned( - meToken.address, - balAfter + const meTokenTotalSupply = await meToken.totalSupply(); + const meTokenDetails = await meTokenRegistry.getDetails( + meToken.address ); - const rewardFromLockedPool = one - .mul(balAfter) - .mul(meDetails.balanceLocked) - .div(meTotSupply) - .div(one); + const rawAssetsReturned = calculateCollateralReturned( + toETHNumber(balAfter), + toETHNumber(meTokenTotalSupply), + toETHNumber(meTokenDetails.balancePooled), + reserveWeight / MAX_WEIGHT + ); + const assetsReturned = + rawAssetsReturned + + (toETHNumber(balAfter) / toETHNumber(meTokenTotalSupply)) * + toETHNumber(meTokenDetails.balanceLocked); await foundry .connect(account0) @@ -439,8 +471,9 @@ const setup = async () => { expect(active).to.be.true; expect(updating).to.be.false; - expect(toETHNumber(balDaiAfter.sub(balDaiBefore))).to.equal( - toETHNumber(tokensReturned.add(rewardFromLockedPool)) + expect(toETHNumber(balDaiAfter.sub(balDaiBefore))).to.be.approximately( + assetsReturned, + 1e-13 ); }); @@ -457,22 +490,32 @@ const setup = async () => { await foundry .connect(account2) .mint(meToken.address, tokenDeposited, account2.address); - const balDaiAfterMint = await token.balanceOf(account2.address); + const balDaiBefore = await token.balanceOf(account2.address); const balAfter = await meToken.balanceOf(account2.address); const vaultBalAfterMint = await token.balanceOf( singleAssetVault.address ); + const meTokenTotalSupply = await meToken.totalSupply(); + const meTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); expect(vaultBalAfterMint.sub(vaultBalBefore)).to.equal(tokenDeposited); - const tokensReturned = await foundry.calculateRawAssetsReturned( - meToken.address, - balAfter + const rawAssetsReturned = calculateCollateralReturned( + toETHNumber(balAfter), + toETHNumber(meTokenTotalSupply), + toETHNumber(meTokenDetails.balancePooled), + reserveWeight / MAX_WEIGHT ); + + const assetsReturned = + (rawAssetsReturned * targetedRefundRatio) / MAX_WEIGHT; + await foundry .connect(account2) .burn(meToken.address, balAfter, account2.address); - const balDaiAfterBurn = await token.balanceOf(account2.address); + const balDaiAfter = await token.balanceOf(account2.address); const { active, @@ -494,12 +537,12 @@ const setup = async () => { expect(block.timestamp).to.be.gt(endTime); expect(block.timestamp).to.be.lt(endCooldown); - const calculatedReturn = tokensReturned - .mul(BigNumber.from(targetedRefundRatio)) - .div(BigNumber.from(10 ** 6)); - // we get the calcWAvrgRes percentage of the tokens returned by the Metokens burn - expect(balDaiAfterBurn.sub(balDaiAfterMint)).to.equal(calculatedReturn); + // we get the calcWAvgRes percentage of the tokens returned by the Metokens burn + expect(toETHNumber(balDaiAfter.sub(balDaiBefore))).to.be.approximately( + assetsReturned, + 1e-15 + ); }); it("Call finishUpdate() and update refundRatio to targetRefundRatio", async () => { @@ -518,13 +561,13 @@ const setup = async () => { await hub.setCooldown(0); await hub.setDuration(0); - let warmup = await hub.getWarmup(); + let warmup = await hub.warmup(); expect(warmup).to.equal(0); - let cooldown = await hub.getCooldown(); + let cooldown = await hub.cooldown(); expect(cooldown).to.equal(0); - let duration = await hub.getDuration(); + let duration = await hub.duration(); expect(duration).to.equal(0); const detBefore = await hub.getDetails(hubId); @@ -604,13 +647,13 @@ const setup = async () => { const hubId = (await hub.count()).toNumber(); expect(hubId).to.be.equal(firstHubId + 2); - let warmup = await hub.getWarmup(); + let warmup = await hub.warmup(); expect(warmup).to.equal(0); - let cooldown = await hub.getCooldown(); + let cooldown = await hub.cooldown(); expect(cooldown).to.equal(0); - let duration = await hub.getDuration(); + let duration = await hub.duration(); expect(duration).to.equal(0); const detBefore = await hub.getDetails(hubId); expect(detBefore.active).to.be.true; diff --git a/test/integration/MeTokenRegistry/ResubscribeCurveDetails.ts b/test/integration/MeTokenRegistry/ResubscribeCurveDetails.ts index 1372b7ee..61f8945c 100644 --- a/test/integration/MeTokenRegistry/ResubscribeCurveDetails.ts +++ b/test/integration/MeTokenRegistry/ResubscribeCurveDetails.ts @@ -8,6 +8,7 @@ import { toETHNumber, weightedAverageSimulation, calculateTokenReturnedFromZero, + fromETHNumber, } from "../../utils/helpers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { BigNumber, ContractTransaction, Signer } from "ethers"; @@ -23,6 +24,9 @@ import { MeToken } from "../../../artifacts/types/MeToken"; import { UniswapSingleTransferMigration } from "../../../artifacts/types/UniswapSingleTransferMigration"; import { SingleAssetVault } from "../../../artifacts/types/SingleAssetVault"; import { mineBlock, setAutomine } from "../../utils/hardhatNode"; +import { Fees } from "../../../artifacts/types/Fees"; +import Decimal from "decimal.js"; +import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; const setup = async () => { describe("MeToken Resubscribe - Same curve, new Curve Details", () => { @@ -43,6 +47,7 @@ const setup = async () => { let account1: SignerWithAddress; let encodedCurveDetails1: string; let encodedCurveDetails2: string; + let fees: Fees; const hubId1 = 1; const hubId2 = 2; @@ -57,11 +62,13 @@ const setup = async () => { const reserveWeight1 = MAX_WEIGHT / 10; const reserveWeight2 = MAX_WEIGHT / 2; const refundRatio = 5000; - const fees = 3000; + const fee = 3000; const tokenDepositedInETH = 100; const tokenDeposited = ethers.utils.parseEther( tokenDepositedInETH.toString() ); + const burnOwnerFee = 1e8; + const burnBuyerFee = 1e9; before(async () => { let token: ERC20; @@ -84,25 +91,37 @@ const setup = async () => { const earliestSwapTime = block.timestamp + 600 * 60; // 10h in future const encodedMigrationArgs = ethers.utils.defaultAbiCoder.encode( ["uint256", "uint24"], - [earliestSwapTime, fees] + [earliestSwapTime, fee] ); // Register first and second hub - bancorABDK = await deploy("BancorABDK"); + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + bancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); + ({ token, - hub, tokenHolder, migrationRegistry, singleAssetVault, - foundry, account0, account1, meTokenRegistry, + fee: fees, } = await hubSetup( encodedCurveDetails1, encodedVaultArgs, refundRatio, + hub, + foundry, bancorABDK )); dai = token; @@ -124,6 +143,8 @@ const setup = async () => { await meTokenRegistry.setWarmup(warmup); await meTokenRegistry.setDuration(duration); await meTokenRegistry.setCooldown(coolDown); + await fees.setBurnOwnerFee(burnOwnerFee); + await fees.setBurnBuyerFee(burnBuyerFee); // Deploy uniswap migration and approve it to the registry migration = await deploy( @@ -211,7 +232,7 @@ const setup = async () => { expect(toETHNumber(ownerMeTokenAfter)).to.be.approximately( calculatedReturn, - 0.000000000000001 + 1e-15 ); expect(meTokenTotalSupplyAfter).to.be.equal(ownerMeTokenAfter); expect(vaultDAIAfter.sub(vaultDAIBefore)).to.equal(tokenDeposited); @@ -246,17 +267,28 @@ const setup = async () => { const vaultDAIAfter = await dai.balanceOf(singleAssetVault.address); const meTokenTotalSupplyAfter = await meToken.totalSupply(); + const burnFee = toETHNumber( + (await fees.burnBuyerFee()) + .mul(fromETHNumber(assetsReturned)) + .div(PRECISION) + ); + expect( toETHNumber(buyerDAIAfter.sub(buyerDAIBefore)) - ).to.be.approximately(assetsReturned, 0.000000000000001); + ).to.be.approximately( + new Decimal(assetsReturned).sub(new Decimal(burnFee)).toNumber(), + 1e-15 + ); expect(buyerMeTokenAfter).to.equal(0); expect(toETHNumber(meTokenTotalSupplyAfter)).to.be.approximately( toETHNumber(meTokenTotalSupply.div(2)), 1e-18 ); - expect(toETHNumber(vaultDAIBefore.sub(vaultDAIAfter))).to.approximately( - assetsReturned, - 0.000000000000001 + expect( + toETHNumber(vaultDAIBefore.sub(vaultDAIAfter)) + ).to.be.approximately( + new Decimal(assetsReturned).sub(new Decimal(burnFee)).toNumber(), + 1e-15 ); }); it("burn() [owner]: assets received based on initial Curve details", async () => { @@ -288,12 +320,21 @@ const setup = async () => { const vaultDAIAfter = await dai.balanceOf(singleAssetVault.address); const meTokenTotalSupplyAfter = await meToken.totalSupply(); + const burnFee = toETHNumber( + (await fees.burnOwnerFee()) + .mul(fromETHNumber(assetsReturned)) + .div(PRECISION) + ); + expect(vaultDAIBefore.sub(vaultDAIAfter)).to.equal( ownerDAIAfter.sub(ownerDAIBefore) ); expect( toETHNumber(ownerDAIAfter.sub(ownerDAIBefore)) - ).to.be.approximately(assetsReturned, 0.000000000000001); + ).to.be.approximately( + new Decimal(assetsReturned).sub(new Decimal(burnFee)).toNumber(), + 1e-15 + ); expect(ownerMeTokenAfter).to.equal(0); expect(toETHNumber(meTokenTotalSupplyAfter)).to.equal(0); }); @@ -354,7 +395,7 @@ const setup = async () => { expect(toETHNumber(ownerMeTokenAfter)).to.be.approximately( calcWAvgRe, - 0.000000000000001 + 1e-15 ); expect(meTokenTotalSupplyAfter).to.be.equal(ownerMeTokenAfter); expect(vaultDAIAfter.sub(vaultDAIBefore)).to.equal(0); // new asset goes to migration @@ -412,9 +453,18 @@ const setup = async () => { const migrationWETHAfter = await weth.balanceOf(migration.address); const meTokenTotalSupplyAfter = await meToken.totalSupply(); + const burnFee = toETHNumber( + (await fees.burnBuyerFee()) + .mul(fromETHNumber(assetsReturned)) + .div(PRECISION) + ); + expect( toETHNumber(buyerWETHAfter.sub(buyerWETHBefore)) - ).to.be.approximately(assetsReturned, 1e-15); + ).to.be.approximately( + new Decimal(assetsReturned).sub(new Decimal(burnFee)).toNumber(), + 1e-15 + ); expect(buyerMeTokenAfter).to.equal(0); expect(toETHNumber(meTokenTotalSupplyAfter)).to.be.approximately( toETHNumber(meTokenTotalSupply.div(2)), @@ -422,7 +472,10 @@ const setup = async () => { ); expect( toETHNumber(migrationWETHBefore.sub(migrationWETHAfter)) - ).to.approximately(assetsReturned, 1e-15); + ).to.approximately( + new Decimal(assetsReturned).sub(new Decimal(burnFee)).toNumber(), + 1e-15 + ); }); it("burn() [owner]: assets received based on weighted average Curve details", async () => { const ownerMeToken = await meToken.balanceOf(account0.address); @@ -461,25 +514,34 @@ const setup = async () => { (toETHNumber(ownerMeToken) / toETHNumber(meTokenTotalSupply)) * toETHNumber(meTokenDetails.balanceLocked); - await mineBlock(block.timestamp + 1); - await setAutomine(true); await foundry .connect(account0) - .burn(meToken.address, ownerMeToken, account0.address); + .burn(meToken.address, ownerMeToken.sub(1), account0.address); + await mineBlock(block.timestamp + 1); + await setAutomine(true); const ownerMeTokenAfter = await meToken.balanceOf(account0.address); const ownerWETHAfter = await weth.balanceOf(account0.address); const migrationWETHAfter = await weth.balanceOf(migration.address); const meTokenTotalSupplyAfter = await meToken.totalSupply(); + const burnFee = toETHNumber( + (await fees.burnOwnerFee()) + .mul(fromETHNumber(assetsReturned)) + .div(PRECISION) + ); + expect(migrationWETHBefore.sub(migrationWETHAfter)).to.equal( ownerWETHAfter.sub(ownerWETHBefore) ); expect( toETHNumber(ownerWETHAfter.sub(ownerWETHBefore)) - ).to.be.approximately(assetsReturned, 0.000000000000001); - expect(ownerMeTokenAfter).to.equal(0); - expect(toETHNumber(meTokenTotalSupplyAfter)).to.equal(0); + ).to.be.approximately( + new Decimal(assetsReturned).sub(new Decimal(burnFee)).toNumber(), + 1e-15 + ); + expect(ownerMeTokenAfter).to.equal(1); + expect(meTokenTotalSupplyAfter).to.equal(1); }); }); @@ -493,6 +555,50 @@ const setup = async () => { const block = await ethers.provider.getBlock("latest"); expect(metokenDetails.endTime).to.be.lt(block.timestamp); }); + it("burn(): finish migration must be called", async () => { + const ownerMeToken = await meToken.balanceOf(account0.address); + const migrationWETHBefore = await weth.balanceOf(migration.address); + const meTokenTotalSupply = await meToken.totalSupply(); + const meTokenDetails = await meTokenRegistry.getDetails( + meToken.address + ); + const ownerWETHBefore = await weth.balanceOf(account0.address); + + await setAutomine(false); + const block = await ethers.provider.getBlock("latest"); + + const targetAssetsReturned = calculateCollateralReturned( + toETHNumber(ownerMeToken), + toETHNumber(meTokenTotalSupply), + toETHNumber(meTokenDetails.balancePooled), + reserveWeight2 / MAX_WEIGHT + ); + + const assetsReturned = + targetAssetsReturned + + (toETHNumber(ownerMeToken) / toETHNumber(meTokenTotalSupply)) * + toETHNumber(meTokenDetails.balanceLocked); + + await foundry + .connect(account0) + .burn(meToken.address, ownerMeToken, account0.address); + await mineBlock(block.timestamp + 1); + await setAutomine(true); + + const ownerMeTokenAfter = await meToken.balanceOf(account0.address); + const ownerWETHAfter = await weth.balanceOf(account0.address); + const migrationWETHAfter = await weth.balanceOf(migration.address); + const meTokenTotalSupplyAfter = await meToken.totalSupply(); + + expect(migrationWETHBefore.sub(migrationWETHAfter)).to.equal( + ownerWETHAfter.sub(ownerWETHBefore) + ); + expect( + toETHNumber(ownerWETHAfter.sub(ownerWETHBefore)) + ).to.be.approximately(assetsReturned, 0.000000000000001); + expect(ownerMeTokenAfter).to.equal(0); + expect(toETHNumber(meTokenTotalSupplyAfter)).to.equal(0); + }); it("mint(): assets received based on target Curve details", async () => { const vaultWETHBefore = await weth.balanceOf(singleAssetVault.address); const migrationWETHBefore = await weth.balanceOf(migration.address); @@ -516,7 +622,7 @@ const setup = async () => { expect(toETHNumber(ownerMeTokenAfter)).to.be.approximately( calculatedTargetReturn, - 0.000000000000001 + 1e-15 ); expect(meTokenTotalSupplyAfter).to.be.equal(ownerMeTokenAfter); expect(vaultWETHAfter.sub(vaultWETHBefore)).to.equal(tokenDeposited); @@ -554,9 +660,18 @@ const setup = async () => { const vaultWETHAfter = await weth.balanceOf(singleAssetVault.address); const meTokenTotalSupplyAfter = await meToken.totalSupply(); + const burnFee = toETHNumber( + (await fees.burnBuyerFee()) + .mul(fromETHNumber(assetsReturned)) + .div(PRECISION) + ); + expect( toETHNumber(buyerWETHAfter.sub(buyerWETHBefore)) - ).to.be.approximately(assetsReturned, 1e-15); + ).to.be.approximately( + new Decimal(assetsReturned).sub(new Decimal(burnFee)).toNumber(), + 1e-15 + ); expect(buyerMeTokenAfter).to.equal(0); expect(toETHNumber(meTokenTotalSupplyAfter)).to.be.approximately( toETHNumber(meTokenTotalSupply.div(2)), @@ -564,7 +679,10 @@ const setup = async () => { ); expect( toETHNumber(vaultWETHBefore.sub(vaultWETHAfter)) - ).to.approximately(assetsReturned, 1e-15); + ).to.approximately( + new Decimal(assetsReturned).sub(new Decimal(burnFee)).toNumber(), + 1e-15 + ); }); it("burn() [owner]: assets received based on target Curve details", async () => { const ownerMeToken = await meToken.balanceOf(account0.address); @@ -593,15 +711,24 @@ const setup = async () => { const ownerMeTokenAfter = await meToken.balanceOf(account0.address); const ownerWETHAfter = await weth.balanceOf(account0.address); - const vaultWETHAfter = await dai.balanceOf(singleAssetVault.address); + const vaultWETHAfter = await weth.balanceOf(singleAssetVault.address); const meTokenTotalSupplyAfter = await meToken.totalSupply(); + const burnFee = toETHNumber( + (await fees.burnOwnerFee()) + .mul(fromETHNumber(assetsReturned)) + .div(PRECISION) + ); + expect(vaultWETHBefore.sub(vaultWETHAfter)).to.equal( ownerWETHAfter.sub(ownerWETHBefore) ); expect( toETHNumber(ownerWETHAfter.sub(ownerWETHBefore)) - ).to.be.approximately(assetsReturned, 0.000000000000001); + ).to.be.approximately( + new Decimal(assetsReturned).sub(new Decimal(burnFee)).toNumber(), + 1e-15 + ); expect(ownerMeTokenAfter).to.equal(0); expect(toETHNumber(meTokenTotalSupplyAfter)).to.equal(0); }); diff --git a/test/integration/MeTokenRegistry/ResubscribeRefundRatio.ts b/test/integration/MeTokenRegistry/ResubscribeRefundRatio.ts index 16d42a0e..729f04d0 100644 --- a/test/integration/MeTokenRegistry/ResubscribeRefundRatio.ts +++ b/test/integration/MeTokenRegistry/ResubscribeRefundRatio.ts @@ -18,6 +18,7 @@ import { expect } from "chai"; import { SingleAssetVault } from "../../../artifacts/types/SingleAssetVault"; import { mineBlock } from "../../utils/hardhatNode"; import { UniswapSingleTransferMigration } from "../../../artifacts/types/UniswapSingleTransferMigration"; +import { WeightedAverage } from "../../../artifacts/types/WeightedAverage"; const setup = async () => { describe("MeToken Resubscribe - new RefundRatio", () => { @@ -64,15 +65,22 @@ const setup = async () => { ["address"], [DAI] ); - bancorABDK = await deploy("BancorABDK"); - + const weightedAverage = await deploy("WeightedAverage"); + foundry = await deploy("Foundry", { + WeightedAverage: weightedAverage.address, + }); + hub = await deploy("Hub"); + bancorABDK = await deploy( + "BancorABDK", + undefined, + hub.address, + foundry.address + ); ({ token: dai, - hub, tokenHolder, migrationRegistry, singleAssetVault, - foundry, account0, account1, account2, @@ -81,6 +89,8 @@ const setup = async () => { encodedCurveDetails, encodedVaultArgs, initialRefundRatio.toNumber(), + hub, + foundry, bancorABDK )); diff --git a/test/utils/hubSetup.ts b/test/utils/hubSetup.ts index 40759c47..dc699945 100644 --- a/test/utils/hubSetup.ts +++ b/test/utils/hubSetup.ts @@ -21,21 +21,20 @@ export async function hubSetup( encodedCurveDetails: string, encodedVaultArgs: string, refundRatio: number, + hub: Hub, + foundry: Foundry, curve: ICurve, fees?: number[], erc20Address?: string, erc20Whale?: string ): Promise<{ tokenAddr: string; - weightedAverage: WeightedAverage; meTokenRegistry: MeTokenRegistry; meTokenFactory: MeTokenFactory; curveRegistry: CurveRegistry; vaultRegistry: VaultRegistry; migrationRegistry: MigrationRegistry; singleAssetVault: SingleAssetVault; - foundry: Foundry; - hub: Hub; token: ERC20; fee: Fees; account0: SignerWithAddress; @@ -47,16 +46,13 @@ export async function hubSetup( }> { const { tokenAddr, - weightedAverage, meTokenRegistry, meTokenFactory, curveRegistry, vaultRegistry, migrationRegistry, singleAssetVault, - foundry, fee, - hub, token, account0, account1, @@ -64,7 +60,14 @@ export async function hubSetup( account3, tokenHolder, tokenWhale, - } = await hubSetupWithoutRegister(curve, fees, erc20Address, erc20Whale); + } = await hubSetupWithoutRegister( + hub, + foundry, + curve, + fees, + erc20Address, + erc20Whale + ); await hub.register( account0.address, @@ -77,16 +80,13 @@ export async function hubSetup( ); return { tokenAddr, - weightedAverage, meTokenRegistry, meTokenFactory, curveRegistry, vaultRegistry, migrationRegistry, singleAssetVault, - foundry, fee, - hub, token, account0, account1, @@ -97,21 +97,20 @@ export async function hubSetup( }; } export async function hubSetupWithoutRegister( + hub: Hub, + foundry: Foundry, curve: ICurve, fees?: number[], erc20Address?: string, erc20Whale?: string ): Promise<{ tokenAddr: string; - weightedAverage: WeightedAverage; meTokenRegistry: MeTokenRegistry; meTokenFactory: MeTokenFactory; curveRegistry: CurveRegistry; vaultRegistry: VaultRegistry; migrationRegistry: MigrationRegistry; singleAssetVault: SingleAssetVault; - foundry: Foundry; - hub: Hub; token: ERC20; fee: Fees; account0: SignerWithAddress; @@ -122,16 +121,13 @@ export async function hubSetupWithoutRegister( tokenWhale: string; }> { let tokenAddr: string; - let weightedAverage: WeightedAverage; let meTokenRegistry: MeTokenRegistry; let meTokenFactory: MeTokenFactory; let curveRegistry: CurveRegistry; let vaultRegistry: VaultRegistry; let migrationRegistry: MigrationRegistry; let singleAssetVault: SingleAssetVault; - let foundry: Foundry; let fee: Fees; - let hub: Hub; let token: ERC20; let account0: SignerWithAddress; let account1: SignerWithAddress; @@ -157,17 +153,11 @@ export async function hubSetupWithoutRegister( token .connect(tokenHolder) .transfer(account1.address, ethers.utils.parseEther("1000")); - weightedAverage = await deploy("WeightedAverage"); curveRegistry = await deploy("CurveRegistry"); vaultRegistry = await deploy("VaultRegistry"); migrationRegistry = await deploy("MigrationRegistry"); - foundry = await deploy("Foundry", { - WeightedAverage: weightedAverage.address, - }); - - hub = await deploy("Hub"); meTokenFactory = await deploy("MeTokenFactory"); meTokenRegistry = await deploy( "MeTokenRegistry", @@ -212,16 +202,13 @@ export async function hubSetupWithoutRegister( return { tokenAddr, - weightedAverage, meTokenRegistry, meTokenFactory, curveRegistry, vaultRegistry, migrationRegistry, singleAssetVault, - foundry, fee, - hub, token, account0, account1,