diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..00d864b4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.sol] +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..80f656e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/ +typechain/ +env.json +.tmp-addresses*.json +data/gmxMigration +flattened + +#Hardhat files +cache +artifacts diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ade2b707 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2016-2020 zOS Global Limited + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..b34ea1f0 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# Gambit Contracts +Contracts for the GMT Token and GMT Treasury. + +## Install Dependencies +If npx is not installed yet: +`npm install -g npx` + +Install packages: +`npm i` + +## Compile Contracts +`npx hardhat compile` + +## Run Tests +`npx hardhat test` + +## Vault +The Vault contract handles buying USDG, selling USDG, swapping, increasing positions, decreasing positions and liquidations. +Overview: https://gambit.gitbook.io/gambit/ + +### Buying USDG +- USDG can be bought with any whitelisted token +- The oracle price is used to determine the amount of USDG that should be minted to the receiver, with 1 USDG being valued at 1 USD +- Fees are collected based on `swapFeeBasisPoints` +- `usdgAmounts` is increased to track the USDG debt of the token +- `poolAmounts` is increased to track the amount of tokens that can be used for swaps or borrowed for margin trading + +### Selling USDG +- USDG can be sold for any whitelisted token +- The oracle price is used to determine the amount of tokens that should be sent to the receiver +- For non-stableTokens, the amount of tokens sent out is additionally capped by the redemption collateral +- To calculate the redemption collateral: + - Convert the value in `guaranteedUsd[token]` from USD to tokens + - Add `poolAmounts[token]` + - Subtract `reservedAmounts[token]` +- The reason for this calculation is because traders can open long positions by borrowing non-stable whitelisted tokens, when these tokens are borrowed the USD value in `guaranteedUsd[token]` is guaranteed until the positions are closed or liquidated +- `reservedAmounts[token]` tracks the amount of tokens in the pool reserved for open positions +- The redemption amount is capped by: `(USDG sold) / (USDG debt) * (redemption collateral) * (redemptionBasisPoints[token]) / BASIS_POINTS_DIVISOR` +- redemptionBasisPoints can be adjusted to allow a larger or smaller amount of redemption +- Fees are collected based on `swapFeeBasisPoints` +- `usdgAmounts` is decreased to reduce the USDG debt of the token +- `poolAmounts` is decreased to reflect the reduction in available collateral for redemption + +### Swap +- Any whitelisted tokens can be swapped for one another +- The oracle prices are used to determine the amount of tokens that should be sent to the receiver +- USDG debt is transferred from the _tokenOut to the _tokenIn +- Fees are collected based on `swapFeeBasisPoints` +- `poolAmounts` are updated to reflect the change in tokens + +### IncreasePosition +- Traders can long and short whitelisted tokens +- For longs, the collateral token must be the same as the index token (the token being longed) +- For shorts, the collateral token must be a stableToken and the index token must not be a stableToken +- For both longs and shorts, the token borrowed from the pool is based on the collateral token +- Fees are collected based on `marginFeeBasisPoints` and funding rates +- Funding rates are calculated based on the `fundingRateFactor` and utilisation of the pool for the token being borrowed +- `reservedAmounts[token]` is increased to ensure there are sufficient tokens to pay profits on the position +- For longs: + - `guaranteedUsd[token]` is updated based on the difference between the position size and the collateral + - `poolAmounts[token]` is increased by the collateral received and considered as part of the pool +- For shorts: + - `guaranteedUsd[token]` is not updated as the collateral token is a stableToken, and no USD amount is additionally guaranteed + - `poolAmounts[token]` is not increased as the collateral is not considered as part of the pool + +### DecreasePosition +- `reservedAmounts[token]` is decreased proportional to the decrease in position size +- For longs: + - The `guaranteedUsd[token]` is updated based on the new difference between the position size and the collateral + - `poolAmounts[token]` is decreased by the amount of USD sent out, since the position's collateral and the position's size are treated as a part of the pool +- For shorts: + - `poolAmounts[token]` is decreased if there are realised profits for the position + - `poolAmounts[token]` is increased if there are realised losses for the position + +### LiquidatePosition +- Any user can liquidate a position if the remaining collateral after losses is lower than `liquidationFeeUsd` or if the `maxLeverage` is exceeded +- `reservedAmounts[token]` is decreased since it is no longer needed for the position +- For longs: + - `guaranteedUsd[token]` is decreased based on the different between the position size and the collateral +- For shorts: + - `poolAmounts[token]` is increased to reflect the additional collateral from the position's losses + +## Front-Running Protection +Oracle results can be known before the result is finalised on-chain. + +This means that a trader could observe oracle results in the mempool or otherwise and enter a favourable position before the result is finalised. + +Over time, this could lead to losses in assets for the system. + +To guard against this attack vector, the last three oracle results are sampled to determine prices. + +This reduces the attack surface as minor fluctuations cannot be exploited for profits. + +Additionally, a `minProfitBasisPoints` configuration is allowed per token. + +If the oracle is updated on every 0.5% price movement, the `minProfitBasisPoints` for the token could be set to 0.75%. +This means that if the profit on a position is less than 0.75%, then the profit would be considered to be 0. + +For buying USDG, selling USDG and swaps, a fee of 0.3% would make trades across a 0.5% price movement unprofitable. + +## Governance +Governance will be set to a timelock contract which would require actions to be broadcasted 5 days in advance before they can be executed. + +This timelock contract will be upgraded to a DAO based contract once the system is stable. diff --git a/audits/ABDK_Audit_Review.txt b/audits/ABDK_Audit_Review.txt new file mode 100644 index 00000000..eae0f3db --- /dev/null +++ b/audits/ABDK_Audit_Review.txt @@ -0,0 +1,9 @@ +The major issues raised in the ABDK Audit have been resolved. + +1. CVF-42, CVF-51: The mentioned functions have been removed, the intention of these functions was to support future features. We have decided to keep the supported features compact, when new features are needed the new code will be sent for another audit + +2. CVF-87: The function is meant to be re-callable, token whitelisting is currently controlled by a Timelock contract with a delay of 5 days: https://bscscan.com/address/0x330EeF6b9B1ea6EDd620C825c9919DC8b611d5d5 + +3. CVF-90: The returned value was not affecting any behaviour, but for correctness it has been fixed: https://github.com/xvi10/gambit-contracts/blob/master/contracts/core/Vault.sol#L310 + +4. CVF-130: The spread between prices is assumed to be small, it is large only if the prices are volatile, in which case, a larger spread is desirable to protect the assets in the system diff --git a/audits/ABDK_Gambit_Solidity.pdf b/audits/ABDK_Gambit_Solidity.pdf new file mode 100644 index 00000000..a075cd9c Binary files /dev/null and b/audits/ABDK_Gambit_Solidity.pdf differ diff --git a/contracts/access/Governable.sol b/contracts/access/Governable.sol new file mode 100644 index 00000000..5f8bd6d7 --- /dev/null +++ b/contracts/access/Governable.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +contract Governable { + address public gov; + + constructor() public { + gov = msg.sender; + } + + modifier onlyGov() { + require(msg.sender == gov, "Governable: forbidden"); + _; + } + + function setGov(address _gov) external onlyGov { + gov = _gov; + } +} diff --git a/contracts/access/TokenManager.sol b/contracts/access/TokenManager.sol new file mode 100644 index 00000000..88547299 --- /dev/null +++ b/contracts/access/TokenManager.sol @@ -0,0 +1,197 @@ +//SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/ERC721/IERC721.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "../peripherals/interfaces/ITimelock.sol"; + +contract TokenManager is ReentrancyGuard { + using SafeMath for uint256; + + bool public isInitialized; + + uint256 public actionsNonce; + uint256 public minAuthorizations; + + address public admin; + + address[] public signers; + mapping (address => bool) public isSigner; + mapping (bytes32 => bool) public pendingActions; + mapping (address => mapping (bytes32 => bool)) public signedActions; + + event SignalApprove(address token, address spender, uint256 amount, bytes32 action, uint256 nonce); + event SignalApproveNFT(address token, address spender, uint256 tokenId, bytes32 action, uint256 nonce); + event SignalApproveNFTs(address token, address spender, uint256[] tokenIds, bytes32 action, uint256 nonce); + event SignalSetAdmin(address target, address admin, bytes32 action, uint256 nonce); + event SignalPendingAction(bytes32 action, uint256 nonce); + event SignAction(bytes32 action, uint256 nonce); + event ClearAction(bytes32 action, uint256 nonce); + + constructor(uint256 _minAuthorizations) public { + admin = msg.sender; + minAuthorizations = _minAuthorizations; + } + + modifier onlyAdmin() { + require(msg.sender == admin, "TokenManager: forbidden"); + _; + } + + modifier onlySigner() { + require(isSigner[msg.sender], "TokenManager: forbidden"); + _; + } + + function initialize(address[] memory _signers) public virtual onlyAdmin { + require(!isInitialized, "TokenManager: already initialized"); + isInitialized = true; + + signers = _signers; + for (uint256 i = 0; i < _signers.length; i++) { + address signer = _signers[i]; + isSigner[signer] = true; + } + } + + function signersLength() public view returns (uint256) { + return signers.length; + } + + function signalApprove(address _token, address _spender, uint256 _amount) external nonReentrant onlyAdmin { + actionsNonce++; + uint256 nonce = actionsNonce; + bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount, nonce)); + _setPendingAction(action, nonce); + emit SignalApprove(_token, _spender, _amount, action, nonce); + } + + function signApprove(address _token, address _spender, uint256 _amount, uint256 _nonce) external nonReentrant onlySigner { + bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount, _nonce)); + _validateAction(action); + require(!signedActions[msg.sender][action], "TokenManager: already signed"); + signedActions[msg.sender][action] = true; + emit SignAction(action, _nonce); + } + + function approve(address _token, address _spender, uint256 _amount, uint256 _nonce) external nonReentrant onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount, _nonce)); + _validateAction(action); + _validateAuthorization(action); + + IERC20(_token).approve(_spender, _amount); + _clearAction(action, _nonce); + } + + function signalApproveNFT(address _token, address _spender, uint256 _tokenId) external nonReentrant onlyAdmin { + actionsNonce++; + uint256 nonce = actionsNonce; + bytes32 action = keccak256(abi.encodePacked("approveNFT", _token, _spender, _tokenId, nonce)); + _setPendingAction(action, nonce); + emit SignalApproveNFT(_token, _spender, _tokenId, action, nonce); + } + + function signApproveNFT(address _token, address _spender, uint256 _tokenId, uint256 _nonce) external nonReentrant onlySigner { + bytes32 action = keccak256(abi.encodePacked("approveNFT", _token, _spender, _tokenId, _nonce)); + _validateAction(action); + require(!signedActions[msg.sender][action], "TokenManager: already signed"); + signedActions[msg.sender][action] = true; + emit SignAction(action, _nonce); + } + + function approveNFT(address _token, address _spender, uint256 _tokenId, uint256 _nonce) external nonReentrant onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("approveNFT", _token, _spender, _tokenId, _nonce)); + _validateAction(action); + _validateAuthorization(action); + + IERC721(_token).approve(_spender, _tokenId); + _clearAction(action, _nonce); + } + + function signalApproveNFTs(address _token, address _spender, uint256[] memory _tokenIds) external nonReentrant onlyAdmin { + actionsNonce++; + uint256 nonce = actionsNonce; + bytes32 action = keccak256(abi.encodePacked("approveNFTs", _token, _spender, _tokenIds, nonce)); + _setPendingAction(action, nonce); + emit SignalApproveNFTs(_token, _spender, _tokenIds, action, nonce); + } + + function signApproveNFTs(address _token, address _spender, uint256[] memory _tokenIds, uint256 _nonce) external nonReentrant onlySigner { + bytes32 action = keccak256(abi.encodePacked("approveNFTs", _token, _spender, _tokenIds, _nonce)); + _validateAction(action); + require(!signedActions[msg.sender][action], "TokenManager: already signed"); + signedActions[msg.sender][action] = true; + emit SignAction(action, _nonce); + } + + function approveNFTs(address _token, address _spender, uint256[] memory _tokenIds, uint256 _nonce) external nonReentrant onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("approveNFTs", _token, _spender, _tokenIds, _nonce)); + _validateAction(action); + _validateAuthorization(action); + + for (uint256 i = 0 ; i < _tokenIds.length; i++) { + IERC721(_token).approve(_spender, _tokenIds[i]); + } + _clearAction(action, _nonce); + } + + function signalSetAdmin(address _target, address _admin) external nonReentrant onlySigner { + actionsNonce++; + uint256 nonce = actionsNonce; + bytes32 action = keccak256(abi.encodePacked("setAdmin", _target, _admin, nonce)); + _setPendingAction(action, nonce); + signedActions[msg.sender][action] = true; + emit SignalSetAdmin(_target, _admin, action, nonce); + } + + function signSetAdmin(address _target, address _admin, uint256 _nonce) external nonReentrant onlySigner { + bytes32 action = keccak256(abi.encodePacked("setAdmin", _target, _admin, _nonce)); + _validateAction(action); + require(!signedActions[msg.sender][action], "TokenManager: already signed"); + signedActions[msg.sender][action] = true; + emit SignAction(action, _nonce); + } + + function setAdmin(address _target, address _admin, uint256 _nonce) external nonReentrant onlySigner { + bytes32 action = keccak256(abi.encodePacked("setAdmin", _target, _admin, _nonce)); + _validateAction(action); + _validateAuthorization(action); + + ITimelock(_target).setAdmin(_admin); + _clearAction(action, _nonce); + } + + function _setPendingAction(bytes32 _action, uint256 _nonce) private { + pendingActions[_action] = true; + emit SignalPendingAction(_action, _nonce); + } + + function _validateAction(bytes32 _action) private view { + require(pendingActions[_action], "TokenManager: action not signalled"); + } + + function _validateAuthorization(bytes32 _action) private view { + uint256 count = 0; + for (uint256 i = 0; i < signers.length; i++) { + address signer = signers[i]; + if (signedActions[signer][_action]) { + count++; + } + } + + if (count == 0) { + revert("TokenManager: action not authorized"); + } + require(count >= minAuthorizations, "TokenManager: insufficient authorization"); + } + + function _clearAction(bytes32 _action, uint256 _nonce) private { + require(pendingActions[_action], "TokenManager: invalid _action"); + delete pendingActions[_action]; + emit ClearAction(_action, _nonce); + } +} diff --git a/contracts/amm/PancakeFactory.sol b/contracts/amm/PancakeFactory.sol new file mode 100644 index 00000000..4ee8d387 --- /dev/null +++ b/contracts/amm/PancakeFactory.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "./interfaces/IPancakeFactory.sol"; + +contract PancakeFactory is IPancakeFactory { + address public btc; + address public bnb; + address public busd; + + address public bnbBusdPair; + address public btcBnbPair; + + constructor(address[] memory _addresses) public { + btc = _addresses[0]; + bnb = _addresses[1]; + busd = _addresses[2]; + + bnbBusdPair = _addresses[3]; + btcBnbPair = _addresses[4]; + } + + function getPair(address tokenA, address tokenB) external override view returns (address) { + if (tokenA == busd && tokenB == bnb) { + return bnbBusdPair; + } + if (tokenA == bnb && tokenB == btc) { + return btcBnbPair; + } + revert("Invalid tokens"); + } +} diff --git a/contracts/amm/PancakePair.sol b/contracts/amm/PancakePair.sol new file mode 100644 index 00000000..e534b1bd --- /dev/null +++ b/contracts/amm/PancakePair.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "./interfaces/IPancakePair.sol"; + +contract PancakePair is IPancakePair { + uint112 private reserve0; // uses single storage slot, accessible via getReserves + uint112 private reserve1; // uses single storage slot, accessible via getReserves + uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves + + function setReserves(uint256 balance0, uint256 balance1) external { + reserve0 = uint112(balance0); + reserve1 = uint112(balance1); + blockTimestampLast = uint32(block.timestamp); + } + + function getReserves() public override view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) { + _reserve0 = reserve0; + _reserve1 = reserve1; + _blockTimestampLast = blockTimestampLast; + } +} diff --git a/contracts/amm/PancakeRouter.sol b/contracts/amm/PancakeRouter.sol new file mode 100644 index 00000000..b89839a2 --- /dev/null +++ b/contracts/amm/PancakeRouter.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../tokens/Token.sol"; +import "../libraries/token/IERC20.sol"; +import "./interfaces/IPancakeRouter.sol"; + +contract PancakeRouter is IPancakeRouter { + address public pair; + + constructor(address _pair) public { + pair = _pair; + } + + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 /*amountAMin*/, + uint256 /*amountBMin*/, + address to, + uint256 deadline + ) external override returns (uint256 amountA, uint256 amountB, uint256 liquidity) { + require(deadline >= block.timestamp, 'PancakeRouter: EXPIRED'); + + Token(pair).mint(to, 1000); + + IERC20(tokenA).transferFrom(msg.sender, pair, amountADesired); + IERC20(tokenB).transferFrom(msg.sender, pair, amountBDesired); + + amountA = amountADesired; + amountB = amountBDesired; + liquidity = 1000; + } +} diff --git a/contracts/amm/UniFactory.sol b/contracts/amm/UniFactory.sol new file mode 100644 index 00000000..03359a81 --- /dev/null +++ b/contracts/amm/UniFactory.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +contract UniFactory { + mapping(address => mapping(address => mapping(uint24 => address))) public getPool; +} diff --git a/contracts/amm/UniPool.sol b/contracts/amm/UniPool.sol new file mode 100644 index 00000000..1ea04cbe --- /dev/null +++ b/contracts/amm/UniPool.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +contract UniPool { + struct Slot0 { + // the current price + uint160 sqrtPriceX96; + // the current tick + int24 tick; + // the most-recently updated index of the observations array + uint16 observationIndex; + // the current maximum number of observations that are being stored + uint16 observationCardinality; + // the next maximum number of observations to store, triggered in observations.write + uint16 observationCardinalityNext; + // the current protocol fee as a percentage of the swap fee taken on withdrawal + // represented as an integer denominator (1/x)% + uint8 feeProtocol; + // whether the pool is locked + bool unlocked; + } + + Slot0 public slot0; + + function tickSpacing() external pure returns (int24) { return 0; } + + function observe(uint32[] calldata /* secondsAgos */) + external + pure + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) + { + return (tickCumulatives, secondsPerLiquidityCumulativeX128s); + } +} diff --git a/contracts/amm/interfaces/IPancakeFactory.sol b/contracts/amm/interfaces/IPancakeFactory.sol new file mode 100644 index 00000000..b95cf4a6 --- /dev/null +++ b/contracts/amm/interfaces/IPancakeFactory.sol @@ -0,0 +1,7 @@ +//SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IPancakeFactory { + function getPair(address tokenA, address tokenB) external view returns (address pair); +} diff --git a/contracts/amm/interfaces/IPancakePair.sol b/contracts/amm/interfaces/IPancakePair.sol new file mode 100644 index 00000000..9c82cd9f --- /dev/null +++ b/contracts/amm/interfaces/IPancakePair.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.5.0; + +interface IPancakePair { + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); +} diff --git a/contracts/amm/interfaces/IPancakeRouter.sol b/contracts/amm/interfaces/IPancakeRouter.sol new file mode 100644 index 00000000..edf907c0 --- /dev/null +++ b/contracts/amm/interfaces/IPancakeRouter.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IPancakeRouter { + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); +} diff --git a/contracts/core/GlpManager.sol b/contracts/core/GlpManager.sol new file mode 100644 index 00000000..7aef75be --- /dev/null +++ b/contracts/core/GlpManager.sol @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: MIT + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./interfaces/IVault.sol"; +import "./interfaces/IGlpManager.sol"; +import "../tokens/interfaces/IUSDG.sol"; +import "../tokens/interfaces/IMintable.sol"; +import "../access/Governable.sol"; + +pragma solidity 0.6.12; + +contract GlpManager is ReentrancyGuard, Governable, IGlpManager { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + uint256 public constant PRICE_PRECISION = 10 ** 30; + uint256 public constant USDG_DECIMALS = 18; + uint256 public constant MAX_COOLDOWN_DURATION = 48 hours; + + IVault public vault; + address public usdg; + address public glp; + + uint256 public cooldownDuration; + mapping (address => uint256) public lastAddedAt; + + uint256 public aumAddition; + uint256 public aumDeduction; + + bool public inPrivateMode; + mapping (address => bool) public isHandler; + + event AddLiquidity( + address account, + address token, + uint256 amount, + uint256 aumInUsdg, + uint256 glpSupply, + uint256 usdgAmount, + uint256 mintAmount + ); + + event RemoveLiquidity( + address account, + address token, + uint256 glpAmount, + uint256 aumInUsdg, + uint256 glpSupply, + uint256 usdgAmount, + uint256 amountOut + ); + + constructor(address _vault, address _usdg, address _glp, uint256 _cooldownDuration) public { + gov = msg.sender; + vault = IVault(_vault); + usdg = _usdg; + glp = _glp; + cooldownDuration = _cooldownDuration; + } + + function setInPrivateMode(bool _inPrivateMode) external onlyGov { + inPrivateMode = _inPrivateMode; + } + + function setHandler(address _handler, bool _isActive) external onlyGov { + isHandler[_handler] = _isActive; + } + + function setCooldownDuration(uint256 _cooldownDuration) external onlyGov { + require(_cooldownDuration <= MAX_COOLDOWN_DURATION, "GlpManager: invalid _cooldownDuration"); + cooldownDuration = _cooldownDuration; + } + + function setAumAdjustment(uint256 _aumAddition, uint256 _aumDeduction) external onlyGov { + aumAddition = _aumAddition; + aumDeduction = _aumDeduction; + } + + function addLiquidity(address _token, uint256 _amount, uint256 _minUsdg, uint256 _minGlp) external override nonReentrant returns (uint256) { + if (inPrivateMode) { revert("GlpManager: action not enabled"); } + return _addLiquidity(msg.sender, msg.sender, _token, _amount, _minUsdg, _minGlp); + } + + function addLiquidityForAccount(address _fundingAccount, address _account, address _token, uint256 _amount, uint256 _minUsdg, uint256 _minGlp) external override nonReentrant returns (uint256) { + _validateHandler(); + return _addLiquidity(_fundingAccount, _account, _token, _amount, _minUsdg, _minGlp); + } + + function removeLiquidity(address _tokenOut, uint256 _glpAmount, uint256 _minOut, address _receiver) external override nonReentrant returns (uint256) { + if (inPrivateMode) { revert("GlpManager: action not enabled"); } + return _removeLiquidity(msg.sender, _tokenOut, _glpAmount, _minOut, _receiver); + } + + function removeLiquidityForAccount(address _account, address _tokenOut, uint256 _glpAmount, uint256 _minOut, address _receiver) external override nonReentrant returns (uint256) { + _validateHandler(); + return _removeLiquidity(_account, _tokenOut, _glpAmount, _minOut, _receiver); + } + + function getAums() public view returns (uint256[] memory) { + uint256[] memory amounts = new uint256[](2); + amounts[0] = getAum(true); + amounts[1] = getAum(false); + return amounts; + } + + function getAumInUsdg(bool maximise) public view returns (uint256) { + uint256 aum = getAum(maximise); + return aum.mul(10 ** USDG_DECIMALS).div(PRICE_PRECISION); + } + + function getAum(bool maximise) public view returns (uint256) { + uint256 length = vault.allWhitelistedTokensLength(); + uint256 aum = aumAddition; + uint256 shortProfits = 0; + + for (uint256 i = 0; i < length; i++) { + address token = vault.allWhitelistedTokens(i); + bool isWhitelisted = vault.whitelistedTokens(token); + + if (!isWhitelisted) { + continue; + } + + uint256 price = maximise ? vault.getMaxPrice(token) : vault.getMinPrice(token); + uint256 poolAmount = vault.poolAmounts(token); + uint256 decimals = vault.tokenDecimals(token); + + if (vault.stableTokens(token)) { + aum = aum.add(poolAmount.mul(price).div(10 ** decimals)); + } else { + // add global short profit / loss + uint256 size = vault.globalShortSizes(token); + if (size > 0) { + uint256 averagePrice = vault.globalShortAveragePrices(token); + uint256 priceDelta = averagePrice > price ? averagePrice.sub(price) : price.sub(averagePrice); + uint256 delta = size.mul(priceDelta).div(averagePrice); + if (price > averagePrice) { + // add losses from shorts + aum = aum.add(delta); + } else { + shortProfits = shortProfits.add(delta); + } + } + + aum = aum.add(vault.guaranteedUsd(token)); + + uint256 reservedAmount = vault.reservedAmounts(token); + aum = aum.add(poolAmount.sub(reservedAmount).mul(price).div(10 ** decimals)); + } + } + + aum = shortProfits > aum ? 0 : aum.sub(shortProfits); + return aumDeduction > aum ? 0 : aum.sub(aumDeduction); + } + + function _addLiquidity(address _fundingAccount, address _account, address _token, uint256 _amount, uint256 _minUsdg, uint256 _minGlp) private returns (uint256) { + require(_amount > 0, "GlpManager: invalid _amount"); + + // calculate aum before buyUSDG + uint256 aumInUsdg = getAumInUsdg(true); + uint256 glpSupply = IERC20(glp).totalSupply(); + + IERC20(_token).safeTransferFrom(_fundingAccount, address(vault), _amount); + uint256 usdgAmount = vault.buyUSDG(_token, address(this)); + require(usdgAmount >= _minUsdg, "GlpManager: insufficient USDG output"); + + uint256 mintAmount = aumInUsdg == 0 ? usdgAmount : usdgAmount.mul(glpSupply).div(aumInUsdg); + require(mintAmount >= _minGlp, "GlpManager: insufficient GLP output"); + + IMintable(glp).mint(_account, mintAmount); + + lastAddedAt[_account] = block.timestamp; + + emit AddLiquidity(_account, _token, _amount, aumInUsdg, glpSupply, usdgAmount, mintAmount); + + return mintAmount; + } + + function _removeLiquidity(address _account, address _tokenOut, uint256 _glpAmount, uint256 _minOut, address _receiver) private returns (uint256) { + require(_glpAmount > 0, "GlpManager: invalid _glpAmount"); + require(lastAddedAt[_account].add(cooldownDuration) <= block.timestamp, "GlpManager: cooldown duration not yet passed"); + + // calculate aum before sellUSDG + uint256 aumInUsdg = getAumInUsdg(false); + uint256 glpSupply = IERC20(glp).totalSupply(); + + uint256 usdgAmount = _glpAmount.mul(aumInUsdg).div(glpSupply); + uint256 usdgBalance = IERC20(usdg).balanceOf(address(this)); + if (usdgAmount > usdgBalance) { + IUSDG(usdg).mint(address(this), usdgAmount.sub(usdgBalance)); + } + + IMintable(glp).burn(_account, _glpAmount); + + IERC20(usdg).transfer(address(vault), usdgAmount); + uint256 amountOut = vault.sellUSDG(_tokenOut, _receiver); + require(amountOut >= _minOut, "GlpManager: insufficient output"); + + emit RemoveLiquidity(_account, _tokenOut, _glpAmount, aumInUsdg, glpSupply, usdgAmount, amountOut); + + return amountOut; + } + + function _validateHandler() private view { + require(isHandler[msg.sender], "GlpManager: forbidden"); + } +} diff --git a/contracts/core/OrderBook.sol b/contracts/core/OrderBook.sol new file mode 100644 index 00000000..0dd7db94 --- /dev/null +++ b/contracts/core/OrderBook.sol @@ -0,0 +1,930 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../tokens/interfaces/IWETH.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/Address.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./interfaces/IRouter.sol"; +import "./interfaces/IVault.sol"; +import "./interfaces/IOrderBook.sol"; +import "../tokens/interfaces/IWETH.sol"; + +contract OrderBook is ReentrancyGuard, IOrderBook { + using SafeMath for uint256; + using SafeERC20 for IERC20; + using Address for address payable; + + uint256 public constant PRICE_PRECISION = 1e30; + uint256 public constant USDG_PRECISION = 1e18; + + struct IncreaseOrder { + address account; + address purchaseToken; + uint256 purchaseTokenAmount; + address collateralToken; + address indexToken; + uint256 sizeDelta; + bool isLong; + uint256 triggerPrice; + bool triggerAboveThreshold; + uint256 executionFee; + } + struct DecreaseOrder { + address account; + address collateralToken; + uint256 collateralDelta; + address indexToken; + uint256 sizeDelta; + bool isLong; + uint256 triggerPrice; + bool triggerAboveThreshold; + uint256 executionFee; + } + struct SwapOrder { + address account; + address[] path; + uint256 amountIn; + uint256 minOut; + uint256 triggerRatio; + bool triggerAboveThreshold; + bool shouldUnwrap; + uint256 executionFee; + } + + mapping (address => mapping(uint256 => IncreaseOrder)) public increaseOrders; + mapping (address => uint256) public increaseOrdersIndex; + mapping (address => mapping(uint256 => DecreaseOrder)) public decreaseOrders; + mapping (address => uint256) public decreaseOrdersIndex; + mapping (address => mapping(uint256 => SwapOrder)) public swapOrders; + mapping (address => uint256) public swapOrdersIndex; + + address public gov; + address public weth; + address public usdg; + address public router; + address public vault; + uint256 public minExecutionFee; + uint256 public minPurchaseTokenAmountUsd; + bool public isInitialized = false; + + event CreateIncreaseOrder( + address indexed account, + uint256 orderIndex, + address purchaseToken, + uint256 purchaseTokenAmount, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold, + uint256 executionFee + ); + event CancelIncreaseOrder( + address indexed account, + uint256 orderIndex, + address purchaseToken, + uint256 purchaseTokenAmount, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold, + uint256 executionFee + ); + event ExecuteIncreaseOrder( + address indexed account, + uint256 orderIndex, + address purchaseToken, + uint256 purchaseTokenAmount, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold, + uint256 executionFee, + uint256 executionPrice + ); + event UpdateIncreaseOrder( + address indexed account, + uint256 orderIndex, + uint256 sizeDelta, + uint256 triggerPrice, + bool triggerAboveThreshold + ); + event CreateDecreaseOrder( + address indexed account, + uint256 orderIndex, + address collateralToken, + uint256 collateralDelta, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold, + uint256 executionFee + ); + event CancelDecreaseOrder( + address indexed account, + uint256 orderIndex, + address collateralToken, + uint256 collateralDelta, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold, + uint256 executionFee + ); + event ExecuteDecreaseOrder( + address indexed account, + uint256 orderIndex, + address collateralToken, + uint256 collateralDelta, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold, + uint256 executionFee, + uint256 executionPrice + ); + event UpdateDecreaseOrder( + address indexed account, + uint256 orderIndex, + uint256 collateralDelta, + uint256 sizeDelta, + uint256 triggerPrice, + bool triggerAboveThreshold + ); + event CreateSwapOrder( + address account, + uint256 orderIndex, + address[] path, + uint256 amountIn, + uint256 minOut, + uint256 triggerRatio, + bool triggerAboveThreshold, + bool shouldUnwrap, + uint256 executionFee + ); + event CancelSwapOrder( + address account, + uint256 orderIndex, + address[] path, + uint256 amountIn, + uint256 minOut, + uint256 triggerRatio, + bool triggerAboveThreshold, + bool shouldUnwrap, + uint256 executionFee + ); + event UpdateSwapOrder( + address account, + uint256 ordexIndex, + address[] path, + uint256 amountIn, + uint256 minOut, + uint256 triggerRatio, + bool triggerAboveThreshold, + bool shouldUnwrap, + uint256 executionFee + ); + event ExecuteSwapOrder( + address account, + uint256 orderIndex, + address[] path, + uint256 amountIn, + uint256 minOut, + uint256 triggerRatio, + bool triggerAboveThreshold, + bool shouldUnwrap, + uint256 executionFee, + uint256 amountOut + ); + + event Initialize( + address router, + address vault, + address weth, + address usdg, + uint256 minExecutionFee, + uint256 minPurchaseTokenAmountUsd + ); + event UpdateMinExecutionFee(uint256 minExecutionFee); + event UpdateMinPurchaseTokenAmountUsd(uint256 minPurchaseTokenAmountUsd); + event UpdateGov(address gov); + + modifier onlyGov() { + require(msg.sender == gov, "OrderBook: forbidden"); + _; + } + + constructor() public { + gov = msg.sender; + } + + function initialize( + address _router, + address _vault, + address _weth, + address _usdg, + uint256 _minExecutionFee, + uint256 _minPurchaseTokenAmountUsd + ) external onlyGov { + require(!isInitialized, "OrderBook: already initialized"); + isInitialized = true; + + router = _router; + vault = _vault; + weth = _weth; + usdg = _usdg; + minExecutionFee = _minExecutionFee; + minPurchaseTokenAmountUsd = _minPurchaseTokenAmountUsd; + + emit Initialize(_router, _vault, _weth, _usdg, _minExecutionFee, _minPurchaseTokenAmountUsd); + } + + receive() external payable { + require(msg.sender == weth, "OrderBook: invalid sender"); + } + + function setMinExecutionFee(uint256 _minExecutionFee) external onlyGov { + minExecutionFee = _minExecutionFee; + + emit UpdateMinExecutionFee(_minExecutionFee); + } + + function setMinPurchaseTokenAmountUsd(uint256 _minPurchaseTokenAmountUsd) external onlyGov { + minPurchaseTokenAmountUsd = _minPurchaseTokenAmountUsd; + + emit UpdateMinPurchaseTokenAmountUsd(_minPurchaseTokenAmountUsd); + } + + function setGov(address _gov) external onlyGov { + gov = _gov; + + emit UpdateGov(_gov); + } + + function getSwapOrder(address _account, uint256 _orderIndex) override public view returns ( + address path0, + address path1, + address path2, + uint256 amountIn, + uint256 minOut, + uint256 triggerRatio, + bool triggerAboveThreshold + ) { + SwapOrder memory order = swapOrders[_account][_orderIndex]; + return ( + order.path.length > 0 ? order.path[0] : address(0), + order.path.length > 1 ? order.path[1] : address(0), + order.path.length > 2 ? order.path[2] : address(0), + order.amountIn, + order.minOut, + order.triggerRatio, + order.triggerAboveThreshold + ); + } + + function createSwapOrder( + address[] memory _path, + uint256 _amountIn, + uint256 _minOut, + uint256 _triggerRatio, // tokenB / tokenA + bool _triggerAboveThreshold, + uint256 _executionFee, + bool _shouldWrap, + bool _shouldUnwrap + ) external payable nonReentrant { + require(_path.length == 2 || _path.length == 3, "OrderBook: invalid _path.length"); + require(_path[0] != _path[_path.length - 1], "OrderBook: invalid _path"); + require(_amountIn > 0, "OrderBook: invalid _amountIn"); + require(_executionFee >= minExecutionFee, "OrderBook: insufficient execution fee"); + + // always need this call because of mandatory executionFee user has to transfer in BNB + _transferInETH(); + + if (_shouldWrap) { + require(_path[0] == weth, "OrderBook: only weth could be wrapped"); + require(msg.value == _executionFee.add(_amountIn), "OrderBook: incorrect value transferred"); + } else { + require(msg.value == _executionFee, "OrderBook: incorrect execution fee transferred"); + IRouter(router).pluginTransfer(_path[0], msg.sender, address(this), _amountIn); + } + + _createSwapOrder(msg.sender, _path, _amountIn, _minOut, _triggerRatio, _triggerAboveThreshold, _shouldUnwrap, _executionFee); + } + + function _createSwapOrder( + address _account, + address[] memory _path, + uint256 _amountIn, + uint256 _minOut, + uint256 _triggerRatio, + bool _triggerAboveThreshold, + bool _shouldUnwrap, + uint256 _executionFee + ) private { + uint256 _orderIndex = swapOrdersIndex[_account]; + SwapOrder memory order = SwapOrder( + _account, + _path, + _amountIn, + _minOut, + _triggerRatio, + _triggerAboveThreshold, + _shouldUnwrap, + _executionFee + ); + swapOrdersIndex[_account] = _orderIndex.add(1); + swapOrders[_account][_orderIndex] = order; + + emit CreateSwapOrder( + _account, + _orderIndex, + _path, + _amountIn, + _minOut, + _triggerRatio, + _triggerAboveThreshold, + _shouldUnwrap, + _executionFee + ); + } + + function cancelSwapOrder(uint256 _orderIndex) external nonReentrant { + SwapOrder memory order = swapOrders[msg.sender][_orderIndex]; + require(order.account != address(0), "OrderBook: non-existent order"); + + delete swapOrders[msg.sender][_orderIndex]; + + if (order.path[0] == weth) { + _transferOutETH(order.executionFee.add(order.amountIn), msg.sender); + } else { + IERC20(order.path[0]).safeTransfer(msg.sender, order.amountIn); + _transferOutETH(order.executionFee, msg.sender); + } + + emit CancelSwapOrder( + msg.sender, + _orderIndex, + order.path, + order.amountIn, + order.minOut, + order.triggerRatio, + order.triggerAboveThreshold, + order.shouldUnwrap, + order.executionFee + ); + } + + function getUsdgMinPrice(address _otherToken) public view returns (uint256) { + // USDG_PRECISION is the same as 1 USDG + uint256 redemptionAmount = IVault(vault).getRedemptionAmount(_otherToken, USDG_PRECISION); + uint256 otherTokenPrice = IVault(vault).getMinPrice(_otherToken); + + uint256 otherTokenDecimals = IVault(vault).tokenDecimals(_otherToken); + return redemptionAmount.mul(otherTokenPrice).div(10 ** otherTokenDecimals); + } + + function validateSwapOrderPriceWithTriggerAboveThreshold( + address[] memory _path, + uint256 _triggerRatio + ) public view returns (bool) { + require(_path.length == 2 || _path.length == 3, "OrderBook: invalid _path.length"); + + // limit orders don't need this validation because minOut is enough + // so this validation handles scenarios for stop orders only + // when a user wants to swap when a price of tokenB increases relative to tokenA + address tokenA = _path[0]; + address tokenB = _path[_path.length - 1]; + uint256 tokenAPrice; + uint256 tokenBPrice; + + // 1. USDG doesn't have a price feed so we need to calculate it based on redepmtion amount of a specific token + // That's why USDG price in USD can vary depending on the redepmtion token + // 2. In complex scenarios with path=[USDG, BNB, BTC] we need to know how much BNB we'll get for provided USDG + // to know how much BTC will be received + // That's why in such scenario BNB should be used to determine price of USDG + if (tokenA == usdg) { + // with both _path.length == 2 or 3 we need usdg price against _path[1] + tokenAPrice = getUsdgMinPrice(_path[1]); + } else { + tokenAPrice = IVault(vault).getMinPrice(tokenA); + } + + if (tokenB == usdg) { + tokenBPrice = PRICE_PRECISION; + } else { + tokenBPrice = IVault(vault).getMaxPrice(tokenB); + } + + uint256 currentRatio = tokenBPrice.mul(PRICE_PRECISION).div(tokenAPrice); + + bool isValid = currentRatio > _triggerRatio; + return isValid; + } + + function updateSwapOrder(uint256 _orderIndex, uint256 _minOut, uint256 _triggerRatio, bool _triggerAboveThreshold) external nonReentrant { + SwapOrder storage order = swapOrders[msg.sender][_orderIndex]; + require(order.account != address(0), "OrderBook: non-existent order"); + + order.minOut = _minOut; + order.triggerRatio = _triggerRatio; + order.triggerAboveThreshold = _triggerAboveThreshold; + + emit UpdateSwapOrder( + msg.sender, + _orderIndex, + order.path, + order.amountIn, + _minOut, + _triggerRatio, + _triggerAboveThreshold, + order.shouldUnwrap, + order.executionFee + ); + } + + function executeSwapOrder(address _account, uint256 _orderIndex, address payable _feeReceiver) external nonReentrant { + SwapOrder memory order = swapOrders[_account][_orderIndex]; + require(order.account != address(0), "OrderBook: non-existent order"); + + if (order.triggerAboveThreshold) { + // gas optimisation + // order.minAmount should prevent wrong price execution in case of simple limit order + require( + validateSwapOrderPriceWithTriggerAboveThreshold(order.path, order.triggerRatio), + "OrderBook: invalid price for execution" + ); + } + + delete swapOrders[_account][_orderIndex]; + + IERC20(order.path[0]).safeTransfer(vault, order.amountIn); + + uint256 _amountOut; + if (order.path[order.path.length - 1] == weth && order.shouldUnwrap) { + _amountOut = _swap(order.path, order.minOut, address(this)); + _transferOutETH(_amountOut, payable(order.account)); + } else { + _amountOut = _swap(order.path, order.minOut, order.account); + } + + // pay executor + _transferOutETH(order.executionFee, _feeReceiver); + + emit ExecuteSwapOrder( + _account, + _orderIndex, + order.path, + order.amountIn, + order.minOut, + order.triggerRatio, + order.triggerAboveThreshold, + order.shouldUnwrap, + order.executionFee, + _amountOut + ); + } + + function validatePositionOrderPrice( + bool _triggerAboveThreshold, + uint256 _triggerPrice, + address _indexToken, + bool _maximizePrice, + bool _raise + ) public view returns (uint256, bool) { + uint256 currentPrice = _maximizePrice + ? IVault(vault).getMaxPrice(_indexToken) : IVault(vault).getMinPrice(_indexToken); + bool isPriceValid = _triggerAboveThreshold ? currentPrice > _triggerPrice : currentPrice < _triggerPrice; + if (_raise) { + require(isPriceValid, "OrderBook: invalid price for execution"); + } + return (currentPrice, isPriceValid); + } + + function getDecreaseOrder(address _account, uint256 _orderIndex) override public view returns ( + address collateralToken, + uint256 collateralDelta, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold + ) { + DecreaseOrder memory order = decreaseOrders[_account][_orderIndex]; + return ( + order.collateralToken, + order.collateralDelta, + order.indexToken, + order.sizeDelta, + order.isLong, + order.triggerPrice, + order.triggerAboveThreshold + ); + } + + function getIncreaseOrder(address _account, uint256 _orderIndex) override public view returns ( + address purchaseToken, + uint256 purchaseTokenAmount, + address collateralToken, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold + ) { + IncreaseOrder memory order = increaseOrders[_account][_orderIndex]; + return ( + order.purchaseToken, + order.purchaseTokenAmount, + order.collateralToken, + order.indexToken, + order.sizeDelta, + order.isLong, + order.triggerPrice, + order.triggerAboveThreshold + ); + } + + function createIncreaseOrder( + address[] memory _path, + uint256 _amountIn, + address _indexToken, + uint256 _minOut, + uint256 _sizeDelta, + address _collateralToken, + bool _isLong, + uint256 _triggerPrice, + bool _triggerAboveThreshold, + uint256 _executionFee, + bool _shouldWrap + ) external payable nonReentrant { + // always need this call because of mandatory executionFee user has to transfer in BNB + _transferInETH(); + + require(_executionFee >= minExecutionFee, "OrderBook: insufficient execution fee"); + if (_shouldWrap) { + require(_path[0] == weth, "OrderBook: only weth could be wrapped"); + require(msg.value == _executionFee.add(_amountIn), "OrderBook: incorrect value transferred"); + } else { + require(msg.value == _executionFee, "OrderBook: incorrect execution fee transferred"); + IRouter(router).pluginTransfer(_path[0], msg.sender, address(this), _amountIn); + } + + address _purchaseToken = _path[_path.length - 1]; + uint256 _purchaseTokenAmount; + if (_path.length > 1) { + require(_path[0] != _purchaseToken, "OrderBook: invalid _path"); + IERC20(_path[0]).safeTransfer(vault, _amountIn); + _purchaseTokenAmount = _swap(_path, _minOut, address(this)); + } else { + _purchaseTokenAmount = _amountIn; + } + + { + uint256 _purchaseTokenAmountUsd = IVault(vault).tokenToUsdMin(_purchaseToken, _purchaseTokenAmount); + require(_purchaseTokenAmountUsd >= minPurchaseTokenAmountUsd, "OrderBook: insufficient collateral"); + } + + _createIncreaseOrder( + msg.sender, + _purchaseToken, + _purchaseTokenAmount, + _collateralToken, + _indexToken, + _sizeDelta, + _isLong, + _triggerPrice, + _triggerAboveThreshold, + _executionFee + ); + } + + function _createIncreaseOrder( + address _account, + address _purchaseToken, + uint256 _purchaseTokenAmount, + address _collateralToken, + address _indexToken, + uint256 _sizeDelta, + bool _isLong, + uint256 _triggerPrice, + bool _triggerAboveThreshold, + uint256 _executionFee + ) private { + uint256 _orderIndex = increaseOrdersIndex[msg.sender]; + IncreaseOrder memory order = IncreaseOrder( + _account, + _purchaseToken, + _purchaseTokenAmount, + _collateralToken, + _indexToken, + _sizeDelta, + _isLong, + _triggerPrice, + _triggerAboveThreshold, + _executionFee + ); + increaseOrdersIndex[_account] = _orderIndex.add(1); + increaseOrders[_account][_orderIndex] = order; + + emit CreateIncreaseOrder( + _account, + _orderIndex, + _purchaseToken, + _purchaseTokenAmount, + _indexToken, + _sizeDelta, + _isLong, + _triggerPrice, + _triggerAboveThreshold, + _executionFee + ); + } + + function updateIncreaseOrder(uint256 _orderIndex, uint256 _sizeDelta, uint256 _triggerPrice, bool _triggerAboveThreshold) external nonReentrant { + IncreaseOrder storage order = increaseOrders[msg.sender][_orderIndex]; + require(order.account != address(0), "OrderBook: non-existent order"); + + order.triggerPrice = _triggerPrice; + order.triggerAboveThreshold = _triggerAboveThreshold; + order.sizeDelta = _sizeDelta; + + emit UpdateIncreaseOrder(msg.sender, _orderIndex, _sizeDelta, _triggerPrice, _triggerAboveThreshold); + } + + function cancelIncreaseOrder(uint256 _orderIndex) external nonReentrant { + IncreaseOrder memory order = increaseOrders[msg.sender][_orderIndex]; + require(order.account != address(0), "OrderBook: non-existent order"); + + delete increaseOrders[msg.sender][_orderIndex]; + + if (order.purchaseToken == weth) { + _transferOutETH(order.executionFee.add(order.purchaseTokenAmount), msg.sender); + } else { + IERC20(order.purchaseToken).safeTransfer(msg.sender, order.purchaseTokenAmount); + _transferOutETH(order.executionFee, msg.sender); + } + + emit CancelIncreaseOrder( + order.account, + _orderIndex, + order.purchaseToken, + order.purchaseTokenAmount, + order.indexToken, + order.sizeDelta, + order.isLong, + order.triggerPrice, + order.triggerAboveThreshold, + order.executionFee + ); + } + + function executeIncreaseOrder(address _address, uint256 _orderIndex, address payable _feeReceiver) external nonReentrant { + IncreaseOrder memory order = increaseOrders[_address][_orderIndex]; + require(order.account != address(0), "OrderBook: non-existent order"); + + // increase long should use max price + // increase short should use min price + (uint256 currentPrice, ) = validatePositionOrderPrice( + order.triggerAboveThreshold, + order.triggerPrice, + order.indexToken, + order.isLong, + true + ); + + delete increaseOrders[_address][_orderIndex]; + + IERC20(order.purchaseToken).safeTransfer(vault, order.purchaseTokenAmount); + + if (order.purchaseToken != order.collateralToken) { + address[] memory path = new address[](2); + path[0] = order.purchaseToken; + path[1] = order.collateralToken; + + uint256 amountOut = _swap(path, 0, address(this)); + IERC20(order.collateralToken).safeTransfer(vault, amountOut); + } + + IRouter(router).pluginIncreasePosition(order.account, order.collateralToken, order.indexToken, order.sizeDelta, order.isLong); + + // pay executor + _transferOutETH(order.executionFee, _feeReceiver); + + emit ExecuteIncreaseOrder( + order.account, + _orderIndex, + order.purchaseToken, + order.purchaseTokenAmount, + order.indexToken, + order.sizeDelta, + order.isLong, + order.triggerPrice, + order.triggerAboveThreshold, + order.executionFee, + currentPrice + ); + } + + function createDecreaseOrder( + address _indexToken, + uint256 _sizeDelta, + address _collateralToken, + uint256 _collateralDelta, + bool _isLong, + uint256 _triggerPrice, + bool _triggerAboveThreshold + ) external payable nonReentrant { + _transferInETH(); + + require(msg.value > minExecutionFee, "OrderBook: insufficient execution fee"); + + _createDecreaseOrder( + msg.sender, + _collateralToken, + _collateralDelta, + _indexToken, + _sizeDelta, + _isLong, + _triggerPrice, + _triggerAboveThreshold + ); + } + + function _createDecreaseOrder( + address _account, + address _collateralToken, + uint256 _collateralDelta, + address _indexToken, + uint256 _sizeDelta, + bool _isLong, + uint256 _triggerPrice, + bool _triggerAboveThreshold + ) private { + uint256 _orderIndex = decreaseOrdersIndex[_account]; + DecreaseOrder memory order = DecreaseOrder( + _account, + _collateralToken, + _collateralDelta, + _indexToken, + _sizeDelta, + _isLong, + _triggerPrice, + _triggerAboveThreshold, + msg.value + ); + decreaseOrdersIndex[_account] = _orderIndex.add(1); + decreaseOrders[_account][_orderIndex] = order; + + emit CreateDecreaseOrder( + _account, + _orderIndex, + _collateralToken, + _collateralDelta, + _indexToken, + _sizeDelta, + _isLong, + _triggerPrice, + _triggerAboveThreshold, + msg.value + ); + } + + function executeDecreaseOrder(address _address, uint256 _orderIndex, address payable _feeReceiver) external nonReentrant { + DecreaseOrder memory order = decreaseOrders[_address][_orderIndex]; + require(order.account != address(0), "OrderBook: non-existent order"); + + // decrease long should use min price + // decrease short should use max price + (uint256 currentPrice, ) = validatePositionOrderPrice( + order.triggerAboveThreshold, + order.triggerPrice, + order.indexToken, + !order.isLong, + true + ); + + delete decreaseOrders[_address][_orderIndex]; + + uint256 amountOut = IRouter(router).pluginDecreasePosition( + order.account, + order.collateralToken, + order.indexToken, + order.collateralDelta, + order.sizeDelta, + order.isLong, + address(this) + ); + + // transfer released collateral to user + if (order.collateralToken == weth) { + _transferOutETH(amountOut, payable(order.account)); + } else { + IERC20(order.collateralToken).safeTransfer(order.account, amountOut); + } + + // pay executor + _transferOutETH(order.executionFee, _feeReceiver); + + emit ExecuteDecreaseOrder( + order.account, + _orderIndex, + order.collateralToken, + order.collateralDelta, + order.indexToken, + order.sizeDelta, + order.isLong, + order.triggerPrice, + order.triggerAboveThreshold, + order.executionFee, + currentPrice + ); + } + + function cancelDecreaseOrder(uint256 _orderIndex) external nonReentrant { + DecreaseOrder memory order = decreaseOrders[msg.sender][_orderIndex]; + require(order.account != address(0), "OrderBook: non-existent order"); + + delete decreaseOrders[msg.sender][_orderIndex]; + _transferOutETH(order.executionFee, msg.sender); + + emit CancelDecreaseOrder( + order.account, + _orderIndex, + order.collateralToken, + order.collateralDelta, + order.indexToken, + order.sizeDelta, + order.isLong, + order.triggerPrice, + order.triggerAboveThreshold, + order.executionFee + ); + } + + function updateDecreaseOrder( + uint256 _orderIndex, + uint256 _collateralDelta, + uint256 _sizeDelta, + uint256 _triggerPrice, + bool _triggerAboveThreshold + ) external nonReentrant { + DecreaseOrder storage order = decreaseOrders[msg.sender][_orderIndex]; + require(order.account != address(0), "OrderBook: non-existent order"); + + order.triggerPrice = _triggerPrice; + order.triggerAboveThreshold = _triggerAboveThreshold; + order.sizeDelta = _sizeDelta; + order.collateralDelta = _collateralDelta; + + emit UpdateDecreaseOrder(msg.sender, _orderIndex, _collateralDelta, _sizeDelta, _triggerPrice, _triggerAboveThreshold); + } + + function _transferInETH() private { + if (msg.value != 0) { + IWETH(weth).deposit{value: msg.value}(); + } + } + + function _transferOutETH(uint256 _amountOut, address payable _receiver) private { + IWETH(weth).withdraw(_amountOut); + _receiver.sendValue(_amountOut); + } + + function _swap(address[] memory _path, uint256 _minOut, address _receiver) private returns (uint256) { + if (_path.length == 2) { + return _vaultSwap(_path[0], _path[1], _minOut, _receiver); + } + if (_path.length == 3) { + uint256 midOut = _vaultSwap(_path[0], _path[1], 0, address(this)); + IERC20(_path[1]).safeTransfer(vault, midOut); + return _vaultSwap(_path[1], _path[2], _minOut, _receiver); + } + + revert("OrderBook: invalid _path.length"); + } + + function _vaultSwap(address _tokenIn, address _tokenOut, uint256 _minOut, address _receiver) private returns (uint256) { + uint256 amountOut; + + if (_tokenOut == usdg) { // buyUSDG + amountOut = IVault(vault).buyUSDG(_tokenIn, _receiver); + } else if (_tokenIn == usdg) { // sellUSDG + amountOut = IVault(vault).sellUSDG(_tokenOut, _receiver); + } else { // swap + amountOut = IVault(vault).swap(_tokenIn, _tokenOut, _receiver); + } + + require(amountOut >= _minOut, "OrderBook: insufficient amountOut"); + return amountOut; + } +} diff --git a/contracts/core/Router.sol b/contracts/core/Router.sol new file mode 100644 index 00000000..1a04fac9 --- /dev/null +++ b/contracts/core/Router.sol @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/Address.sol"; + +import "../tokens/interfaces/IWETH.sol"; +import "./interfaces/IVault.sol"; +import "./interfaces/IRouter.sol"; + +contract Router is IRouter { + using SafeMath for uint256; + using SafeERC20 for IERC20; + using Address for address payable; + + address public gov; + + // wrapped BNB / ETH + address public weth; + address public usdg; + address public vault; + + mapping (address => bool) public plugins; + mapping (address => mapping (address => bool)) public approvedPlugins; + + event Swap(address account, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut); + + modifier onlyGov() { + require(msg.sender == gov, "Router: forbidden"); + _; + } + + constructor(address _vault, address _usdg, address _weth) public { + vault = _vault; + usdg = _usdg; + weth = _weth; + + gov = msg.sender; + } + + receive() external payable { + require(msg.sender == weth, "Router: invalid sender"); + } + + function setGov(address _gov) external onlyGov { + gov = _gov; + } + + function addPlugin(address _plugin) external override onlyGov { + plugins[_plugin] = true; + } + + function removePlugin(address _plugin) external onlyGov { + plugins[_plugin] = false; + } + + function approvePlugin(address _plugin) external { + approvedPlugins[msg.sender][_plugin] = true; + } + + function denyPlugin(address _plugin) external { + approvedPlugins[msg.sender][_plugin] = false; + } + + function pluginTransfer(address _token, address _account, address _receiver, uint256 _amount) external override { + _validatePlugin(_account); + IERC20(_token).safeTransferFrom(_account, _receiver, _amount); + } + + function pluginIncreasePosition(address _account, address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong) external override { + _validatePlugin(_account); + IVault(vault).increasePosition(_account, _collateralToken, _indexToken, _sizeDelta, _isLong); + } + + function pluginDecreasePosition(address _account, address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver) external override returns (uint256) { + _validatePlugin(_account); + return IVault(vault).decreasePosition(_account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, _receiver); + } + + function directPoolDeposit(address _token, uint256 _amount) external { + IERC20(_token).safeTransferFrom(_sender(), vault, _amount); + IVault(vault).directPoolDeposit(_token); + } + + function swap(address[] memory _path, uint256 _amountIn, uint256 _minOut, address _receiver) public override { + IERC20(_path[0]).safeTransferFrom(_sender(), vault, _amountIn); + uint256 amountOut = _swap(_path, _minOut, _receiver); + emit Swap(msg.sender, _path[0], _path[_path.length - 1], _amountIn, amountOut); + } + + function swapETHToTokens(address[] memory _path, uint256 _minOut, address _receiver) external payable { + require(_path[0] == weth, "Router: invalid _path"); + _transferETHToVault(); + uint256 amountOut = _swap(_path, _minOut, _receiver); + emit Swap(msg.sender, _path[0], _path[_path.length - 1], msg.value, amountOut); + } + + function swapTokensToETH(address[] memory _path, uint256 _amountIn, uint256 _minOut, address payable _receiver) external { + require(_path[_path.length - 1] == weth, "Router: invalid _path"); + IERC20(_path[0]).safeTransferFrom(_sender(), vault, _amountIn); + uint256 amountOut = _swap(_path, _minOut, address(this)); + _transferOutETH(amountOut, _receiver); + emit Swap(msg.sender, _path[0], _path[_path.length - 1], _amountIn, amountOut); + } + + function increasePosition(address[] memory _path, address _indexToken, uint256 _amountIn, uint256 _minOut, uint256 _sizeDelta, bool _isLong, uint256 _price) external { + if (_amountIn > 0) { + IERC20(_path[0]).safeTransferFrom(_sender(), vault, _amountIn); + } + if (_path.length > 1 && _amountIn > 0) { + uint256 amountOut = _swap(_path, _minOut, address(this)); + IERC20(_path[_path.length - 1]).safeTransfer(vault, amountOut); + } + _increasePosition(_path[_path.length - 1], _indexToken, _sizeDelta, _isLong, _price); + } + + function increasePositionETH(address[] memory _path, address _indexToken, uint256 _minOut, uint256 _sizeDelta, bool _isLong, uint256 _price) external payable { + require(_path[0] == weth, "Router: invalid _path"); + if (msg.value > 0) { + _transferETHToVault(); + } + if (_path.length > 1 && msg.value > 0) { + uint256 amountOut = _swap(_path, _minOut, address(this)); + IERC20(_path[_path.length - 1]).safeTransfer(vault, amountOut); + } + _increasePosition(_path[_path.length - 1], _indexToken, _sizeDelta, _isLong, _price); + } + + function decreasePosition(address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver, uint256 _price) external { + _decreasePosition(_collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, _receiver, _price); + } + + function decreasePositionETH(address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address payable _receiver, uint256 _price) external { + uint256 amountOut = _decreasePosition(_collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, address(this), _price); + _transferOutETH(amountOut, _receiver); + } + + function decreasePositionAndSwap(address[] memory _path, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver, uint256 _price, uint256 _minOut) external { + uint256 amount = _decreasePosition(_path[0], _indexToken, _collateralDelta, _sizeDelta, _isLong, address(this), _price); + IERC20(_path[0]).safeTransfer(vault, amount); + _swap(_path, _minOut, _receiver); + } + + function decreasePositionAndSwapETH(address[] memory _path, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address payable _receiver, uint256 _price, uint256 _minOut) external { + require(_path[_path.length - 1] == weth, "Router: invalid _path"); + uint256 amount = _decreasePosition(_path[0], _indexToken, _collateralDelta, _sizeDelta, _isLong, address(this), _price); + IERC20(_path[0]).safeTransfer(vault, amount); + uint256 amountOut = _swap(_path, _minOut, address(this)); + _transferOutETH(amountOut, _receiver); + } + + function _increasePosition(address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong, uint256 _price) private { + if (_isLong) { + require(IVault(vault).getMaxPrice(_indexToken) <= _price, "Router: mark price higher than limit"); + } else { + require(IVault(vault).getMinPrice(_indexToken) >= _price, "Router: mark price lower than limit"); + } + + IVault(vault).increasePosition(_sender(), _collateralToken, _indexToken, _sizeDelta, _isLong); + } + + function _decreasePosition(address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver, uint256 _price) private returns (uint256) { + if (_isLong) { + require(IVault(vault).getMinPrice(_indexToken) >= _price, "Router: mark price lower than limit"); + } else { + require(IVault(vault).getMaxPrice(_indexToken) <= _price, "Router: mark price higher than limit"); + } + + return IVault(vault).decreasePosition(_sender(), _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, _receiver); + } + + function _transferETHToVault() private { + IWETH(weth).deposit{value: msg.value}(); + IERC20(weth).safeTransfer(vault, msg.value); + } + + function _transferOutETH(uint256 _amountOut, address payable _receiver) private { + IWETH(weth).withdraw(_amountOut); + _receiver.sendValue(_amountOut); + } + + function _swap(address[] memory _path, uint256 _minOut, address _receiver) private returns (uint256) { + if (_path.length == 2) { + return _vaultSwap(_path[0], _path[1], _minOut, _receiver); + } + if (_path.length == 3) { + uint256 midOut = _vaultSwap(_path[0], _path[1], 0, address(this)); + IERC20(_path[1]).safeTransfer(vault, midOut); + return _vaultSwap(_path[1], _path[2], _minOut, _receiver); + } + + revert("Router: invalid _path.length"); + } + + function _vaultSwap(address _tokenIn, address _tokenOut, uint256 _minOut, address _receiver) private returns (uint256) { + uint256 amountOut; + + if (_tokenOut == usdg) { // buyUSDG + amountOut = IVault(vault).buyUSDG(_tokenIn, _receiver); + } else if (_tokenIn == usdg) { // sellUSDG + amountOut = IVault(vault).sellUSDG(_tokenOut, _receiver); + } else { // swap + amountOut = IVault(vault).swap(_tokenIn, _tokenOut, _receiver); + } + + require(amountOut >= _minOut, "Router: insufficient amountOut"); + return amountOut; + } + + function _sender() private view returns (address) { + return msg.sender; + } + + function _validatePlugin(address _account) private view { + require(plugins[msg.sender], "Router: invalid plugin"); + require(approvedPlugins[_account][msg.sender], "Router: plugin not approved"); + } +} diff --git a/contracts/core/Vault.sol b/contracts/core/Vault.sol new file mode 100644 index 00000000..90cb0669 --- /dev/null +++ b/contracts/core/Vault.sol @@ -0,0 +1,1272 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "../tokens/interfaces/IUSDG.sol"; +import "./interfaces/IVault.sol"; +import "./interfaces/IVaultPriceFeed.sol"; + +contract Vault is ReentrancyGuard, IVault { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + struct Position { + uint256 size; + uint256 collateral; + uint256 averagePrice; + uint256 entryFundingRate; + uint256 reserveAmount; + int256 realisedPnl; + uint256 lastIncreasedTime; + } + + uint256 public constant BASIS_POINTS_DIVISOR = 10000; + uint256 public constant FUNDING_RATE_PRECISION = 1000000; + uint256 public constant PRICE_PRECISION = 10 ** 30; + uint256 public constant MIN_LEVERAGE = 10000; // 1x + uint256 public constant USDG_DECIMALS = 18; + uint256 public constant MAX_FEE_BASIS_POINTS = 500; // 5% + uint256 public constant MAX_LIQUIDATION_FEE_USD = 100 * PRICE_PRECISION; // 100 USD + uint256 public constant MIN_FUNDING_RATE_INTERVAL = 1 hours; + uint256 public constant MAX_FUNDING_RATE_FACTOR = 10000; // 1% + + bool public override isInitialized; + bool public override isSwapEnabled = true; + bool public override isLeverageEnabled = true; + + address public errorController; + + address public override router; + address public override priceFeed; + + address public override usdg; + address public override gov; + + uint256 public override whitelistedTokenCount; + + uint256 public override maxLeverage = 50 * 10000; // 50x + + uint256 public override liquidationFeeUsd; + uint256 public override taxBasisPoints = 50; // 0.5% + uint256 public override stableTaxBasisPoints = 20; // 0.2% + uint256 public override mintBurnFeeBasisPoints = 30; // 0.3% + uint256 public override swapFeeBasisPoints = 30; // 0.3% + uint256 public override stableSwapFeeBasisPoints = 4; // 0.04% + uint256 public override marginFeeBasisPoints = 10; // 0.1% + + uint256 public override minProfitTime; + bool public override hasDynamicFees = false; + + uint256 public override fundingInterval = 8 hours; + uint256 public override fundingRateFactor; + uint256 public override stableFundingRateFactor; + uint256 public override totalTokenWeights; + + bool public includeAmmPrice = true; + bool public useSwapPricing = false; + + bool public override inManagerMode = false; + bool public override inPrivateLiquidationMode = false; + + uint256 public override maxGasPrice; + + mapping (address => mapping (address => bool)) public override approvedRouters; + mapping (address => bool) public override isLiquidator; + mapping (address => bool) public override isManager; + + address[] public override allWhitelistedTokens; + + mapping (address => bool) public override whitelistedTokens; + mapping (address => uint256) public override tokenDecimals; + mapping (address => uint256) public override minProfitBasisPoints; + mapping (address => bool) public override stableTokens; + mapping (address => bool) public override shortableTokens; + + // tokenBalances is used only to determine _transferIn values + mapping (address => uint256) public override tokenBalances; + + // tokenWeights allows customisation of index composition + mapping (address => uint256) public override tokenWeights; + + // usdgAmounts tracks the amount of USDG debt for each whitelisted token + mapping (address => uint256) public override usdgAmounts; + + // maxUsdgAmounts allows setting a max amount of USDG debt for a token + mapping (address => uint256) public override maxUsdgAmounts; + + // poolAmounts tracks the number of received tokens that can be used for leverage + // this is tracked separately from tokenBalances to exclude funds that are deposited as margin collateral + mapping (address => uint256) public override poolAmounts; + + // reservedAmounts tracks the number of tokens reserved for open leverage positions + mapping (address => uint256) public override reservedAmounts; + + // bufferAmounts allows specification of an amount to exclude from swaps + // this can be used to ensure a certain amount of liquidity is available for leverage positions + mapping (address => uint256) public override bufferAmounts; + + // guaranteedUsd tracks the amount of USD that is "guaranteed" by opened leverage positions + // this value is used to calculate the redemption values for selling of USDG + // this is an estimated amount, it is possible for the actual guaranteed value to be lower + // in the case of sudden price decreases, the guaranteed value should be corrected + // after liquidations are carried out + mapping (address => uint256) public override guaranteedUsd; + + // cumulativeFundingRates tracks the funding rates based on utilization + mapping (address => uint256) public override cumulativeFundingRates; + // lastFundingTimes tracks the last time funding was updated for a token + mapping (address => uint256) public override lastFundingTimes; + + // positions tracks all open positions + mapping (bytes32 => Position) public positions; + + // feeReserves tracks the amount of fees per token + mapping (address => uint256) public override feeReserves; + + mapping (address => uint256) public override globalShortSizes; + mapping (address => uint256) public override globalShortAveragePrices; + + mapping (uint256 => string) public errors; + + event BuyUSDG(address account, address token, uint256 tokenAmount, uint256 usdgAmount, uint256 feeBasisPoints); + event SellUSDG(address account, address token, uint256 usdgAmount, uint256 tokenAmount, uint256 feeBasisPoints); + event Swap(address account, address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut, uint256 amountOutAfterFees, uint256 feeBasisPoints); + + event IncreasePosition( + bytes32 key, + address account, + address collateralToken, + address indexToken, + uint256 collateralDelta, + uint256 sizeDelta, + bool isLong, + uint256 price, + uint256 fee + ); + event DecreasePosition( + bytes32 key, + address account, + address collateralToken, + address indexToken, + uint256 collateralDelta, + uint256 sizeDelta, + bool isLong, + uint256 price, + uint256 fee + ); + event LiquidatePosition( + bytes32 key, + address account, + address collateralToken, + address indexToken, + bool isLong, + uint256 size, + uint256 collateral, + uint256 reserveAmount, + int256 realisedPnl, + uint256 markPrice + ); + event UpdatePosition( + bytes32 key, + uint256 size, + uint256 collateral, + uint256 averagePrice, + uint256 entryFundingRate, + uint256 reserveAmount, + int256 realisedPnl + ); + event ClosePosition( + bytes32 key, + uint256 size, + uint256 collateral, + uint256 averagePrice, + uint256 entryFundingRate, + uint256 reserveAmount, + int256 realisedPnl + ); + + event UpdateFundingRate(address token, uint256 fundingRate); + event UpdatePnl(bytes32 key, bool hasProfit, uint256 delta); + + event CollectSwapFees(address token, uint256 feeUsd, uint256 feeTokens); + event CollectMarginFees(address token, uint256 feeUsd, uint256 feeTokens); + + event DirectPoolDeposit(address token, uint256 amount); + event IncreasePoolAmount(address token, uint256 amount); + event DecreasePoolAmount(address token, uint256 amount); + event IncreaseUsdgAmount(address token, uint256 amount); + event DecreaseUsdgAmount(address token, uint256 amount); + event IncreaseReservedAmount(address token, uint256 amount); + event DecreaseReservedAmount(address token, uint256 amount); + event IncreaseGuaranteedUsd(address token, uint256 amount); + event DecreaseGuaranteedUsd(address token, uint256 amount); + + // once the parameters are verified to be working correctly, + // gov should be set to a timelock contract or a governance contract + constructor() public { + gov = msg.sender; + } + + function initialize( + address _router, + address _usdg, + address _priceFeed, + uint256 _liquidationFeeUsd, + uint256 _fundingRateFactor, + uint256 _stableFundingRateFactor + ) external { + _onlyGov(); + _validate(!isInitialized, 1); + isInitialized = true; + + router = _router; + usdg = _usdg; + priceFeed = _priceFeed; + liquidationFeeUsd = _liquidationFeeUsd; + fundingRateFactor = _fundingRateFactor; + stableFundingRateFactor = _stableFundingRateFactor; + } + + function setErrorController(address _errorController) external { + _onlyGov(); + errorController = _errorController; + } + + function setError(uint256 _errorCode, string calldata _error) external override { + require(msg.sender == errorController, "Vault: invalid errorController"); + errors[_errorCode] = _error; + } + + function allWhitelistedTokensLength() external override view returns (uint256) { + return allWhitelistedTokens.length; + } + + function setInManagerMode(bool _inManagerMode) external override { + _onlyGov(); + inManagerMode = _inManagerMode; + } + + function setManager(address _manager, bool _isManager) external override { + _onlyGov(); + isManager[_manager] = _isManager; + } + + function setInPrivateLiquidationMode(bool _inPrivateLiquidationMode) external { + _onlyGov(); + inPrivateLiquidationMode = _inPrivateLiquidationMode; + } + + function setLiquidator(address _liquidator, bool _isActive) external { + _onlyGov(); + isLiquidator[_liquidator] = _isActive; + } + + function setIsSwapEnabled(bool _isSwapEnabled) external override { + _onlyGov(); + isSwapEnabled = _isSwapEnabled; + } + + function setIsLeverageEnabled(bool _isLeverageEnabled) external override { + _onlyGov(); + isLeverageEnabled = _isLeverageEnabled; + } + + function setMaxGasPrice(uint256 _maxGasPrice) external override { + _onlyGov(); + maxGasPrice = _maxGasPrice; + } + + function setGov(address _gov) external { + _onlyGov(); + gov = _gov; + } + + function setPriceFeed(address _priceFeed) external override { + _onlyGov(); + priceFeed = _priceFeed; + } + + function setMaxLeverage(uint256 _maxLeverage) external { + _onlyGov(); + _validate(_maxLeverage > MIN_LEVERAGE, 2); + maxLeverage = _maxLeverage; + } + + function setBufferAmount(address _token, uint256 _amount) external override { + _onlyGov(); + bufferAmounts[_token] = _amount; + } + + function setFees( + uint256 _taxBasisPoints, + uint256 _stableTaxBasisPoints, + uint256 _mintBurnFeeBasisPoints, + uint256 _swapFeeBasisPoints, + uint256 _stableSwapFeeBasisPoints, + uint256 _marginFeeBasisPoints, + uint256 _liquidationFeeUsd, + uint256 _minProfitTime, + bool _hasDynamicFees + ) external override { + _onlyGov(); + _validate(_taxBasisPoints <= MAX_FEE_BASIS_POINTS, 3); + _validate(_stableTaxBasisPoints <= MAX_FEE_BASIS_POINTS, 4); + _validate(_mintBurnFeeBasisPoints <= MAX_FEE_BASIS_POINTS, 5); + _validate(_swapFeeBasisPoints <= MAX_FEE_BASIS_POINTS, 6); + _validate(_stableSwapFeeBasisPoints <= MAX_FEE_BASIS_POINTS, 7); + _validate(_marginFeeBasisPoints <= MAX_FEE_BASIS_POINTS, 8); + _validate(_liquidationFeeUsd <= MAX_LIQUIDATION_FEE_USD, 9); + taxBasisPoints = _taxBasisPoints; + stableTaxBasisPoints = _stableTaxBasisPoints; + mintBurnFeeBasisPoints = _mintBurnFeeBasisPoints; + swapFeeBasisPoints = _swapFeeBasisPoints; + stableSwapFeeBasisPoints = _stableSwapFeeBasisPoints; + marginFeeBasisPoints = _marginFeeBasisPoints; + liquidationFeeUsd = _liquidationFeeUsd; + minProfitTime = _minProfitTime; + hasDynamicFees = _hasDynamicFees; + } + + function setFundingRate(uint256 _fundingInterval, uint256 _fundingRateFactor, uint256 _stableFundingRateFactor) external { + _onlyGov(); + _validate(_fundingInterval >= MIN_FUNDING_RATE_INTERVAL, 10); + _validate(_fundingRateFactor <= MAX_FUNDING_RATE_FACTOR, 11); + _validate(_stableFundingRateFactor <= MAX_FUNDING_RATE_FACTOR, 12); + fundingInterval = _fundingInterval; + fundingRateFactor = _fundingRateFactor; + stableFundingRateFactor = _stableFundingRateFactor; + } + + function setTokenConfig( + address _token, + uint256 _tokenDecimals, + uint256 _tokenWeight, + uint256 _minProfitBps, + uint256 _maxUsdgAmount, + bool _isStable, + bool _isShortable + ) external override { + _onlyGov(); + // increment token count for the first time + if (!whitelistedTokens[_token]) { + whitelistedTokenCount = whitelistedTokenCount.add(1); + allWhitelistedTokens.push(_token); + } + + uint256 _totalTokenWeights = totalTokenWeights; + _totalTokenWeights = _totalTokenWeights.sub(tokenWeights[_token]); + + whitelistedTokens[_token] = true; + tokenDecimals[_token] = _tokenDecimals; + tokenWeights[_token] = _tokenWeight; + minProfitBasisPoints[_token] = _minProfitBps; + maxUsdgAmounts[_token] = _maxUsdgAmount; + stableTokens[_token] = _isStable; + shortableTokens[_token] = _isShortable; + + totalTokenWeights = _totalTokenWeights.add(_tokenWeight); + + // validate price feed + getMaxPrice(_token); + } + + function clearTokenConfig(address _token) external { + _onlyGov(); + _validate(whitelistedTokens[_token], 13); + totalTokenWeights = totalTokenWeights.sub(tokenWeights[_token]); + delete whitelistedTokens[_token]; + delete tokenDecimals[_token]; + delete tokenWeights[_token]; + delete minProfitBasisPoints[_token]; + delete maxUsdgAmounts[_token]; + delete stableTokens[_token]; + delete shortableTokens[_token]; + whitelistedTokenCount = whitelistedTokenCount.sub(1); + } + + function withdrawFees(address _token, address _receiver) external override returns (uint256) { + _onlyGov(); + uint256 amount = feeReserves[_token]; + if(amount == 0) { return 0; } + feeReserves[_token] = 0; + _transferOut(_token, amount, _receiver); + return amount; + } + + function addRouter(address _router) external { + approvedRouters[msg.sender][_router] = true; + } + + function removeRouter(address _router) external { + approvedRouters[msg.sender][_router] = false; + } + + function setUsdgAmount(address _token, uint256 _amount) external { + _onlyGov(); + + uint256 usdgAmount = usdgAmounts[_token]; + if (_amount > usdgAmount) { + _increaseUsdgAmount(_token, _amount.sub(usdgAmount)); + return; + } + + _decreaseUsdgAmount(_token, usdgAmount.sub(_amount)); + } + + // the governance controlling this function should have a timelock + function upgradeVault(address _newVault, address _token, uint256 _amount) external { + _onlyGov(); + IERC20(_token).safeTransfer(_newVault, _amount); + } + + // deposit into the pool without minting USDG tokens + // useful in allowing the pool to become over-collaterised + function directPoolDeposit(address _token) external override nonReentrant { + _validate(whitelistedTokens[_token], 14); + uint256 tokenAmount = _transferIn(_token); + _validate(tokenAmount > 0, 15); + _increasePoolAmount(_token, tokenAmount); + emit DirectPoolDeposit(_token, tokenAmount); + } + + function buyUSDG(address _token, address _receiver) external override nonReentrant returns (uint256) { + _validateManager(); + _validate(whitelistedTokens[_token], 16); + useSwapPricing = true; + + uint256 tokenAmount = _transferIn(_token); + _validate(tokenAmount > 0, 17); + + updateCumulativeFundingRate(_token); + + uint256 price = getMinPrice(_token); + + uint256 usdgAmount = tokenAmount.mul(price).div(PRICE_PRECISION); + usdgAmount = adjustForDecimals(usdgAmount, _token, usdg); + _validate(usdgAmount > 0, 18); + + uint256 feeBasisPoints = getFeeBasisPoints(_token, usdgAmount, mintBurnFeeBasisPoints, taxBasisPoints, true); + uint256 amountAfterFees = _collectSwapFees(_token, tokenAmount, feeBasisPoints); + uint256 mintAmount = amountAfterFees.mul(price).div(PRICE_PRECISION); + mintAmount = adjustForDecimals(mintAmount, _token, usdg); + + _increaseUsdgAmount(_token, mintAmount); + _increasePoolAmount(_token, amountAfterFees); + + IUSDG(usdg).mint(_receiver, mintAmount); + + emit BuyUSDG(_receiver, _token, tokenAmount, mintAmount, feeBasisPoints); + + useSwapPricing = false; + return mintAmount; + } + + function sellUSDG(address _token, address _receiver) external override nonReentrant returns (uint256) { + _validateManager(); + _validate(whitelistedTokens[_token], 19); + useSwapPricing = true; + + uint256 usdgAmount = _transferIn(usdg); + _validate(usdgAmount > 0, 20); + + updateCumulativeFundingRate(_token); + + uint256 redemptionAmount = getRedemptionAmount(_token, usdgAmount); + _validate(redemptionAmount > 0, 21); + + _decreaseUsdgAmount(_token, usdgAmount); + _decreasePoolAmount(_token, redemptionAmount); + + IUSDG(usdg).burn(address(this), usdgAmount); + + // the _transferIn call increased the value of tokenBalances[usdg] + // usually decreases in token balances are synced by calling _transferOut + // however, for usdg, the tokens are burnt, so _updateTokenBalance should + // be manually called to record the decrease in tokens + _updateTokenBalance(usdg); + + uint256 feeBasisPoints = getFeeBasisPoints(_token, usdgAmount, mintBurnFeeBasisPoints, taxBasisPoints, false); + uint256 amountOut = _collectSwapFees(_token, redemptionAmount, feeBasisPoints); + _validate(amountOut > 0, 22); + + _transferOut(_token, amountOut, _receiver); + + emit SellUSDG(_receiver, _token, usdgAmount, amountOut, feeBasisPoints); + + useSwapPricing = false; + return amountOut; + } + + function swap(address _tokenIn, address _tokenOut, address _receiver) external override nonReentrant returns (uint256) { + _validate(isSwapEnabled, 23); + _validate(whitelistedTokens[_tokenIn], 24); + _validate(whitelistedTokens[_tokenOut], 25); + _validate(_tokenIn != _tokenOut, 26); + + useSwapPricing = true; + + updateCumulativeFundingRate(_tokenIn); + updateCumulativeFundingRate(_tokenOut); + + uint256 amountIn = _transferIn(_tokenIn); + _validate(amountIn > 0, 27); + + uint256 priceIn = getMinPrice(_tokenIn); + uint256 priceOut = getMaxPrice(_tokenOut); + + uint256 amountOut = amountIn.mul(priceIn).div(priceOut); + amountOut = adjustForDecimals(amountOut, _tokenIn, _tokenOut); + + // adjust usdgAmounts by the same usdgAmount as debt is shifted between the assets + uint256 usdgAmount = amountIn.mul(priceIn).div(PRICE_PRECISION); + usdgAmount = adjustForDecimals(usdgAmount, _tokenIn, usdg); + + bool isStableSwap = stableTokens[_tokenIn] && stableTokens[_tokenOut]; + uint256 feeBasisPoints; + { + uint256 baseBps = isStableSwap ? stableSwapFeeBasisPoints : swapFeeBasisPoints; + uint256 taxBps = isStableSwap ? stableTaxBasisPoints : taxBasisPoints; + uint256 feesBasisPoints0 = getFeeBasisPoints(_tokenIn, usdgAmount, baseBps, taxBps, true); + uint256 feesBasisPoints1 = getFeeBasisPoints(_tokenOut, usdgAmount, baseBps, taxBps, false); + // use the higher of the two fee basis points + feeBasisPoints = feesBasisPoints0 > feesBasisPoints1 ? feesBasisPoints0 : feesBasisPoints1; + } + uint256 amountOutAfterFees = _collectSwapFees(_tokenOut, amountOut, feeBasisPoints); + + _increaseUsdgAmount(_tokenIn, usdgAmount); + _decreaseUsdgAmount(_tokenOut, usdgAmount); + + _increasePoolAmount(_tokenIn, amountIn); + _decreasePoolAmount(_tokenOut, amountOut); + + _validateBufferAmount(_tokenOut); + + _transferOut(_tokenOut, amountOutAfterFees, _receiver); + + emit Swap(_receiver, _tokenIn, _tokenOut, amountIn, amountOut, amountOutAfterFees, feeBasisPoints); + + useSwapPricing = false; + return amountOutAfterFees; + } + + function increasePosition(address _account, address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong) external override nonReentrant { + _validate(isLeverageEnabled, 28); + _validateGasPrice(); + _validateRouter(_account); + _validateTokens(_collateralToken, _indexToken, _isLong); + updateCumulativeFundingRate(_collateralToken); + + bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong); + Position storage position = positions[key]; + + uint256 price = _isLong ? getMaxPrice(_indexToken) : getMinPrice(_indexToken); + + if (position.size == 0) { + position.averagePrice = price; + } + + if (position.size > 0 && _sizeDelta > 0) { + position.averagePrice = getNextAveragePrice(_indexToken, position.size, position.averagePrice, _isLong, price, _sizeDelta, position.lastIncreasedTime); + } + + uint256 fee = _collectMarginFees(_collateralToken, _sizeDelta, position.size, position.entryFundingRate); + uint256 collateralDelta = _transferIn(_collateralToken); + uint256 collateralDeltaUsd = tokenToUsdMin(_collateralToken, collateralDelta); + + position.collateral = position.collateral.add(collateralDeltaUsd); + _validate(position.collateral >= fee, 29); + + position.collateral = position.collateral.sub(fee); + position.entryFundingRate = cumulativeFundingRates[_collateralToken]; + position.size = position.size.add(_sizeDelta); + position.lastIncreasedTime = block.timestamp; + + _validate(position.size > 0, 30); + _validatePosition(position.size, position.collateral); + validateLiquidation(_account, _collateralToken, _indexToken, _isLong, true); + + // reserve tokens to pay profits on the position + uint256 reserveDelta = usdToTokenMax(_collateralToken, _sizeDelta); + position.reserveAmount = position.reserveAmount.add(reserveDelta); + _increaseReservedAmount(_collateralToken, reserveDelta); + + if (_isLong) { + // guaranteedUsd stores the sum of (position.size - position.collateral) for all positions + // if a fee is charged on the collateral then guaranteedUsd should be increased by that fee amount + // since (position.size - position.collateral) would have increased by `fee` + _increaseGuaranteedUsd(_collateralToken, _sizeDelta.add(fee)); + _decreaseGuaranteedUsd(_collateralToken, collateralDeltaUsd); + // treat the deposited collateral as part of the pool + _increasePoolAmount(_collateralToken, collateralDelta); + // fees need to be deducted from the pool since fees are deducted from position.collateral + // and collateral is treated as part of the pool + _decreasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, fee)); + } else { + if (globalShortSizes[_indexToken] == 0) { + globalShortAveragePrices[_indexToken] = price; + } else { + globalShortAveragePrices[_indexToken] = getNextGlobalShortAveragePrice(_indexToken, price, _sizeDelta); + } + globalShortSizes[_indexToken] = globalShortSizes[_indexToken].add(_sizeDelta); + } + + emit IncreasePosition(key, _account, _collateralToken, _indexToken, collateralDeltaUsd, _sizeDelta, _isLong, price, fee); + emit UpdatePosition(key, position.size, position.collateral, position.averagePrice, position.entryFundingRate, position.reserveAmount, position.realisedPnl); + } + + function decreasePosition(address _account, address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver) external override nonReentrant returns (uint256) { + _validateGasPrice(); + _validateRouter(_account); + return _decreasePosition(_account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, _receiver); + } + + function _decreasePosition(address _account, address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver) private returns (uint256) { + updateCumulativeFundingRate(_collateralToken); + + bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong); + Position storage position = positions[key]; + _validate(position.size > 0, 31); + _validate(position.size >= _sizeDelta, 32); + _validate(position.collateral >= _collateralDelta, 33); + + uint256 collateral = position.collateral; + // scrop variables to avoid stack too deep errors + { + uint256 reserveDelta = position.reserveAmount.mul(_sizeDelta).div(position.size); + position.reserveAmount = position.reserveAmount.sub(reserveDelta); + _decreaseReservedAmount(_collateralToken, reserveDelta); + } + + (uint256 usdOut, uint256 usdOutAfterFee) = _reduceCollateral(_account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong); + + if (position.size != _sizeDelta) { + position.entryFundingRate = cumulativeFundingRates[_collateralToken]; + position.size = position.size.sub(_sizeDelta); + + _validatePosition(position.size, position.collateral); + validateLiquidation(_account, _collateralToken, _indexToken, _isLong, true); + + if (_isLong) { + _increaseGuaranteedUsd(_collateralToken, collateral.sub(position.collateral)); + _decreaseGuaranteedUsd(_collateralToken, _sizeDelta); + } + + uint256 price = _isLong ? getMinPrice(_indexToken) : getMaxPrice(_indexToken); + emit DecreasePosition(key, _account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, price, usdOut.sub(usdOutAfterFee)); + emit UpdatePosition(key, position.size, position.collateral, position.averagePrice, position.entryFundingRate, position.reserveAmount, position.realisedPnl); + } else { + if (_isLong) { + _increaseGuaranteedUsd(_collateralToken, collateral); + _decreaseGuaranteedUsd(_collateralToken, _sizeDelta); + } + + uint256 price = _isLong ? getMinPrice(_indexToken) : getMaxPrice(_indexToken); + emit DecreasePosition(key, _account, _collateralToken, _indexToken, _collateralDelta, _sizeDelta, _isLong, price, usdOut.sub(usdOutAfterFee)); + emit ClosePosition(key, position.size, position.collateral, position.averagePrice, position.entryFundingRate, position.reserveAmount, position.realisedPnl); + + delete positions[key]; + } + + if (!_isLong) { + _decreaseGlobalShortSize(_indexToken, _sizeDelta); + } + + if (usdOut > 0) { + if (_isLong) { + _decreasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, usdOut)); + } + uint256 amountOutAfterFees = usdToTokenMin(_collateralToken, usdOutAfterFee); + _transferOut(_collateralToken, amountOutAfterFees, _receiver); + return amountOutAfterFees; + } + + return 0; + } + + function liquidatePosition(address _account, address _collateralToken, address _indexToken, bool _isLong, address _feeReceiver) external nonReentrant { + if (inPrivateLiquidationMode) { + _validate(isLiquidator[msg.sender], 34); + } + + // set includeAmmPrice to false prevent manipulated liquidations + includeAmmPrice = false; + + updateCumulativeFundingRate(_collateralToken); + + bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong); + Position memory position = positions[key]; + _validate(position.size > 0, 35); + + (uint256 liquidationState, uint256 marginFees) = validateLiquidation(_account, _collateralToken, _indexToken, _isLong, false); + _validate(liquidationState != 0, 36); + if (liquidationState == 2) { + // max leverage exceeded but there is collateral remaining after deducting losses so decreasePosition instead + _decreasePosition(_account, _collateralToken, _indexToken, 0, position.size, _isLong, _account); + return; + } + + uint256 feeTokens = usdToTokenMin(_collateralToken, marginFees); + feeReserves[_collateralToken] = feeReserves[_collateralToken].add(feeTokens); + emit CollectMarginFees(_collateralToken, marginFees, feeTokens); + + _decreaseReservedAmount(_collateralToken, position.reserveAmount); + if (_isLong) { + _decreaseGuaranteedUsd(_collateralToken, position.size.sub(position.collateral)); + _decreasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, marginFees)); + } + + uint256 markPrice = _isLong ? getMinPrice(_indexToken) : getMaxPrice(_indexToken); + emit LiquidatePosition(key, _account, _collateralToken, _indexToken, _isLong, position.size, position.collateral, position.reserveAmount, position.realisedPnl, markPrice); + + if (!_isLong && marginFees < position.collateral) { + uint256 remainingCollateral = position.collateral.sub(marginFees); + _increasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, remainingCollateral)); + } + + if (!_isLong) { + _decreaseGlobalShortSize(_indexToken, position.size); + } + + delete positions[key]; + + // pay the fee receiver using the pool, we assume that in general the liquidated amount should be sufficient to cover + // the liquidation fees + _decreasePoolAmount(_collateralToken, usdToTokenMin(_collateralToken, liquidationFeeUsd)); + _transferOut(_collateralToken, usdToTokenMin(_collateralToken, liquidationFeeUsd), _feeReceiver); + + includeAmmPrice = true; + } + + // validateLiquidation returns (state, fees) + function validateLiquidation(address _account, address _collateralToken, address _indexToken, bool _isLong, bool _raise) public view returns (uint256, uint256) { + bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong); + Position memory position = positions[key]; + + (bool hasProfit, uint256 delta) = getDelta(_indexToken, position.size, position.averagePrice, _isLong, position.lastIncreasedTime); + uint256 marginFees = getFundingFee(_collateralToken, position.size, position.entryFundingRate); + marginFees = marginFees.add(getPositionFee(position.size)); + + if (!hasProfit && position.collateral < delta) { + if (_raise) { revert("Vault: losses exceed collateral"); } + return (1, marginFees); + } + + uint256 remainingCollateral = position.collateral; + if (!hasProfit) { + remainingCollateral = position.collateral.sub(delta); + } + + if (remainingCollateral < marginFees) { + if (_raise) { revert("Vault: fees exceed collateral"); } + // cap the fees to the remainingCollateral + return (1, remainingCollateral); + } + + if (remainingCollateral < marginFees.add(liquidationFeeUsd)) { + if (_raise) { revert("Vault: liquidation fees exceed collateral"); } + return (1, marginFees); + } + + if (remainingCollateral.mul(maxLeverage) < position.size.mul(BASIS_POINTS_DIVISOR)) { + if (_raise) { revert("Vault: maxLeverage exceeded"); } + return (2, marginFees); + } + + return (0, marginFees); + } + + function getMaxPrice(address _token) public override view returns (uint256) { + return IVaultPriceFeed(priceFeed).getPrice(_token, true, includeAmmPrice, useSwapPricing); + } + + function getMinPrice(address _token) public override view returns (uint256) { + return IVaultPriceFeed(priceFeed).getPrice(_token, false, includeAmmPrice, useSwapPricing); + } + + function getRedemptionAmount(address _token, uint256 _usdgAmount) public override view returns (uint256) { + uint256 price = getMaxPrice(_token); + uint256 redemptionAmount = _usdgAmount.mul(PRICE_PRECISION).div(price); + return adjustForDecimals(redemptionAmount, usdg, _token); + } + + function getRedemptionCollateral(address _token) public view returns (uint256) { + if (stableTokens[_token]) { + return poolAmounts[_token]; + } + uint256 collateral = usdToTokenMin(_token, guaranteedUsd[_token]); + return collateral.add(poolAmounts[_token]).sub(reservedAmounts[_token]); + } + + function getRedemptionCollateralUsd(address _token) public view returns (uint256) { + return tokenToUsdMin(_token, getRedemptionCollateral(_token)); + } + + function adjustForDecimals(uint256 _amount, address _tokenDiv, address _tokenMul) public view returns (uint256) { + uint256 decimalsDiv = _tokenDiv == usdg ? USDG_DECIMALS : tokenDecimals[_tokenDiv]; + uint256 decimalsMul = _tokenMul == usdg ? USDG_DECIMALS : tokenDecimals[_tokenMul]; + return _amount.mul(10 ** decimalsMul).div(10 ** decimalsDiv); + } + + function tokenToUsdMin(address _token, uint256 _tokenAmount) public override view returns (uint256) { + if (_tokenAmount == 0) { return 0; } + uint256 price = getMinPrice(_token); + uint256 decimals = tokenDecimals[_token]; + return _tokenAmount.mul(price).div(10 ** decimals); + } + + function usdToTokenMax(address _token, uint256 _usdAmount) public view returns (uint256) { + if (_usdAmount == 0) { return 0; } + return usdToToken(_token, _usdAmount, getMinPrice(_token)); + } + + function usdToTokenMin(address _token, uint256 _usdAmount) public view returns (uint256) { + if (_usdAmount == 0) { return 0; } + return usdToToken(_token, _usdAmount, getMaxPrice(_token)); + } + + function usdToToken(address _token, uint256 _usdAmount, uint256 _price) public view returns (uint256) { + if (_usdAmount == 0) { return 0; } + uint256 decimals = tokenDecimals[_token]; + return _usdAmount.mul(10 ** decimals).div(_price); + } + + function getPosition(address _account, address _collateralToken, address _indexToken, bool _isLong) public override view returns (uint256, uint256, uint256, uint256, uint256, uint256, bool, uint256) { + bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong); + Position memory position = positions[key]; + uint256 realisedPnl = position.realisedPnl > 0 ? uint256(position.realisedPnl) : uint256(-position.realisedPnl); + return ( + position.size, // 0 + position.collateral, // 1 + position.averagePrice, // 2 + position.entryFundingRate, // 3 + position.reserveAmount, // 4 + realisedPnl, // 5 + position.realisedPnl >= 0, // 6 + position.lastIncreasedTime // 7 + ); + } + + function getPositionKey(address _account, address _collateralToken, address _indexToken, bool _isLong) public pure returns (bytes32) { + return keccak256(abi.encodePacked( + _account, + _collateralToken, + _indexToken, + _isLong + )); + } + + function updateCumulativeFundingRate(address _token) public { + if (lastFundingTimes[_token] == 0) { + lastFundingTimes[_token] = block.timestamp.div(fundingInterval).mul(fundingInterval); + return; + } + + if (lastFundingTimes[_token].add(fundingInterval) > block.timestamp) { + return; + } + + uint256 fundingRate = getNextFundingRate(_token); + cumulativeFundingRates[_token] = cumulativeFundingRates[_token].add(fundingRate); + lastFundingTimes[_token] = block.timestamp.div(fundingInterval).mul(fundingInterval); + + emit UpdateFundingRate(_token, cumulativeFundingRates[_token]); + } + + function getNextFundingRate(address _token) public override view returns (uint256) { + if (lastFundingTimes[_token].add(fundingInterval) > block.timestamp) { return 0; } + + uint256 intervals = block.timestamp.sub(lastFundingTimes[_token]).div(fundingInterval); + uint256 poolAmount = poolAmounts[_token]; + if (poolAmount == 0) { return 0; } + + uint256 _fundingRateFactor = stableTokens[_token] ? stableFundingRateFactor : fundingRateFactor; + return _fundingRateFactor.mul(reservedAmounts[_token]).mul(intervals).div(poolAmount); + } + + function getUtilisation(address _token) public view returns (uint256) { + uint256 poolAmount = poolAmounts[_token]; + if (poolAmount == 0) { return 0; } + + return reservedAmounts[_token].mul(FUNDING_RATE_PRECISION).div(poolAmount); + } + + function getPositionLeverage(address _account, address _collateralToken, address _indexToken, bool _isLong) public view returns (uint256) { + bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong); + Position memory position = positions[key]; + _validate(position.collateral > 0, 37); + return position.size.mul(BASIS_POINTS_DIVISOR).div(position.collateral); + } + + // for longs: nextAveragePrice = (nextPrice * nextSize)/ (nextSize + delta) + // for shorts: nextAveragePrice = (nextPrice * nextSize) / (nextSize - delta) + function getNextAveragePrice(address _indexToken, uint256 _size, uint256 _averagePrice, bool _isLong, uint256 _nextPrice, uint256 _sizeDelta, uint256 _lastIncreasedTime) public view returns (uint256) { + (bool hasProfit, uint256 delta) = getDelta(_indexToken, _size, _averagePrice, _isLong, _lastIncreasedTime); + uint256 nextSize = _size.add(_sizeDelta); + uint256 divisor; + if (_isLong) { + divisor = hasProfit ? nextSize.add(delta) : nextSize.sub(delta); + } else { + divisor = hasProfit ? nextSize.sub(delta) : nextSize.add(delta); + } + return _nextPrice.mul(nextSize).div(divisor); + } + + // for longs: nextAveragePrice = (nextPrice * nextSize)/ (nextSize + delta) + // for shorts: nextAveragePrice = (nextPrice * nextSize) / (nextSize - delta) + function getNextGlobalShortAveragePrice(address _indexToken, uint256 _nextPrice, uint256 _sizeDelta) public view returns (uint256) { + uint256 size = globalShortSizes[_indexToken]; + uint256 averagePrice = globalShortAveragePrices[_indexToken]; + uint256 priceDelta = averagePrice > _nextPrice ? averagePrice.sub(_nextPrice) : _nextPrice.sub(averagePrice); + uint256 delta = size.mul(priceDelta).div(averagePrice); + bool hasProfit = averagePrice > _nextPrice; + + uint256 nextSize = size.add(_sizeDelta); + uint256 divisor = hasProfit ? nextSize.sub(delta) : nextSize.add(delta); + + return _nextPrice.mul(nextSize).div(divisor); + } + + function getGlobalShortDelta(address _token) public view returns (bool, uint256) { + uint256 size = globalShortSizes[_token]; + if (size == 0) { return (false, 0); } + + uint256 nextPrice = getMaxPrice(_token); + uint256 averagePrice = globalShortAveragePrices[_token]; + uint256 priceDelta = averagePrice > nextPrice ? averagePrice.sub(nextPrice) : nextPrice.sub(averagePrice); + uint256 delta = size.mul(priceDelta).div(averagePrice); + bool hasProfit = averagePrice > nextPrice; + + return (hasProfit, delta); + } + + function getPositionDelta(address _account, address _collateralToken, address _indexToken, bool _isLong) public view returns (bool, uint256) { + bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong); + Position memory position = positions[key]; + return getDelta(_indexToken, position.size, position.averagePrice, _isLong, position.lastIncreasedTime); + } + + function getDelta(address _indexToken, uint256 _size, uint256 _averagePrice, bool _isLong, uint256 _lastIncreasedTime) public override view returns (bool, uint256) { + _validate(_averagePrice > 0, 38); + uint256 price = _isLong ? getMinPrice(_indexToken) : getMaxPrice(_indexToken); + uint256 priceDelta = _averagePrice > price ? _averagePrice.sub(price) : price.sub(_averagePrice); + uint256 delta = _size.mul(priceDelta).div(_averagePrice); + + bool hasProfit; + + if (_isLong) { + hasProfit = price > _averagePrice; + } else { + hasProfit = _averagePrice > price; + } + + // if the minProfitTime has passed then there will be no min profit threshold + // the min profit threshold helps to prevent front-running issues + uint256 minBps = block.timestamp > _lastIncreasedTime.add(minProfitTime) ? 0 : minProfitBasisPoints[_indexToken]; + if (hasProfit && delta.mul(BASIS_POINTS_DIVISOR) <= _size.mul(minBps)) { + delta = 0; + } + + return (hasProfit, delta); + } + + function getFundingFee(address _token, uint256 _size, uint256 _entryFundingRate) public view returns (uint256) { + if (_size == 0) { return 0; } + + uint256 fundingRate = cumulativeFundingRates[_token].sub(_entryFundingRate); + if (fundingRate == 0) { return 0; } + + return _size.mul(fundingRate).div(FUNDING_RATE_PRECISION); + } + + function getPositionFee(uint256 _sizeDelta) public view returns (uint256) { + if (_sizeDelta == 0) { return 0; } + uint256 afterFeeUsd = _sizeDelta.mul(BASIS_POINTS_DIVISOR.sub(marginFeeBasisPoints)).div(BASIS_POINTS_DIVISOR); + return _sizeDelta.sub(afterFeeUsd); + } + + // cases to consider + // 1. initialAmount is far from targetAmount, action increases balance slightly => high rebate + // 2. initialAmount is far from targetAmount, action increases balance largely => high rebate + // 3. initialAmount is close to targetAmount, action increases balance slightly => low rebate + // 4. initialAmount is far from targetAmount, action reduces balance slightly => high tax + // 5. initialAmount is far from targetAmount, action reduces balance largely => high tax + // 6. initialAmount is close to targetAmount, action reduces balance largely => low tax + // 7. initialAmount is above targetAmount, nextAmount is below targetAmount and vice versa + // 8. a large swap should have similar fees as the same trade split into multiple smaller swaps + function getFeeBasisPoints(address _token, uint256 _usdgDelta, uint256 _feeBasisPoints, uint256 _taxBasisPoints, bool _increment) public override view returns (uint256) { + if (!hasDynamicFees) { return _feeBasisPoints; } + + uint256 initialAmount = usdgAmounts[_token]; + uint256 nextAmount = initialAmount.add(_usdgDelta); + if (!_increment) { + nextAmount = _usdgDelta > initialAmount ? 0 : initialAmount.sub(_usdgDelta); + } + + uint256 targetAmount = getTargetUsdgAmount(_token); + if (targetAmount == 0) { return _feeBasisPoints; } + + uint256 initialDiff = initialAmount > targetAmount ? initialAmount.sub(targetAmount) : targetAmount.sub(initialAmount); + uint256 nextDiff = nextAmount > targetAmount ? nextAmount.sub(targetAmount) : targetAmount.sub(nextAmount); + + // action improves relative asset balance + if (nextDiff < initialDiff) { + uint256 rebateBps = _taxBasisPoints.mul(initialDiff).div(targetAmount); + return rebateBps > _feeBasisPoints ? 0 : _feeBasisPoints.sub(rebateBps); + } + + uint256 averageDiff = initialDiff.add(nextDiff).div(2); + if (averageDiff > targetAmount) { + averageDiff = targetAmount; + } + uint256 taxBps = _taxBasisPoints.mul(averageDiff).div(targetAmount); + return _feeBasisPoints.add(taxBps); + } + + function getTargetUsdgAmount(address _token) public view returns (uint256) { + uint256 supply = IERC20(usdg).totalSupply(); + if (supply == 0) { return 0; } + uint256 weight = tokenWeights[_token]; + return weight.mul(supply).div(totalTokenWeights); + } + + function _reduceCollateral(address _account, address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong) private returns (uint256, uint256) { + bytes32 key = getPositionKey(_account, _collateralToken, _indexToken, _isLong); + Position storage position = positions[key]; + + uint256 fee = _collectMarginFees(_collateralToken, _sizeDelta, position.size, position.entryFundingRate); + bool hasProfit; + uint256 adjustedDelta; + + // scope variables to avoid stack too deep errors + { + (bool _hasProfit, uint256 delta) = getDelta(_indexToken, position.size, position.averagePrice, _isLong, position.lastIncreasedTime); + hasProfit = _hasProfit; + // get the proportional change in pnl + adjustedDelta = _sizeDelta.mul(delta).div(position.size); + } + + uint256 usdOut; + // transfer profits out + if (hasProfit && adjustedDelta > 0) { + usdOut = adjustedDelta; + position.realisedPnl = position.realisedPnl + int256(adjustedDelta); + + // pay out realised profits from the pool amount for short positions + if (!_isLong) { + uint256 tokenAmount = usdToTokenMin(_collateralToken, adjustedDelta); + _decreasePoolAmount(_collateralToken, tokenAmount); + } + } + + if (!hasProfit && adjustedDelta > 0) { + position.collateral = position.collateral.sub(adjustedDelta); + + // transfer realised losses to the pool for short positions + // realised losses for long positions are not transferred here as + // _increasePoolAmount was already called in increasePosition for longs + if (!_isLong) { + uint256 tokenAmount = usdToTokenMin(_collateralToken, adjustedDelta); + _increasePoolAmount(_collateralToken, tokenAmount); + } + + position.realisedPnl = position.realisedPnl - int256(adjustedDelta); + } + + // reduce the position's collateral by _collateralDelta + // transfer _collateralDelta out + if (_collateralDelta > 0) { + usdOut = usdOut.add(_collateralDelta); + position.collateral = position.collateral.sub(_collateralDelta); + } + + // if the position will be closed, then transfer the remaining collateral out + if (position.size == _sizeDelta) { + usdOut = usdOut.add(position.collateral); + position.collateral = 0; + } + + // if the usdOut is more than the fee then deduct the fee from the usdOut directly + // else deduct the fee from the position's collateral + uint256 usdOutAfterFee = usdOut; + if (usdOut > fee) { + usdOutAfterFee = usdOut.sub(fee); + } else { + position.collateral = position.collateral.sub(fee); + if (_isLong) { + uint256 feeTokens = usdToTokenMin(_collateralToken, fee); + _decreasePoolAmount(_collateralToken, feeTokens); + } + } + + emit UpdatePnl(key, hasProfit, adjustedDelta); + + return (usdOut, usdOutAfterFee); + } + + function _validatePosition(uint256 _size, uint256 _collateral) private view { + if (_size == 0) { + _validate(_collateral == 0, 39); + return; + } + _validate(_size >= _collateral, 40); + } + + function _validateRouter(address _account) private view { + if (msg.sender == _account) { return; } + if (msg.sender == router) { return; } + _validate(approvedRouters[_account][msg.sender], 41); + } + + function _validateTokens(address _collateralToken, address _indexToken, bool _isLong) private view { + if (_isLong) { + _validate(_collateralToken == _indexToken, 42); + _validate(whitelistedTokens[_collateralToken], 43); + _validate(!stableTokens[_collateralToken], 44); + return; + } + + _validate(whitelistedTokens[_collateralToken], 45); + _validate(stableTokens[_collateralToken], 46); + _validate(!stableTokens[_indexToken], 47); + _validate(shortableTokens[_indexToken], 48); + } + + function _collectSwapFees(address _token, uint256 _amount, uint256 _feeBasisPoints) private returns (uint256) { + uint256 afterFeeAmount = _amount.mul(BASIS_POINTS_DIVISOR.sub(_feeBasisPoints)).div(BASIS_POINTS_DIVISOR); + uint256 feeAmount = _amount.sub(afterFeeAmount); + feeReserves[_token] = feeReserves[_token].add(feeAmount); + emit CollectSwapFees(_token, tokenToUsdMin(_token, feeAmount), feeAmount); + return afterFeeAmount; + } + + function _collectMarginFees(address _token, uint256 _sizeDelta, uint256 _size, uint256 _entryFundingRate) private returns (uint256) { + uint256 feeUsd = getPositionFee(_sizeDelta); + + uint256 fundingFee = getFundingFee(_token, _size, _entryFundingRate); + feeUsd = feeUsd.add(fundingFee); + + uint256 feeTokens = usdToTokenMin(_token, feeUsd); + feeReserves[_token] = feeReserves[_token].add(feeTokens); + + emit CollectMarginFees(_token, feeUsd, feeTokens); + return feeUsd; + } + + function _transferIn(address _token) private returns (uint256) { + uint256 prevBalance = tokenBalances[_token]; + uint256 nextBalance = IERC20(_token).balanceOf(address(this)); + tokenBalances[_token] = nextBalance; + + return nextBalance.sub(prevBalance); + } + + function _transferOut(address _token, uint256 _amount, address _receiver) private { + IERC20(_token).safeTransfer(_receiver, _amount); + tokenBalances[_token] = IERC20(_token).balanceOf(address(this)); + } + + function _updateTokenBalance(address _token) private { + uint256 nextBalance = IERC20(_token).balanceOf(address(this)); + tokenBalances[_token] = nextBalance; + } + + function _increasePoolAmount(address _token, uint256 _amount) private { + poolAmounts[_token] = poolAmounts[_token].add(_amount); + uint256 balance = IERC20(_token).balanceOf(address(this)); + _validate(poolAmounts[_token] <= balance, 49); + emit IncreasePoolAmount(_token, _amount); + } + + function _decreasePoolAmount(address _token, uint256 _amount) private { + poolAmounts[_token] = poolAmounts[_token].sub(_amount, "Vault: poolAmount exceeded"); + _validate(reservedAmounts[_token] <= poolAmounts[_token], 50); + emit DecreasePoolAmount(_token, _amount); + } + + function _validateBufferAmount(address _token) private view { + if (poolAmounts[_token] < bufferAmounts[_token]) { + revert("Vault: poolAmount < buffer"); + } + } + + function _increaseUsdgAmount(address _token, uint256 _amount) private { + usdgAmounts[_token] = usdgAmounts[_token].add(_amount); + uint256 maxUsdgAmount = maxUsdgAmounts[_token]; + if (maxUsdgAmount != 0) { + _validate(usdgAmounts[_token] <= maxUsdgAmount, 51); + } + emit IncreaseUsdgAmount(_token, _amount); + } + + function _decreaseUsdgAmount(address _token, uint256 _amount) private { + uint256 value = usdgAmounts[_token]; + // since USDG can be minted using multiple assets + // it is possible for the USDG debt for a single asset to be less than zero + // the USDG debt is capped to zero for this case + if (value <= _amount) { + usdgAmounts[_token] = 0; + emit DecreaseUsdgAmount(_token, value); + return; + } + usdgAmounts[_token] = value.sub(_amount); + emit DecreaseUsdgAmount(_token, _amount); + } + + function _increaseReservedAmount(address _token, uint256 _amount) private { + reservedAmounts[_token] = reservedAmounts[_token].add(_amount); + _validate(reservedAmounts[_token] <= poolAmounts[_token], 52); + emit IncreaseReservedAmount(_token, _amount); + } + + function _decreaseReservedAmount(address _token, uint256 _amount) private { + reservedAmounts[_token] = reservedAmounts[_token].sub(_amount, "Vault: insufficient reserve"); + emit DecreaseReservedAmount(_token, _amount); + } + + function _increaseGuaranteedUsd(address _token, uint256 _usdAmount) private { + guaranteedUsd[_token] = guaranteedUsd[_token].add(_usdAmount); + emit IncreaseGuaranteedUsd(_token, _usdAmount); + } + + function _decreaseGuaranteedUsd(address _token, uint256 _usdAmount) private { + guaranteedUsd[_token] = guaranteedUsd[_token].sub(_usdAmount); + emit DecreaseGuaranteedUsd(_token, _usdAmount); + } + + function _decreaseGlobalShortSize(address _token, uint256 _amount) private { + uint256 size = globalShortSizes[_token]; + if (_amount > size) { + globalShortSizes[_token] = 0; + return; + } + + globalShortSizes[_token] = size.sub(_amount); + } + + // we have this validation as a function instead of a modifier to reduce contract size + function _onlyGov() private view { + _validate(msg.sender == gov, 53); + } + + // we have this validation as a function instead of a modifier to reduce contract size + function _validateManager() private view { + if (inManagerMode) { + _validate(isManager[msg.sender], 54); + } + } + + // we have this validation as a function instead of a modifier to reduce contract size + function _validateGasPrice() private view { + if (maxGasPrice == 0) { return; } + _validate(tx.gasprice <= maxGasPrice, 55); + } + + function _validate(bool _condition, uint256 _errorCode) private view { + require(_condition, errors[_errorCode]); + } +} diff --git a/contracts/core/VaultErrorController.sol b/contracts/core/VaultErrorController.sol new file mode 100644 index 00000000..c4629c50 --- /dev/null +++ b/contracts/core/VaultErrorController.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import "./interfaces/IVault.sol"; +import "../access/Governable.sol"; + +contract VaultErrorController is Governable { + function setErrors(IVault _vault, string[] calldata _errors) external onlyGov { + for (uint256 i = 0; i < _errors.length; i++) { + _vault.setError(i, _errors[i]); + } + } +} diff --git a/contracts/core/VaultPriceFeed.sol b/contracts/core/VaultPriceFeed.sol new file mode 100644 index 00000000..127c625c --- /dev/null +++ b/contracts/core/VaultPriceFeed.sol @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: MIT + +import "../libraries/math/SafeMath.sol"; + +import "./interfaces/IVaultPriceFeed.sol"; +import "../oracle/interfaces/IPriceFeed.sol"; +import "../oracle/interfaces/ISecondaryPriceFeed.sol"; +import "../oracle/interfaces/IChainlinkFlags.sol"; +import "../amm/interfaces/IPancakePair.sol"; + +pragma solidity 0.6.12; + +contract VaultPriceFeed is IVaultPriceFeed { + using SafeMath for uint256; + + uint256 public constant PRICE_PRECISION = 10 ** 30; + uint256 public constant ONE_USD = PRICE_PRECISION; + uint256 public constant BASIS_POINTS_DIVISOR = 10000; + uint256 public constant MAX_SPREAD_BASIS_POINTS = 50; + uint256 public constant MAX_ADJUSTMENT_INTERVAL = 2 hours; + uint256 public constant MAX_ADJUSTMENT_BASIS_POINTS = 20; + + // Identifier of the Sequencer offline flag on the Flags contract + address constant private FLAG_ARBITRUM_SEQ_OFFLINE = address(bytes20(bytes32(uint256(keccak256("chainlink.flags.arbitrum-seq-offline")) - 1))); + + address public gov; + address public chainlinkFlags; + + bool public isAmmEnabled = true; + bool public isSecondaryPriceEnabled = true; + bool public useV2Pricing = false; + bool public favorPrimaryPrice = false; + uint256 public priceSampleSpace = 3; + uint256 public maxStrictPriceDeviation = 0; + address public secondaryPriceFeed; + uint256 public spreadThresholdBasisPoints = 30; + + address public btc; + address public eth; + address public bnb; + address public bnbBusd; + address public ethBnb; + address public btcBnb; + + mapping (address => address) public priceFeeds; + mapping (address => uint256) public priceDecimals; + mapping (address => uint256) public spreadBasisPoints; + // Chainlink can return prices for stablecoins + // that differs from 1 USD by a larger percentage than stableSwapFeeBasisPoints + // we use strictStableTokens to cap the price to 1 USD + // this allows us to configure stablecoins like DAI as being a stableToken + // while not being a strictStableToken + mapping (address => bool) public strictStableTokens; + + mapping (address => uint256) public override adjustmentBasisPoints; + mapping (address => bool) public override isAdjustmentAdditive; + mapping (address => uint256) public lastAdjustmentTimings; + + modifier onlyGov() { + require(msg.sender == gov, "VaultPriceFeed: forbidden"); + _; + } + + constructor() public { + gov = msg.sender; + } + + function setGov(address _gov) external onlyGov { + gov = _gov; + } + + function setChainlinkFlags(address _chainlinkFlags) external onlyGov { + chainlinkFlags = _chainlinkFlags; + } + + function setAdjustment(address _token, bool _isAdditive, uint256 _adjustmentBps) external override onlyGov { + require( + lastAdjustmentTimings[_token].add(MAX_ADJUSTMENT_INTERVAL) < block.timestamp, + "VaultPriceFeed: adjustment frequency exceeded" + ); + require(_adjustmentBps <= MAX_ADJUSTMENT_BASIS_POINTS, "invalid _adjustmentBps"); + isAdjustmentAdditive[_token] = _isAdditive; + adjustmentBasisPoints[_token] = _adjustmentBps; + lastAdjustmentTimings[_token] = block.timestamp; + } + + function setUseV2Pricing(bool _useV2Pricing) external override onlyGov { + useV2Pricing = _useV2Pricing; + } + + function setIsAmmEnabled(bool _isEnabled) external override onlyGov { + isAmmEnabled = _isEnabled; + } + + function setIsSecondaryPriceEnabled(bool _isEnabled) external override onlyGov { + isSecondaryPriceEnabled = _isEnabled; + } + + function setSecondaryPriceFeed(address _secondaryPriceFeed) external onlyGov { + secondaryPriceFeed = _secondaryPriceFeed; + } + + function setTokens(address _btc, address _eth, address _bnb) external onlyGov { + btc = _btc; + eth = _eth; + bnb = _bnb; + } + + function setPairs(address _bnbBusd, address _ethBnb, address _btcBnb) external onlyGov { + bnbBusd = _bnbBusd; + ethBnb = _ethBnb; + btcBnb = _btcBnb; + } + + function setSpreadBasisPoints(address _token, uint256 _spreadBasisPoints) external override onlyGov { + require(_spreadBasisPoints <= MAX_SPREAD_BASIS_POINTS, "VaultPriceFeed: invalid _spreadBasisPoints"); + spreadBasisPoints[_token] = _spreadBasisPoints; + } + + function setSpreadThresholdBasisPoints(uint256 _spreadThresholdBasisPoints) external override onlyGov { + spreadThresholdBasisPoints = _spreadThresholdBasisPoints; + } + + function setFavorPrimaryPrice(bool _favorPrimaryPrice) external override onlyGov { + favorPrimaryPrice = _favorPrimaryPrice; + } + + function setPriceSampleSpace(uint256 _priceSampleSpace) external override onlyGov { + require(_priceSampleSpace > 0, "VaultPriceFeed: invalid _priceSampleSpace"); + priceSampleSpace = _priceSampleSpace; + } + + function setMaxStrictPriceDeviation(uint256 _maxStrictPriceDeviation) external override onlyGov { + maxStrictPriceDeviation = _maxStrictPriceDeviation; + } + + function setTokenConfig( + address _token, + address _priceFeed, + uint256 _priceDecimals, + bool _isStrictStable + ) external onlyGov { + priceFeeds[_token] = _priceFeed; + priceDecimals[_token] = _priceDecimals; + strictStableTokens[_token] = _isStrictStable; + } + + function getPrice(address _token, bool _maximise, bool _includeAmmPrice, bool /* _useSwapPricing */) public override view returns (uint256) { + uint256 price = useV2Pricing ? getPriceV2(_token, _maximise, _includeAmmPrice) : getPriceV1(_token, _maximise, _includeAmmPrice); + + uint256 adjustmentBps = adjustmentBasisPoints[_token]; + if (adjustmentBps > 0) { + bool isAdditive = isAdjustmentAdditive[_token]; + if (isAdditive) { + price = price.mul(BASIS_POINTS_DIVISOR.add(adjustmentBps)).div(BASIS_POINTS_DIVISOR); + } else { + price = price.mul(BASIS_POINTS_DIVISOR.sub(adjustmentBps)).div(BASIS_POINTS_DIVISOR); + } + } + + return price; + } + + function getPriceV1(address _token, bool _maximise, bool _includeAmmPrice) public view returns (uint256) { + uint256 price = getPrimaryPrice(_token, _maximise); + + if (_includeAmmPrice && isAmmEnabled) { + uint256 ammPrice = getAmmPrice(_token); + if (ammPrice > 0) { + if (_maximise && ammPrice > price) { + price = ammPrice; + } + if (!_maximise && ammPrice < price) { + price = ammPrice; + } + } + } + + if (isSecondaryPriceEnabled) { + price = getSecondaryPrice(_token, price, _maximise); + } + + if (strictStableTokens[_token]) { + uint256 delta = price > ONE_USD ? price.sub(ONE_USD) : ONE_USD.sub(price); + if (delta <= maxStrictPriceDeviation) { + return ONE_USD; + } + } + + uint256 _spreadBasisPoints = spreadBasisPoints[_token]; + + if (_maximise) { + return price.mul(BASIS_POINTS_DIVISOR.add(_spreadBasisPoints)).div(BASIS_POINTS_DIVISOR); + } + + return price.mul(BASIS_POINTS_DIVISOR.sub(_spreadBasisPoints)).div(BASIS_POINTS_DIVISOR); + } + + function getPriceV2(address _token, bool _maximise, bool _includeAmmPrice) public view returns (uint256) { + uint256 price = getPrimaryPrice(_token, _maximise); + + if (_includeAmmPrice && isAmmEnabled) { + price = getAmmPriceV2(_token, _maximise, price); + } + + if (isSecondaryPriceEnabled) { + price = getSecondaryPrice(_token, price, _maximise); + } + + if (strictStableTokens[_token]) { + uint256 delta = price > ONE_USD ? price.sub(ONE_USD) : ONE_USD.sub(price); + if (delta <= maxStrictPriceDeviation) { + return ONE_USD; + } + } + + uint256 _spreadBasisPoints = spreadBasisPoints[_token]; + + if (_maximise) { + return price.mul(BASIS_POINTS_DIVISOR.add(_spreadBasisPoints)).div(BASIS_POINTS_DIVISOR); + } + + return price.mul(BASIS_POINTS_DIVISOR.sub(_spreadBasisPoints)).div(BASIS_POINTS_DIVISOR); + } + + function getAmmPriceV2(address _token, bool _maximise, uint256 _primaryPrice) public view returns (uint256) { + uint256 ammPrice = getAmmPrice(_token); + if (ammPrice == 0) { + return _primaryPrice; + } + + uint256 diff = ammPrice > _primaryPrice ? ammPrice.sub(_primaryPrice) : _primaryPrice.sub(ammPrice); + if (diff.mul(BASIS_POINTS_DIVISOR) < _primaryPrice.mul(spreadThresholdBasisPoints)) { + if (favorPrimaryPrice) { + return _primaryPrice; + } + return ammPrice; + } + + if (_maximise && ammPrice > _primaryPrice) { + return ammPrice; + } + + if (!_maximise && ammPrice < _primaryPrice) { + return ammPrice; + } + + return _primaryPrice; + } + + function getPrimaryPrice(address _token, bool _maximise) public override view returns (uint256) { + address priceFeedAddress = priceFeeds[_token]; + require(priceFeedAddress != address(0), "VaultPriceFeed: invalid price feed"); + + if (chainlinkFlags != address(0)) { + bool isRaised = IChainlinkFlags(chainlinkFlags).getFlag(FLAG_ARBITRUM_SEQ_OFFLINE); + if (isRaised) { + // If flag is raised we shouldn't perform any critical operations + revert("Chainlink feeds are not being updated"); + } + } + + IPriceFeed priceFeed = IPriceFeed(priceFeedAddress); + + uint256 price = 0; + uint80 roundId = priceFeed.latestRound(); + + for (uint80 i = 0; i < priceSampleSpace; i++) { + if (roundId <= i) { break; } + uint256 p; + + if (i == 0) { + int256 _p = priceFeed.latestAnswer(); + require(_p > 0, "VaultPriceFeed: invalid price"); + p = uint256(_p); + } else { + (, int256 _p, , ,) = priceFeed.getRoundData(roundId - i); + require(_p > 0, "VaultPriceFeed: invalid price"); + p = uint256(_p); + } + + if (price == 0) { + price = p; + continue; + } + + if (_maximise && p > price) { + price = p; + continue; + } + + if (!_maximise && p < price) { + price = p; + } + } + + require(price > 0, "VaultPriceFeed: could not fetch price"); + // normalise price precision + uint256 _priceDecimals = priceDecimals[_token]; + return price.mul(PRICE_PRECISION).div(10 ** _priceDecimals); + } + + function getSecondaryPrice(address _token, uint256 _referencePrice, bool _maximise) public view returns (uint256) { + if (secondaryPriceFeed == address(0)) { return _referencePrice; } + return ISecondaryPriceFeed(secondaryPriceFeed).getPrice(_token, _referencePrice, _maximise); + } + + function getAmmPrice(address _token) public override view returns (uint256) { + if (_token == bnb) { + // for bnbBusd, reserve0: BNB, reserve1: BUSD + return getPairPrice(bnbBusd, true); + } + + if (_token == eth) { + uint256 price0 = getPairPrice(bnbBusd, true); + // for ethBnb, reserve0: ETH, reserve1: BNB + uint256 price1 = getPairPrice(ethBnb, true); + // this calculation could overflow if (price0 / 10**30) * (price1 / 10**30) is more than 10**17 + return price0.mul(price1).div(PRICE_PRECISION); + } + + if (_token == btc) { + uint256 price0 = getPairPrice(bnbBusd, true); + // for btcBnb, reserve0: BTC, reserve1: BNB + uint256 price1 = getPairPrice(btcBnb, true); + // this calculation could overflow if (price0 / 10**30) * (price1 / 10**30) is more than 10**17 + return price0.mul(price1).div(PRICE_PRECISION); + } + + return 0; + } + + // if divByReserve0: calculate price as reserve1 / reserve0 + // if !divByReserve1: calculate price as reserve0 / reserve1 + function getPairPrice(address _pair, bool _divByReserve0) public view returns (uint256) { + (uint256 reserve0, uint256 reserve1, ) = IPancakePair(_pair).getReserves(); + if (_divByReserve0) { + if (reserve0 == 0) { return 0; } + return reserve1.mul(PRICE_PRECISION).div(reserve0); + } + if (reserve1 == 0) { return 0; } + return reserve0.mul(PRICE_PRECISION).div(reserve1); + } +} diff --git a/contracts/core/interfaces/IGlpManager.sol b/contracts/core/interfaces/IGlpManager.sol new file mode 100644 index 00000000..253b52f2 --- /dev/null +++ b/contracts/core/interfaces/IGlpManager.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IGlpManager { + function addLiquidity(address _token, uint256 _amount, uint256 _minUsdg, uint256 _minGlp) external returns (uint256); + function addLiquidityForAccount(address _fundingAccount, address _account, address _token, uint256 _amount, uint256 _minUsdg, uint256 _minGlp) external returns (uint256); + function removeLiquidity(address _tokenOut, uint256 _glpAmount, uint256 _minOut, address _receiver) external returns (uint256); + function removeLiquidityForAccount(address _account, address _tokenOut, uint256 _glpAmount, uint256 _minOut, address _receiver) external returns (uint256); +} diff --git a/contracts/core/interfaces/IOrderBook.sol b/contracts/core/interfaces/IOrderBook.sol new file mode 100644 index 00000000..51bab9b6 --- /dev/null +++ b/contracts/core/interfaces/IOrderBook.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IOrderBook { + function getSwapOrder(address _account, uint256 _orderIndex) external view returns ( + address, + address, + address, + uint256, + uint256, + uint256, + bool + ); + + function getIncreaseOrder(address _account, uint256 _orderIndex) external view returns ( + address purchaseToken, + uint256 purchaseTokenAmount, + address collateralToken, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold + ); + + function getDecreaseOrder(address _account, uint256 _orderIndex) external view returns ( + address collateralToken, + uint256 collateralDelta, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold + ); +} \ No newline at end of file diff --git a/contracts/core/interfaces/IRouter.sol b/contracts/core/interfaces/IRouter.sol new file mode 100644 index 00000000..02208811 --- /dev/null +++ b/contracts/core/interfaces/IRouter.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IRouter { + function addPlugin(address _plugin) external; + function pluginTransfer(address _token, address _account, address _receiver, uint256 _amount) external; + function pluginIncreasePosition(address _account, address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong) external; + function pluginDecreasePosition(address _account, address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver) external returns (uint256); + function swap(address[] memory _path, uint256 _amountIn, uint256 _minOut, address _receiver) external; +} diff --git a/contracts/core/interfaces/IVault.sol b/contracts/core/interfaces/IVault.sol new file mode 100644 index 00000000..eee5d9de --- /dev/null +++ b/contracts/core/interfaces/IVault.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IVault { + function isInitialized() external view returns (bool); + function isSwapEnabled() external view returns (bool); + function isLeverageEnabled() external view returns (bool); + + function setError(uint256 _errorCode, string calldata _error) external; + + function router() external view returns (address); + function usdg() external view returns (address); + function gov() external view returns (address); + + function whitelistedTokenCount() external view returns (uint256); + function maxLeverage() external view returns (uint256); + + function minProfitTime() external view returns (uint256); + function hasDynamicFees() external view returns (bool); + function fundingInterval() external view returns (uint256); + function totalTokenWeights() external view returns (uint256); + + function inManagerMode() external view returns (bool); + function inPrivateLiquidationMode() external view returns (bool); + + function maxGasPrice() external view returns (uint256); + + function approvedRouters(address _account, address _router) external view returns (bool); + function isLiquidator(address _account) external view returns (bool); + function isManager(address _account) external view returns (bool); + + function minProfitBasisPoints(address _token) external view returns (uint256); + function tokenBalances(address _token) external view returns (uint256); + function lastFundingTimes(address _token) external view returns (uint256); + + function setInManagerMode(bool _inManagerMode) external; + function setManager(address _manager, bool _isManager) external; + function setIsSwapEnabled(bool _isSwapEnabled) external; + function setIsLeverageEnabled(bool _isLeverageEnabled) external; + function setMaxGasPrice(uint256 _maxGasPrice) external; + function setBufferAmount(address _token, uint256 _amount) external; + + function setFees( + uint256 _taxBasisPoints, + uint256 _stableTaxBasisPoints, + uint256 _mintBurnFeeBasisPoints, + uint256 _swapFeeBasisPoints, + uint256 _stableSwapFeeBasisPoints, + uint256 _marginFeeBasisPoints, + uint256 _liquidationFeeUsd, + uint256 _minProfitTime, + bool _hasDynamicFees + ) external; + + function setTokenConfig( + address _token, + uint256 _tokenDecimals, + uint256 _redemptionBps, + uint256 _minProfitBps, + uint256 _maxUsdgAmount, + bool _isStable, + bool _isShortable + ) external; + + function setPriceFeed(address _priceFeed) external; + function withdrawFees(address _token, address _receiver) external returns (uint256); + + function directPoolDeposit(address _token) external; + function buyUSDG(address _token, address _receiver) external returns (uint256); + function sellUSDG(address _token, address _receiver) external returns (uint256); + function swap(address _tokenIn, address _tokenOut, address _receiver) external returns (uint256); + function increasePosition(address _account, address _collateralToken, address _indexToken, uint256 _sizeDelta, bool _isLong) external; + function decreasePosition(address _account, address _collateralToken, address _indexToken, uint256 _collateralDelta, uint256 _sizeDelta, bool _isLong, address _receiver) external returns (uint256); + function tokenToUsdMin(address _token, uint256 _tokenAmount) external view returns (uint256); + + function priceFeed() external view returns (address); + function fundingRateFactor() external view returns (uint256); + function stableFundingRateFactor() external view returns (uint256); + function cumulativeFundingRates(address _token) external view returns (uint256); + function getNextFundingRate(address _token) external view returns (uint256); + function getFeeBasisPoints(address _token, uint256 _usdgDelta, uint256 _feeBasisPoints, uint256 _taxBasisPoints, bool _increment) external view returns (uint256); + + function liquidationFeeUsd() external view returns (uint256); + function taxBasisPoints() external view returns (uint256); + function stableTaxBasisPoints() external view returns (uint256); + function mintBurnFeeBasisPoints() external view returns (uint256); + function swapFeeBasisPoints() external view returns (uint256); + function stableSwapFeeBasisPoints() external view returns (uint256); + function marginFeeBasisPoints() external view returns (uint256); + + function allWhitelistedTokensLength() external view returns (uint256); + function allWhitelistedTokens(uint256) external view returns (address); + function whitelistedTokens(address _token) external view returns (bool); + function stableTokens(address _token) external view returns (bool); + function shortableTokens(address _token) external view returns (bool); + function feeReserves(address _token) external view returns (uint256); + function globalShortSizes(address _token) external view returns (uint256); + function globalShortAveragePrices(address _token) external view returns (uint256); + function tokenDecimals(address _token) external view returns (uint256); + function tokenWeights(address _token) external view returns (uint256); + function guaranteedUsd(address _token) external view returns (uint256); + function poolAmounts(address _token) external view returns (uint256); + function bufferAmounts(address _token) external view returns (uint256); + function reservedAmounts(address _token) external view returns (uint256); + function usdgAmounts(address _token) external view returns (uint256); + function maxUsdgAmounts(address _token) external view returns (uint256); + function getRedemptionAmount(address _token, uint256 _usdgAmount) external view returns (uint256); + function getMaxPrice(address _token) external view returns (uint256); + function getMinPrice(address _token) external view returns (uint256); + + function getDelta(address _indexToken, uint256 _size, uint256 _averagePrice, bool _isLong, uint256 _lastIncreasedTime) external view returns (bool, uint256); + function getPosition(address _account, address _collateralToken, address _indexToken, bool _isLong) external view returns (uint256, uint256, uint256, uint256, uint256, uint256, bool, uint256); +} diff --git a/contracts/core/interfaces/IVaultPriceFeed.sol b/contracts/core/interfaces/IVaultPriceFeed.sol new file mode 100644 index 00000000..843309fb --- /dev/null +++ b/contracts/core/interfaces/IVaultPriceFeed.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IVaultPriceFeed { + function adjustmentBasisPoints(address _token) external view returns (uint256); + function isAdjustmentAdditive(address _token) external view returns (bool); + function setAdjustment(address _token, bool _isAdditive, uint256 _adjustmentBps) external; + function setUseV2Pricing(bool _useV2Pricing) external; + function setIsAmmEnabled(bool _isEnabled) external; + function setIsSecondaryPriceEnabled(bool _isEnabled) external; + function setSpreadBasisPoints(address _token, uint256 _spreadBasisPoints) external; + function setSpreadThresholdBasisPoints(uint256 _spreadThresholdBasisPoints) external; + function setFavorPrimaryPrice(bool _favorPrimaryPrice) external; + function setPriceSampleSpace(uint256 _priceSampleSpace) external; + function setMaxStrictPriceDeviation(uint256 _maxStrictPriceDeviation) external; + function getPrice(address _token, bool _maximise, bool _includeAmmPrice, bool _useSwapPricing) external view returns (uint256); + function getAmmPrice(address _token) external view returns (uint256); + function getPrimaryPrice(address _token, bool _maximise) external view returns (uint256); +} diff --git a/contracts/gambit-token/GMT.sol b/contracts/gambit-token/GMT.sol new file mode 100644 index 00000000..cf095567 --- /dev/null +++ b/contracts/gambit-token/GMT.sol @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "./interfaces/IGMT.sol"; +import "../peripherals/interfaces/ITimelockTarget.sol"; + +contract GMT is IERC20, IGMT, ITimelockTarget { + using SafeMath for uint256; + + string public constant name = "Gambit"; + string public constant symbol = "GMT"; + uint8 public constant decimals = 18; + + uint256 public override totalSupply; + address public gov; + + bool public hasActiveMigration; + uint256 public migrationTime; + + mapping (address => uint256) public balances; + mapping (address => mapping (address => uint256)) public allowances; + + mapping (address => bool) public admins; + + // only checked when hasActiveMigration is true + // this can be used to block the AMM pair as a recipient + // and protect liquidity providers during a migration + // by disabling the selling of GMT + mapping (address => bool) public blockedRecipients; + + // only checked when hasActiveMigration is true + // this can be used for: + // - only allowing tokens to be transferred by the distribution contract + // during the initial distribution phase, this would prevent token buyers + // from adding liquidity before the initial liquidity is seeded + // - only allowing removal of GMT liquidity and no other actions + // during the migration phase + mapping (address => bool) public allowedMsgSenders; + + modifier onlyGov() { + require(msg.sender == gov, "GMT: forbidden"); + _; + } + + modifier onlyAdmin() { + require(admins[msg.sender], "GMT: forbidden"); + _; + } + + constructor(uint256 _initialSupply) public { + gov = msg.sender; + admins[msg.sender] = true; + _mint(msg.sender, _initialSupply); + } + + function setGov(address _gov) external override onlyGov { + gov = _gov; + } + + function addAdmin(address _account) external onlyGov { + admins[_account] = true; + } + + function removeAdmin(address _account) external onlyGov { + admins[_account] = false; + } + + function setNextMigrationTime(uint256 _migrationTime) external onlyGov { + require(_migrationTime > migrationTime, "GMT: invalid _migrationTime"); + migrationTime = _migrationTime; + } + + function beginMigration() external override onlyAdmin { + require(block.timestamp > migrationTime, "GMT: migrationTime not yet passed"); + hasActiveMigration = true; + } + + function endMigration() external override onlyAdmin { + hasActiveMigration = false; + } + + function addBlockedRecipient(address _recipient) external onlyGov { + blockedRecipients[_recipient] = true; + } + + function removeBlockedRecipient(address _recipient) external onlyGov { + blockedRecipients[_recipient] = false; + } + + function addMsgSender(address _msgSender) external onlyGov { + allowedMsgSenders[_msgSender] = true; + } + + function removeMsgSender(address _msgSender) external onlyGov { + allowedMsgSenders[_msgSender] = false; + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external override onlyGov { + IERC20(_token).transfer(_account, _amount); + } + + function balanceOf(address _account) external view override returns (uint256) { + return balances[_account]; + } + + function transfer(address _recipient, uint256 _amount) external override returns (bool) { + _transfer(msg.sender, _recipient, _amount); + return true; + } + + function allowance(address _owner, address _spender) external view override returns (uint256) { + return allowances[_owner][_spender]; + } + + function approve(address _spender, uint256 _amount) external override returns (bool) { + _approve(msg.sender, _spender, _amount); + return true; + } + + function transferFrom(address _sender, address _recipient, uint256 _amount) external override returns (bool) { + uint256 nextAllowance = allowances[_sender][msg.sender].sub(_amount, "GMT: transfer amount exceeds allowance"); + _approve(_sender, msg.sender, nextAllowance); + _transfer(_sender, _recipient, _amount); + return true; + } + + function _transfer(address _sender, address _recipient, uint256 _amount) private { + require(_sender != address(0), "GMT: transfer from the zero address"); + require(_recipient != address(0), "GMT: transfer to the zero address"); + + if (hasActiveMigration) { + require(allowedMsgSenders[msg.sender], "GMT: forbidden msg.sender"); + require(!blockedRecipients[_recipient], "GMT: forbidden recipient"); + } + + balances[_sender] = balances[_sender].sub(_amount, "GMT: transfer amount exceeds balance"); + balances[_recipient] = balances[_recipient].add(_amount); + + emit Transfer(_sender, _recipient,_amount); + } + + function _mint(address _account, uint256 _amount) private { + require(_account != address(0), "GMT: mint to the zero address"); + + totalSupply = totalSupply.add(_amount); + balances[_account] = balances[_account].add(_amount); + + emit Transfer(address(0), _account, _amount); + } + + function _approve(address _owner, address _spender, uint256 _amount) private { + require(_owner != address(0), "GMT: approve from the zero address"); + require(_spender != address(0), "GMT: approve to the zero address"); + + allowances[_owner][_spender] = _amount; + + emit Approval(_owner, _spender, _amount); + } +} diff --git a/contracts/gambit-token/Treasury.sol b/contracts/gambit-token/Treasury.sol new file mode 100644 index 00000000..02fde975 --- /dev/null +++ b/contracts/gambit-token/Treasury.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "../amm/interfaces/IPancakeRouter.sol"; +import "./interfaces/IGMT.sol"; +import "../peripherals/interfaces/ITimelockTarget.sol"; + +contract Treasury is ReentrancyGuard, ITimelockTarget { + using SafeMath for uint256; + + uint256 constant PRECISION = 1000000; + uint256 constant BASIS_POINTS_DIVISOR = 10000; + + bool public isInitialized; + bool public isSwapActive = true; + bool public isLiquidityAdded = false; + + address public gmt; + address public busd; + address public router; + address public fund; + + uint256 public gmtPresalePrice; + uint256 public gmtListingPrice; + uint256 public busdSlotCap; + uint256 public busdHardCap; + uint256 public busdBasisPoints; + uint256 public unlockTime; + + uint256 public busdReceived; + + address public gov; + + mapping (address => uint256) public swapAmounts; + mapping (address => bool) public swapWhitelist; + + modifier onlyGov() { + require(msg.sender == gov, "Treasury: forbidden"); + _; + } + + constructor() public { + gov = msg.sender; + } + + function initialize( + address[] memory _addresses, + uint256[] memory _values + ) external onlyGov { + require(!isInitialized, "Treasury: already initialized"); + isInitialized = true; + + gmt = _addresses[0]; + busd = _addresses[1]; + router = _addresses[2]; + fund = _addresses[3]; + + gmtPresalePrice = _values[0]; + gmtListingPrice = _values[1]; + busdSlotCap = _values[2]; + busdHardCap = _values[3]; + busdBasisPoints = _values[4]; + unlockTime = _values[5]; + } + + function setGov(address _gov) external override onlyGov nonReentrant { + gov = _gov; + } + + function setFund(address _fund) external onlyGov nonReentrant { + fund = _fund; + } + + function extendUnlockTime(uint256 _unlockTime) external onlyGov nonReentrant { + require(_unlockTime > unlockTime, "Treasury: invalid _unlockTime"); + unlockTime = _unlockTime; + } + + function addWhitelists(address[] memory _accounts) external onlyGov nonReentrant { + for (uint256 i = 0; i < _accounts.length; i++) { + address account = _accounts[i]; + swapWhitelist[account] = true; + } + } + + function removeWhitelists(address[] memory _accounts) external onlyGov nonReentrant { + for (uint256 i = 0; i < _accounts.length; i++) { + address account = _accounts[i]; + swapWhitelist[account] = false; + } + } + + function updateWhitelist(address prevAccount, address nextAccount) external onlyGov nonReentrant { + require(swapWhitelist[prevAccount], "Treasury: invalid prevAccount"); + swapWhitelist[prevAccount] = false; + swapWhitelist[nextAccount] = true; + } + + function swap(uint256 _busdAmount) external nonReentrant { + address account = msg.sender; + require(swapWhitelist[account], "Treasury: forbidden"); + require(isSwapActive, "Treasury: swap is no longer active"); + require(_busdAmount > 0, "Treasury: invalid _busdAmount"); + + busdReceived = busdReceived.add(_busdAmount); + require(busdReceived <= busdHardCap, "Treasury: busdHardCap exceeded"); + + swapAmounts[account] = swapAmounts[account].add(_busdAmount); + require(swapAmounts[account] <= busdSlotCap, "Treasury: busdSlotCap exceeded"); + + // receive BUSD + uint256 busdBefore = IERC20(busd).balanceOf(address(this)); + IERC20(busd).transferFrom(account, address(this), _busdAmount); + uint256 busdAfter = IERC20(busd).balanceOf(address(this)); + require(busdAfter.sub(busdBefore) == _busdAmount, "Treasury: invalid transfer"); + + // send GMT + uint256 gmtAmount = _busdAmount.mul(PRECISION).div(gmtPresalePrice); + IERC20(gmt).transfer(account, gmtAmount); + } + + function addLiquidity() external onlyGov nonReentrant { + require(!isLiquidityAdded, "Treasury: liquidity already added"); + isLiquidityAdded = true; + + uint256 busdAmount = busdReceived.mul(busdBasisPoints).div(BASIS_POINTS_DIVISOR); + uint256 gmtAmount = busdAmount.mul(PRECISION).div(gmtListingPrice); + + IERC20(busd).approve(router, busdAmount); + IERC20(gmt).approve(router, gmtAmount); + + IGMT(gmt).endMigration(); + + IPancakeRouter(router).addLiquidity( + busd, // tokenA + gmt, // tokenB + busdAmount, // amountADesired + gmtAmount, // amountBDesired + 0, // amountAMin + 0, // amountBMin + address(this), // to + block.timestamp // deadline + ); + + IGMT(gmt).beginMigration(); + + uint256 fundAmount = busdReceived.sub(busdAmount); + IERC20(busd).transfer(fund, fundAmount); + } + + function withdrawToken(address _token, address _account, uint256 _amount) external override onlyGov nonReentrant { + require(block.timestamp > unlockTime, "Treasury: unlockTime not yet passed"); + IERC20(_token).transfer(_account, _amount); + } + + function increaseBusdBasisPoints(uint256 _busdBasisPoints) external onlyGov nonReentrant { + require(_busdBasisPoints > busdBasisPoints, "Treasury: invalid _busdBasisPoints"); + busdBasisPoints = _busdBasisPoints; + } + + function endSwap() external onlyGov nonReentrant { + isSwapActive = false; + } +} diff --git a/contracts/gambit-token/interfaces/IGMT.sol b/contracts/gambit-token/interfaces/IGMT.sol new file mode 100644 index 00000000..06205a70 --- /dev/null +++ b/contracts/gambit-token/interfaces/IGMT.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IGMT { + function beginMigration() external; + function endMigration() external; +} diff --git a/contracts/gmx/EsGMX.sol b/contracts/gmx/EsGMX.sol new file mode 100644 index 00000000..e1ea4e51 --- /dev/null +++ b/contracts/gmx/EsGMX.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../tokens/MintableBaseToken.sol"; + +contract EsGMX is MintableBaseToken { + constructor() public MintableBaseToken("Escrowed GMX", "esGMX", 0) { + } + + function id() external pure returns (string memory _name) { + return "esGMX"; + } +} diff --git a/contracts/gmx/GLP.sol b/contracts/gmx/GLP.sol new file mode 100644 index 00000000..922ed5c2 --- /dev/null +++ b/contracts/gmx/GLP.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../tokens/MintableBaseToken.sol"; + +contract GLP is MintableBaseToken { + constructor() public MintableBaseToken("GMX LP", "GLP", 0) { + } + + function id() external pure returns (string memory _name) { + return "GLP"; + } +} diff --git a/contracts/gmx/GMX.sol b/contracts/gmx/GMX.sol new file mode 100644 index 00000000..3ca36eb5 --- /dev/null +++ b/contracts/gmx/GMX.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../tokens/MintableBaseToken.sol"; + +contract GMX is MintableBaseToken { + constructor() public MintableBaseToken("GMX", "GMX", 0) { + } + + function id() external pure returns (string memory _name) { + return "GMX"; + } +} diff --git a/contracts/gmx/GmxFloor.sol b/contracts/gmx/GmxFloor.sol new file mode 100644 index 00000000..71deadaf --- /dev/null +++ b/contracts/gmx/GmxFloor.sol @@ -0,0 +1,116 @@ +//SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "../tokens/interfaces/IMintable.sol"; +import "../access/TokenManager.sol"; + +contract GmxFloor is ReentrancyGuard, TokenManager { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + uint256 public constant BASIS_POINTS_DIVISOR = 10000; + uint256 public constant PRICE_PRECISION = 10 ** 30; + uint256 public constant BURN_BASIS_POINTS = 9000; + + address public gmx; + address public reserveToken; + uint256 public backedSupply; + uint256 public baseMintPrice; + uint256 public mintMultiplier; + uint256 public mintedSupply; + uint256 public multiplierPrecision; + + mapping (address => bool) public isHandler; + + modifier onlyHandler() { + require(isHandler[msg.sender], "GmxFloor: forbidden"); + _; + } + + constructor( + address _gmx, + address _reserveToken, + uint256 _backedSupply, + uint256 _baseMintPrice, + uint256 _mintMultiplier, + uint256 _multiplierPrecision, + uint256 _minAuthorizations + ) public TokenManager(_minAuthorizations) { + gmx = _gmx; + + reserveToken = _reserveToken; + backedSupply = _backedSupply; + + baseMintPrice = _baseMintPrice; + mintMultiplier = _mintMultiplier; + multiplierPrecision = _multiplierPrecision; + } + + function initialize(address[] memory _signers) public override onlyAdmin { + TokenManager.initialize(_signers); + } + + function setHandler(address _handler, bool _isHandler) public onlyAdmin { + isHandler[_handler] = _isHandler; + } + + function setBackedSupply(uint256 _backedSupply) public onlyAdmin { + require(_backedSupply > backedSupply, "GmxFloor: invalid _backedSupply"); + backedSupply = _backedSupply; + } + + function setMintMultiplier(uint256 _mintMultiplier) public onlyAdmin { + require(_mintMultiplier > mintMultiplier, "GmxFloor: invalid _mintMultiplier"); + mintMultiplier = _mintMultiplier; + } + + // mint refers to increasing the circulating supply + // the GMX tokens to be transferred out must be pre-transferred into this contract + function mint(uint256 _amount, uint256 _maxCost, address _receiver) public onlyHandler nonReentrant returns (uint256) { + require(_amount > 0, "GmxFloor: invalid _amount"); + + uint256 currentMintPrice = getMintPrice(); + uint256 nextMintPrice = currentMintPrice.add(_amount.mul(mintMultiplier).div(multiplierPrecision)); + uint256 averageMintPrice = currentMintPrice.add(nextMintPrice).div(2); + + uint256 cost = _amount.mul(averageMintPrice).div(PRICE_PRECISION); + require(cost <= _maxCost, "GmxFloor: _maxCost exceeded"); + + mintedSupply = mintedSupply.add(_amount); + backedSupply = backedSupply.add(_amount); + + IERC20(reserveToken).safeTransferFrom(msg.sender, address(this), cost); + IERC20(gmx).transfer(_receiver, _amount); + + return cost; + } + + function burn(uint256 _amount, uint256 _minOut, address _receiver) public onlyHandler nonReentrant returns (uint256) { + require(_amount > 0, "GmxFloor: invalid _amount"); + + uint256 amountOut = getBurnAmountOut(_amount); + require(amountOut >= _minOut, "GmxFloor: insufficient amountOut"); + + backedSupply = backedSupply.sub(_amount); + + IMintable(gmx).burn(msg.sender, _amount); + IERC20(reserveToken).safeTransfer(_receiver, amountOut); + + return amountOut; + } + + function getMintPrice() public view returns (uint256) { + return baseMintPrice.add(mintedSupply.mul(mintMultiplier).div(multiplierPrecision)); + } + + function getBurnAmountOut(uint256 _amount) public view returns (uint256) { + uint256 balance = IERC20(reserveToken).balanceOf(address(this)); + return _amount.mul(balance).div(backedSupply).mul(BURN_BASIS_POINTS).div(BASIS_POINTS_DIVISOR); + } +} diff --git a/contracts/gmx/GmxIou.sol b/contracts/gmx/GmxIou.sol new file mode 100644 index 00000000..f9f952ed --- /dev/null +++ b/contracts/gmx/GmxIou.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/math/SafeMath.sol"; + +import "./interfaces/IGmxIou.sol"; + +contract GmxIou is IERC20, IGmxIou { + using SafeMath for uint256; + + mapping (address => uint256) private _balances; + uint256 public override totalSupply; + + string public name; + string public symbol; + uint8 public decimals; + + address public minter; + + constructor (address _minter, string memory _name, string memory _symbol) public { + name = _name; + symbol = _symbol; + minter = _minter; + decimals = 18; + } + + function mint(address account, uint256 amount) public override returns (bool) { + require(msg.sender == minter, "GmxIou: forbidden"); + _mint(account, amount); + return true; + } + + function balanceOf(address account) public view override returns (uint256) { + return _balances[account]; + } + + // empty implementation, GmxIou tokens are non-transferrable + function transfer(address /* recipient */, uint256 /* amount */) public override returns (bool) { + revert("GmxIou: non-transferrable"); + } + + // empty implementation, GmxIou tokens are non-transferrable + function allowance(address /* owner */, address /* spender */) public view virtual override returns (uint256) { + return 0; + } + + // empty implementation, GmxIou tokens are non-transferrable + function approve(address /* spender */, uint256 /* amount */) public virtual override returns (bool) { + revert("GmxIou: non-transferrable"); + } + + // empty implementation, GmxIou tokens are non-transferrable + function transferFrom(address /* sender */, address /* recipient */, uint256 /* amount */) public virtual override returns (bool) { + revert("GmxIou: non-transferrable"); + } + + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "GmxIou: mint to the zero address"); + + totalSupply = totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } +} diff --git a/contracts/gmx/GmxMigrator.sol b/contracts/gmx/GmxMigrator.sol new file mode 100644 index 00000000..342ae5ae --- /dev/null +++ b/contracts/gmx/GmxMigrator.sol @@ -0,0 +1,239 @@ +//SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./interfaces/IGmxIou.sol"; +import "./interfaces/IAmmRouter.sol"; +import "./interfaces/IGmxMigrator.sol"; + +contract GmxMigrator is ReentrancyGuard, IGmxMigrator { + using SafeMath for uint256; + + bool public isInitialized; + bool public isMigrationActive = true; + bool public hasMaxMigrationLimit = false; + + uint256 public minAuthorizations; + + address public ammRouter; + uint256 public gmxPrice; + + uint256 public actionsNonce; + address public admin; + + address[] public signers; + mapping (address => bool) public isSigner; + mapping (bytes32 => bool) public pendingActions; + mapping (address => mapping (bytes32 => bool)) public signedActions; + + mapping (address => bool) public whitelistedTokens; + mapping (address => address) public override iouTokens; + mapping (address => uint256) public prices; + mapping (address => uint256) public caps; + + mapping (address => bool) public lpTokens; + mapping (address => address) public lpTokenAs; + mapping (address => address) public lpTokenBs; + + mapping (address => uint256) public tokenAmounts; + + mapping (address => mapping (address => uint256)) public migratedAmounts; + mapping (address => mapping (address => uint256)) public maxMigrationAmounts; + + event SignalApprove(address token, address spender, uint256 amount, bytes32 action, uint256 nonce); + + event SignalPendingAction(bytes32 action, uint256 nonce); + event SignAction(bytes32 action, uint256 nonce); + event ClearAction(bytes32 action, uint256 nonce); + + constructor(uint256 _minAuthorizations) public { + admin = msg.sender; + minAuthorizations = _minAuthorizations; + } + + modifier onlyAdmin() { + require(msg.sender == admin, "GmxMigrator: forbidden"); + _; + } + + modifier onlySigner() { + require(isSigner[msg.sender], "GmxMigrator: forbidden"); + _; + } + + function initialize( + address _ammRouter, + uint256 _gmxPrice, + address[] memory _signers, + address[] memory _whitelistedTokens, + address[] memory _iouTokens, + uint256[] memory _prices, + uint256[] memory _caps, + address[] memory _lpTokens, + address[] memory _lpTokenAs, + address[] memory _lpTokenBs + ) public onlyAdmin { + require(!isInitialized, "GmxMigrator: already initialized"); + require(_whitelistedTokens.length == _iouTokens.length, "GmxMigrator: invalid _iouTokens.length"); + require(_whitelistedTokens.length == _prices.length, "GmxMigrator: invalid _prices.length"); + require(_whitelistedTokens.length == _caps.length, "GmxMigrator: invalid _caps.length"); + require(_lpTokens.length == _lpTokenAs.length, "GmxMigrator: invalid _lpTokenAs.length"); + require(_lpTokens.length == _lpTokenBs.length, "GmxMigrator: invalid _lpTokenBs.length"); + + isInitialized = true; + + ammRouter = _ammRouter; + gmxPrice = _gmxPrice; + + signers = _signers; + for (uint256 i = 0; i < _signers.length; i++) { + address signer = _signers[i]; + isSigner[signer] = true; + } + + for (uint256 i = 0; i < _whitelistedTokens.length; i++) { + address token = _whitelistedTokens[i]; + whitelistedTokens[token] = true; + iouTokens[token] = _iouTokens[i]; + prices[token] = _prices[i]; + caps[token] = _caps[i]; + } + + for (uint256 i = 0; i < _lpTokens.length; i++) { + address token = _lpTokens[i]; + lpTokens[token] = true; + lpTokenAs[token] = _lpTokenAs[i]; + lpTokenBs[token] = _lpTokenBs[i]; + } + } + + function endMigration() public onlyAdmin { + isMigrationActive = false; + } + + function setHasMaxMigrationLimit(bool _hasMaxMigrationLimit) public onlyAdmin { + hasMaxMigrationLimit = _hasMaxMigrationLimit; + } + + function setMaxMigrationAmount(address _account, address _token, uint256 _maxMigrationAmount) public onlyAdmin { + maxMigrationAmounts[_account][_token] = _maxMigrationAmount; + } + + function migrate( + address _token, + uint256 _tokenAmount + ) public nonReentrant { + require(isMigrationActive, "GmxMigrator: migration is no longer active"); + require(whitelistedTokens[_token], "GmxMigrator: token not whitelisted"); + require(_tokenAmount > 0, "GmxMigrator: invalid tokenAmount"); + + if (hasMaxMigrationLimit) { + migratedAmounts[msg.sender][_token] = migratedAmounts[msg.sender][_token].add(_tokenAmount); + require(migratedAmounts[msg.sender][_token] <= maxMigrationAmounts[msg.sender][_token], "GmxMigrator: maxMigrationAmount exceeded"); + } + + uint256 tokenPrice = getTokenPrice(_token); + uint256 mintAmount = _tokenAmount.mul(tokenPrice).div(gmxPrice); + require(mintAmount > 0, "GmxMigrator: invalid mintAmount"); + + tokenAmounts[_token] = tokenAmounts[_token].add(_tokenAmount); + require(tokenAmounts[_token] < caps[_token], "GmxMigrator: token cap exceeded"); + + IERC20(_token).transferFrom(msg.sender, address(this), _tokenAmount); + + if (lpTokens[_token]) { + address tokenA = lpTokenAs[_token]; + address tokenB = lpTokenBs[_token]; + require(tokenA != address(0), "GmxMigrator: invalid tokenA"); + require(tokenB != address(0), "GmxMigrator: invalid tokenB"); + + IERC20(_token).approve(ammRouter, _tokenAmount); + IAmmRouter(ammRouter).removeLiquidity(tokenA, tokenB, _tokenAmount, 0, 0, address(this), block.timestamp); + } + + address iouToken = getIouToken(_token); + IGmxIou(iouToken).mint(msg.sender, mintAmount); + } + + function signalApprove(address _token, address _spender, uint256 _amount) external nonReentrant onlyAdmin { + actionsNonce++; + uint256 nonce = actionsNonce; + bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount, nonce)); + _setPendingAction(action, nonce); + emit SignalApprove(_token, _spender, _amount, action, nonce); + } + + function signApprove(address _token, address _spender, uint256 _amount, uint256 _nonce) external nonReentrant onlySigner { + bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount, _nonce)); + _validateAction(action); + require(!signedActions[msg.sender][action], "GmxMigrator: already signed"); + signedActions[msg.sender][action] = true; + emit SignAction(action, _nonce); + } + + function approve(address _token, address _spender, uint256 _amount, uint256 _nonce) external nonReentrant onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount, _nonce)); + _validateAction(action); + _validateAuthorization(action); + + IERC20(_token).approve(_spender, _amount); + _clearAction(action, _nonce); + } + + function getTokenAmounts(address[] memory _tokens) public view returns (uint256[] memory) { + uint256[] memory amounts = new uint256[](_tokens.length); + + for (uint256 i = 0; i < _tokens.length; i++) { + address token = _tokens[i]; + amounts[i] = tokenAmounts[token]; + } + + return amounts; + } + + function getTokenPrice(address _token) public view returns (uint256) { + uint256 price = prices[_token]; + require(price != 0, "GmxMigrator: invalid token price"); + return price; + } + + function getIouToken(address _token) public view returns (address) { + address iouToken = iouTokens[_token]; + require(iouToken != address(0), "GmxMigrator: invalid iou token"); + return iouToken; + } + + function _setPendingAction(bytes32 _action, uint256 _nonce) private { + pendingActions[_action] = true; + emit SignalPendingAction(_action, _nonce); + } + + function _validateAction(bytes32 _action) private view { + require(pendingActions[_action], "GmxMigrator: action not signalled"); + } + + function _validateAuthorization(bytes32 _action) private view { + uint256 count = 0; + for (uint256 i = 0; i < signers.length; i++) { + address signer = signers[i]; + if (signedActions[signer][_action]) { + count++; + } + } + + if (count == 0) { + revert("GmxMigrator: action not authorized"); + } + require(count >= minAuthorizations, "GmxMigrator: insufficient authorization"); + } + + function _clearAction(bytes32 _action, uint256 _nonce) private { + require(pendingActions[_action], "GmxMigrator: invalid _action"); + delete pendingActions[_action]; + emit ClearAction(_action, _nonce); + } +} diff --git a/contracts/gmx/MigrationHandler.sol b/contracts/gmx/MigrationHandler.sol new file mode 100644 index 00000000..6a6f5632 --- /dev/null +++ b/contracts/gmx/MigrationHandler.sol @@ -0,0 +1,159 @@ +//SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./interfaces/IAmmRouter.sol"; +import "./interfaces/IGmxMigrator.sol"; +import "../core/interfaces/IVault.sol"; + +contract MigrationHandler is ReentrancyGuard { + using SafeMath for uint256; + + uint256 public constant USDG_PRECISION = 10 ** 18; + + bool public isInitialized; + + address public admin; + address public ammRouterV1; + address public ammRouterV2; + + address public vault; + + address public gmt; + address public xgmt; + address public usdg; + address public bnb; + address public busd; + + mapping (address => mapping (address => uint256)) public refundedAmounts; + + modifier onlyAdmin() { + require(msg.sender == admin, "MigrationHandler: forbidden"); + _; + } + + constructor() public { + admin = msg.sender; + } + + function initialize( + address _ammRouterV1, + address _ammRouterV2, + address _vault, + address _gmt, + address _xgmt, + address _usdg, + address _bnb, + address _busd + ) public onlyAdmin { + require(!isInitialized, "MigrationHandler: already initialized"); + isInitialized = true; + + ammRouterV1 = _ammRouterV1; + ammRouterV2 = _ammRouterV2; + + vault = _vault; + + gmt = _gmt; + xgmt = _xgmt; + usdg = _usdg; + bnb = _bnb; + busd = _busd; + } + + function redeemUsdg( + address _migrator, + address _redemptionToken, + uint256 _usdgAmount + ) external onlyAdmin nonReentrant { + IERC20(usdg).transferFrom(_migrator, vault, _usdgAmount); + uint256 amount = IVault(vault).sellUSDG(_redemptionToken, address(this)); + + address[] memory path = new address[](2); + path[0] = bnb; + path[1] = busd; + + if (_redemptionToken != bnb) { + path = new address[](3); + path[0] = _redemptionToken; + path[1] = bnb; + path[2] = busd; + } + + IERC20(_redemptionToken).approve(ammRouterV2, amount); + IAmmRouter(ammRouterV2).swapExactTokensForTokens( + amount, + 0, + path, + _migrator, + block.timestamp + ); + } + + function swap( + address _migrator, + uint256 _gmtAmountForUsdg, + uint256 _xgmtAmountForUsdg, + uint256 _gmtAmountForBusd + ) external onlyAdmin nonReentrant { + address[] memory path = new address[](2); + + path[0] = gmt; + path[1] = usdg; + IERC20(gmt).transferFrom(_migrator, address(this), _gmtAmountForUsdg); + IERC20(gmt).approve(ammRouterV2, _gmtAmountForUsdg); + IAmmRouter(ammRouterV2).swapExactTokensForTokens( + _gmtAmountForUsdg, + 0, + path, + _migrator, + block.timestamp + ); + + path[0] = xgmt; + path[1] = usdg; + IERC20(xgmt).transferFrom(_migrator, address(this), _xgmtAmountForUsdg); + IERC20(xgmt).approve(ammRouterV2, _xgmtAmountForUsdg); + IAmmRouter(ammRouterV2).swapExactTokensForTokens( + _xgmtAmountForUsdg, + 0, + path, + _migrator, + block.timestamp + ); + + path[0] = gmt; + path[1] = busd; + IERC20(gmt).transferFrom(_migrator, address(this), _gmtAmountForBusd); + IERC20(gmt).approve(ammRouterV1, _gmtAmountForBusd); + IAmmRouter(ammRouterV1).swapExactTokensForTokens( + _gmtAmountForBusd, + 0, + path, + _migrator, + block.timestamp + ); + } + + function refund( + address _migrator, + address _account, + address _token, + uint256 _usdgAmount + ) external onlyAdmin nonReentrant { + address iouToken = IGmxMigrator(_migrator).iouTokens(_token); + uint256 iouBalance = IERC20(iouToken).balanceOf(_account); + uint256 iouTokenAmount = _usdgAmount.div(2); // each GMX is priced at $2 + + uint256 refunded = refundedAmounts[_account][iouToken]; + refundedAmounts[_account][iouToken] = refunded.add(iouTokenAmount); + + require(refundedAmounts[_account][iouToken] <= iouBalance, "MigrationHandler: refundable amount exceeded"); + + IERC20(usdg).transferFrom(_migrator, _account, _usdgAmount); + } +} diff --git a/contracts/gmx/interfaces/IAmmRouter.sol b/contracts/gmx/interfaces/IAmmRouter.sol new file mode 100644 index 00000000..4d1b0aa2 --- /dev/null +++ b/contracts/gmx/interfaces/IAmmRouter.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IAmmRouter { + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); +} diff --git a/contracts/gmx/interfaces/IGmxIou.sol b/contracts/gmx/interfaces/IGmxIou.sol new file mode 100644 index 00000000..148bc80a --- /dev/null +++ b/contracts/gmx/interfaces/IGmxIou.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IGmxIou { + function mint(address account, uint256 amount) external returns (bool); +} diff --git a/contracts/gmx/interfaces/IGmxMigrator.sol b/contracts/gmx/interfaces/IGmxMigrator.sol new file mode 100644 index 00000000..46a121ef --- /dev/null +++ b/contracts/gmx/interfaces/IGmxMigrator.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IGmxMigrator { + function iouTokens(address _token) external view returns (address); +} diff --git a/contracts/libraries/GSN/Context.sol b/contracts/libraries/GSN/Context.sol new file mode 100644 index 00000000..97251177 --- /dev/null +++ b/contracts/libraries/GSN/Context.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with GSN meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} diff --git a/contracts/libraries/access/Ownable.sol b/contracts/libraries/access/Ownable.sol new file mode 100644 index 00000000..cc2cfa83 --- /dev/null +++ b/contracts/libraries/access/Ownable.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../GSN/Context.sol"; +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor () internal { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(_owner == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } +} diff --git a/contracts/libraries/introspection/ERC165.sol b/contracts/libraries/introspection/ERC165.sol new file mode 100644 index 00000000..8f34a88a --- /dev/null +++ b/contracts/libraries/introspection/ERC165.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +import "./IERC165.sol"; + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts may inherit from this and call {_registerInterface} to declare + * their support of an interface. + */ +contract ERC165 is IERC165 { + /* + * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7 + */ + bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7; + + /** + * @dev Mapping of interface ids to whether or not it's supported. + */ + mapping(bytes4 => bool) private _supportedInterfaces; + + constructor () internal { + // Derived contracts need only register support for their own interfaces, + // we register support for ERC165 itself here + _registerInterface(_INTERFACE_ID_ERC165); + } + + /** + * @dev See {IERC165-supportsInterface}. + * + * Time complexity O(1), guaranteed to always use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return _supportedInterfaces[interfaceId]; + } + + /** + * @dev Registers the contract as an implementer of the interface defined by + * `interfaceId`. Support of the actual ERC165 interface is automatic and + * registering its interface id is not required. + * + * See {IERC165-supportsInterface}. + * + * Requirements: + * + * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`). + */ + function _registerInterface(bytes4 interfaceId) internal virtual { + require(interfaceId != 0xffffffff, "ERC165: invalid interface id"); + _supportedInterfaces[interfaceId] = true; + } +} diff --git a/contracts/libraries/introspection/IERC165.sol b/contracts/libraries/introspection/IERC165.sol new file mode 100644 index 00000000..425458d6 --- /dev/null +++ b/contracts/libraries/introspection/IERC165.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +/** + * @dev Interface of the ERC165 standard, as defined in the + * https://eips.ethereum.org/EIPS/eip-165[EIP]. + * + * Implementers can declare support of contract interfaces, which can then be + * queried by others ({ERC165Checker}). + * + * For an implementation, see {ERC165}. + */ +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} diff --git a/contracts/libraries/math/Math.sol b/contracts/libraries/math/Math.sol new file mode 100644 index 00000000..084dfddb --- /dev/null +++ b/contracts/libraries/math/Math.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +// a library for performing various math operations + +library Math { + function min(uint x, uint y) internal pure returns (uint z) { + z = x < y ? x : y; + } + + // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) + function sqrt(uint y) internal pure returns (uint z) { + if (y > 3) { + z = y; + uint x = y / 2 + 1; + while (x < z) { + z = x; + x = (y / x + x) / 2; + } + } else if (y != 0) { + z = 1; + } + } +} diff --git a/contracts/libraries/math/SafeMath.sol b/contracts/libraries/math/SafeMath.sol new file mode 100644 index 00000000..7cb3abaa --- /dev/null +++ b/contracts/libraries/math/SafeMath.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} diff --git a/contracts/libraries/math/UQ112x112.sol b/contracts/libraries/math/UQ112x112.sol new file mode 100644 index 00000000..2184bc30 --- /dev/null +++ b/contracts/libraries/math/UQ112x112.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +// a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) + +// range: [0, 2**112 - 1] +// resolution: 1 / 2**112 + +library UQ112x112 { + uint224 constant Q112 = 2**112; + + // encode a uint112 as a UQ112x112 + function encode(uint112 y) internal pure returns (uint224 z) { + z = uint224(y) * Q112; // never overflows + } + + // divide a UQ112x112 by a uint112, returning a UQ112x112 + function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) { + z = x / uint224(y); + } +} diff --git a/contracts/libraries/token/ERC20.sol b/contracts/libraries/token/ERC20.sol new file mode 100644 index 00000000..c2aeed27 --- /dev/null +++ b/contracts/libraries/token/ERC20.sol @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../GSN/Context.sol"; +import "./IERC20.sol"; +import "../math/SafeMath.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20 { + using SafeMath for uint256; + + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + /** + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name, string memory symbol) public { + _name = name; + _symbol = symbol; + _decimals = 18; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is + * called. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Sets {decimals} to a value other than the default one of 18. + * + * WARNING: This function should only be called from the constructor. Most + * applications that interact with token contracts will not expect + * {decimals} to ever change, and may work incorrectly if it does. + */ + function _setupDecimals(uint8 decimals_) internal { + _decimals = decimals_; + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } +} diff --git a/contracts/libraries/token/ERC721/ERC721.sol b/contracts/libraries/token/ERC721/ERC721.sol new file mode 100644 index 00000000..2ec56425 --- /dev/null +++ b/contracts/libraries/token/ERC721/ERC721.sol @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +import "../../GSN/Context.sol"; +import "./IERC721.sol"; +import "./IERC721Metadata.sol"; +import "./IERC721Enumerable.sol"; +import "./IERC721Receiver.sol"; +import "../../introspection/ERC165.sol"; +import "../../math/SafeMath.sol"; +import "../../utils/Address.sol"; +import "../../utils/EnumerableSet.sol"; +import "../../utils/EnumerableMap.sol"; +import "../../utils/Strings.sol"; + +/** + * @title ERC721 Non-Fungible Token Standard basic implementation + * @dev see https://eips.ethereum.org/EIPS/eip-721 + */ +contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable { + using SafeMath for uint256; + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableMap for EnumerableMap.UintToAddressMap; + using Strings for uint256; + + // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector` + bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; + + // Mapping from holder address to their (enumerable) set of owned tokens + mapping (address => EnumerableSet.UintSet) private _holderTokens; + + // Enumerable mapping from token ids to their owners + EnumerableMap.UintToAddressMap private _tokenOwners; + + // Mapping from token ID to approved address + mapping (uint256 => address) private _tokenApprovals; + + // Mapping from owner to operator approvals + mapping (address => mapping (address => bool)) private _operatorApprovals; + + // Token name + string private _name; + + // Token symbol + string private _symbol; + + // Optional mapping for token URIs + mapping (uint256 => string) private _tokenURIs; + + // Base URI + string private _baseURI; + + /* + * bytes4(keccak256('balanceOf(address)')) == 0x70a08231 + * bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e + * bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3 + * bytes4(keccak256('getApproved(uint256)')) == 0x081812fc + * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465 + * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5 + * bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde + * + * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^ + * 0xa22cb465 ^ 0xe985e9c5 ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd + */ + bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd; + + /* + * bytes4(keccak256('name()')) == 0x06fdde03 + * bytes4(keccak256('symbol()')) == 0x95d89b41 + * bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd + * + * => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f + */ + bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f; + + /* + * bytes4(keccak256('totalSupply()')) == 0x18160ddd + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59 + * bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7 + * + * => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63 + */ + bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor (string memory name, string memory symbol) public { + _name = name; + _symbol = symbol; + + // register the supported interfaces to conform to ERC721 via ERC165 + _registerInterface(_INTERFACE_ID_ERC721); + _registerInterface(_INTERFACE_ID_ERC721_METADATA); + _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE); + } + + function mint(address to, uint256 tokenId) public { + _mint(to, tokenId); + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view override returns (uint256) { + require(owner != address(0), "ERC721: balance query for the zero address"); + + return _holderTokens[owner].length(); + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view override returns (address) { + return _tokenOwners.get(tokenId, "ERC721: owner query for nonexistent token"); + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view override returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view override returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view override returns (string memory) { + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + string memory _tokenURI = _tokenURIs[tokenId]; + + // If there is no base URI, return the token URI. + if (bytes(_baseURI).length == 0) { + return _tokenURI; + } + // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). + if (bytes(_tokenURI).length > 0) { + return string(abi.encodePacked(_baseURI, _tokenURI)); + } + // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI. + return string(abi.encodePacked(_baseURI, tokenId.toString())); + } + + /** + * @dev Returns the base URI set via {_setBaseURI}. This will be + * automatically added as a prefix in {tokenURI} to each token's URI, or + * to the token ID if no specific URI is set for that token ID. + */ + function baseURI() public view returns (string memory) { + return _baseURI; + } + + /** + * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) { + return _holderTokens[owner].at(index); + } + + /** + * @dev See {IERC721Enumerable-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + // _tokenOwners are indexed by tokenIds, so .length() returns the number of tokenIds + return _tokenOwners.length(); + } + + /** + * @dev See {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view override returns (uint256) { + (uint256 tokenId, ) = _tokenOwners.at(index); + return tokenId; + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual override { + address owner = ownerOf(tokenId); + require(to != owner, "ERC721: approval to current owner"); + + require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()), + "ERC721: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view override returns (address) { + require(_exists(tokenId), "ERC721: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + require(operator != _msgSender(), "ERC721: approve to caller"); + + _operatorApprovals[_msgSender()][operator] = approved; + emit ApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view override returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev See {IERC721-transferFrom}. + */ + function transferFrom(address from, address to, uint256 tokenId) public virtual override { + //solhint-disable-next-line max-line-length + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + + _transfer(from, to, tokenId); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + _safeTransfer(from, to, tokenId, _data); + } + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * `_data` is additional data, it has no specified format and it is sent in call to `to`. + * + * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. + * implement alternative mechanisms to perform token transfer, such as signature-based. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual { + _transfer(from, to, tokenId); + require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Returns whether `tokenId` exists. + * + * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. + * + * Tokens start existing when they are minted (`_mint`), + * and stop existing when they are burned (`_burn`). + */ + function _exists(uint256 tokenId) internal view returns (bool) { + return _tokenOwners.contains(tokenId); + } + + /** + * @dev Returns whether `spender` is allowed to manage `tokenId`. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + + /** + * @dev Safely mints `tokenId` and transfers it to `to`. + * + * Requirements: + d* + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeMint(address to, uint256 tokenId) internal virtual { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeMint(address to, uint256 tokenId, bytes memory _data) internal virtual { + _mint(to, tokenId); + require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function _mint(address to, uint256 tokenId) internal virtual { + require(to != address(0), "ERC721: mint to the zero address"); + require(!_exists(tokenId), "ERC721: token already minted"); + + _beforeTokenTransfer(address(0), to, tokenId); + + _holderTokens[to].add(tokenId); + + _tokenOwners.set(tokenId, to); + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function _burn(uint256 tokenId) internal virtual { + address owner = ownerOf(tokenId); + + _beforeTokenTransfer(owner, address(0), tokenId); + + // Clear approvals + _approve(address(0), tokenId); + + // Clear metadata (if any) + if (bytes(_tokenURIs[tokenId]).length != 0) { + delete _tokenURIs[tokenId]; + } + + _holderTokens[owner].remove(tokenId); + + _tokenOwners.remove(tokenId); + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer(address from, address to, uint256 tokenId) internal virtual { + require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Clear approvals from the previous owner + _approve(address(0), tokenId); + + _holderTokens[from].remove(tokenId); + _holderTokens[to].add(tokenId); + + _tokenOwners.set(tokenId, to); + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Sets `_tokenURI` as the tokenURI of `tokenId`. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { + require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token"); + _tokenURIs[tokenId] = _tokenURI; + } + + /** + * @dev Internal function to set the base URI for all token IDs. It is + * automatically added as a prefix to the value returned in {tokenURI}, + * or to the token ID if {tokenURI} is empty. + */ + function _setBaseURI(string memory baseURI_) internal virtual { + _baseURI = baseURI_; + } + + /** + * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. + * The call is not executed if the target address is not a contract. + * + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) + private returns (bool) + { + if (!to.isContract()) { + return true; + } + bytes memory returndata = to.functionCall(abi.encodeWithSelector( + IERC721Receiver(to).onERC721Received.selector, + _msgSender(), + from, + tokenId, + _data + ), "ERC721: transfer to non ERC721Receiver implementer"); + bytes4 retval = abi.decode(returndata, (bytes4)); + return (retval == _ERC721_RECEIVED); + } + + function _approve(address to, uint256 tokenId) private { + _tokenApprovals[tokenId] = to; + emit Approval(ownerOf(tokenId), to, tokenId); + } + + /** + * @dev Hook that is called before any token transfer. This includes minting + * and burning. + * + * Calling conditions: + * + * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be + * transferred to `to`. + * - When `from` is zero, `tokenId` will be minted for `to`. + * - When `to` is zero, ``from``'s `tokenId` will be burned. + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual { } +} diff --git a/contracts/libraries/token/ERC721/IERC721.sol b/contracts/libraries/token/ERC721/IERC721.sol new file mode 100644 index 00000000..5e7d20e3 --- /dev/null +++ b/contracts/libraries/token/ERC721/IERC721.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.2; + +import "../../introspection/IERC165.sol"; + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 tokenId) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external; +} diff --git a/contracts/libraries/token/ERC721/IERC721Enumerable.sol b/contracts/libraries/token/ERC721/IERC721Enumerable.sol new file mode 100644 index 00000000..bd3c86d1 --- /dev/null +++ b/contracts/libraries/token/ERC721/IERC721Enumerable.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.2; + +import "./IERC721.sol"; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable is IERC721 { + + /** + * @dev Returns the total amount of tokens stored by the contract. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns a token ID owned by `owner` at a given `index` of its token list. + * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev Returns a token ID at a given `index` of all the tokens stored by the contract. + * Use along with {totalSupply} to enumerate all tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} diff --git a/contracts/libraries/token/ERC721/IERC721Metadata.sol b/contracts/libraries/token/ERC721/IERC721Metadata.sol new file mode 100644 index 00000000..f2ebb495 --- /dev/null +++ b/contracts/libraries/token/ERC721/IERC721Metadata.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.2; + +import "./IERC721.sol"; + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + + /** + * @dev Returns the token collection name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the token collection symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} diff --git a/contracts/libraries/token/ERC721/IERC721Receiver.sol b/contracts/libraries/token/ERC721/IERC721Receiver.sol new file mode 100644 index 00000000..0d17a070 --- /dev/null +++ b/contracts/libraries/token/ERC721/IERC721Receiver.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + * + * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`. + */ + function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4); +} diff --git a/contracts/libraries/token/IERC20.sol b/contracts/libraries/token/IERC20.sol new file mode 100644 index 00000000..66b36c98 --- /dev/null +++ b/contracts/libraries/token/IERC20.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} diff --git a/contracts/libraries/token/SafeERC20.sol b/contracts/libraries/token/SafeERC20.sol new file mode 100644 index 00000000..8d9eeecb --- /dev/null +++ b/contracts/libraries/token/SafeERC20.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "./IERC20.sol"; +import "../math/SafeMath.sol"; +import "../utils/Address.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove(IERC20 token, address spender, uint256 value) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} diff --git a/contracts/libraries/utils/Address.sol b/contracts/libraries/utils/Address.sol new file mode 100644 index 00000000..a3a98d5d --- /dev/null +++ b/contracts/libraries/utils/Address.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.2; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: value }(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.3._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.3._ + */ + function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private pure returns(bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} diff --git a/contracts/libraries/utils/EnumerableMap.sol b/contracts/libraries/utils/EnumerableMap.sol new file mode 100644 index 00000000..82eb04c9 --- /dev/null +++ b/contracts/libraries/utils/EnumerableMap.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +/** + * @dev Library for managing an enumerable variant of Solidity's + * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`] + * type. + * + * Maps have the following properties: + * + * - Entries are added, removed, and checked for existence in constant time + * (O(1)). + * - Entries are enumerated in O(n). No guarantees are made on the ordering. + * + * ``` + * contract Example { + * // Add the library methods + * using EnumerableMap for EnumerableMap.UintToAddressMap; + * + * // Declare a set state variable + * EnumerableMap.UintToAddressMap private myMap; + * } + * ``` + * + * As of v3.0.0, only maps of type `uint256 -> address` (`UintToAddressMap`) are + * supported. + */ +library EnumerableMap { + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Map type with + // bytes32 keys and values. + // The Map implementation uses private functions, and user-facing + // implementations (such as Uint256ToAddressMap) are just wrappers around + // the underlying Map. + // This means that we can only create new EnumerableMaps for types that fit + // in bytes32. + + struct MapEntry { + bytes32 _key; + bytes32 _value; + } + + struct Map { + // Storage of map keys and values + MapEntry[] _entries; + + // Position of the entry defined by a key in the `entries` array, plus 1 + // because index 0 means a key is not in the map. + mapping (bytes32 => uint256) _indexes; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function _set(Map storage map, bytes32 key, bytes32 value) private returns (bool) { + // We read and store the key's index to prevent multiple reads from the same storage slot + uint256 keyIndex = map._indexes[key]; + + if (keyIndex == 0) { // Equivalent to !contains(map, key) + map._entries.push(MapEntry({ _key: key, _value: value })); + // The entry is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + map._indexes[key] = map._entries.length; + return true; + } else { + map._entries[keyIndex - 1]._value = value; + return false; + } + } + + /** + * @dev Removes a key-value pair from a map. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function _remove(Map storage map, bytes32 key) private returns (bool) { + // We read and store the key's index to prevent multiple reads from the same storage slot + uint256 keyIndex = map._indexes[key]; + + if (keyIndex != 0) { // Equivalent to contains(map, key) + // To delete a key-value pair from the _entries array in O(1), we swap the entry to delete with the last one + // in the array, and then remove the last entry (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 toDeleteIndex = keyIndex - 1; + uint256 lastIndex = map._entries.length - 1; + + // When the entry to delete is the last one, the swap operation is unnecessary. However, since this occurs + // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. + + MapEntry storage lastEntry = map._entries[lastIndex]; + + // Move the last entry to the index where the entry to delete is + map._entries[toDeleteIndex] = lastEntry; + // Update the index for the moved entry + map._indexes[lastEntry._key] = toDeleteIndex + 1; // All indexes are 1-based + + // Delete the slot where the moved entry was stored + map._entries.pop(); + + // Delete the index for the deleted slot + delete map._indexes[key]; + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function _contains(Map storage map, bytes32 key) private view returns (bool) { + return map._indexes[key] != 0; + } + + /** + * @dev Returns the number of key-value pairs in the map. O(1). + */ + function _length(Map storage map) private view returns (uint256) { + return map._entries.length; + } + + /** + * @dev Returns the key-value pair stored at position `index` in the map. O(1). + * + * Note that there are no guarantees on the ordering of entries inside the + * array, and it may change when more entries are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) { + require(map._entries.length > index, "EnumerableMap: index out of bounds"); + + MapEntry storage entry = map._entries[index]; + return (entry._key, entry._value); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function _get(Map storage map, bytes32 key) private view returns (bytes32) { + return _get(map, key, "EnumerableMap: nonexistent key"); + } + + /** + * @dev Same as {_get}, with a custom error message when `key` is not in the map. + */ + function _get(Map storage map, bytes32 key, string memory errorMessage) private view returns (bytes32) { + uint256 keyIndex = map._indexes[key]; + require(keyIndex != 0, errorMessage); // Equivalent to contains(map, key) + return map._entries[keyIndex - 1]._value; // All indexes are 1-based + } + + // UintToAddressMap + + struct UintToAddressMap { + Map _inner; + } + + /** + * @dev Adds a key-value pair to a map, or updates the value for an existing + * key. O(1). + * + * Returns true if the key was added to the map, that is if it was not + * already present. + */ + function set(UintToAddressMap storage map, uint256 key, address value) internal returns (bool) { + return _set(map._inner, bytes32(key), bytes32(uint256(value))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the key was removed from the map, that is if it was present. + */ + function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) { + return _remove(map._inner, bytes32(key)); + } + + /** + * @dev Returns true if the key is in the map. O(1). + */ + function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) { + return _contains(map._inner, bytes32(key)); + } + + /** + * @dev Returns the number of elements in the map. O(1). + */ + function length(UintToAddressMap storage map) internal view returns (uint256) { + return _length(map._inner); + } + + /** + * @dev Returns the element stored at position `index` in the set. O(1). + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) { + (bytes32 key, bytes32 value) = _at(map._inner, index); + return (uint256(key), address(uint256(value))); + } + + /** + * @dev Returns the value associated with `key`. O(1). + * + * Requirements: + * + * - `key` must be in the map. + */ + function get(UintToAddressMap storage map, uint256 key) internal view returns (address) { + return address(uint256(_get(map._inner, bytes32(key)))); + } + + /** + * @dev Same as {get}, with a custom error message when `key` is not in the map. + */ + function get(UintToAddressMap storage map, uint256 key, string memory errorMessage) internal view returns (address) { + return address(uint256(_get(map._inner, bytes32(key), errorMessage))); + } +} diff --git a/contracts/libraries/utils/EnumerableSet.sol b/contracts/libraries/utils/EnumerableSet.sol new file mode 100644 index 00000000..7f4c761a --- /dev/null +++ b/contracts/libraries/utils/EnumerableSet.sol @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +/** + * @dev Library for managing + * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive + * types. + * + * Sets have the following properties: + * + * - Elements are added, removed, and checked for existence in constant time + * (O(1)). + * - Elements are enumerated in O(n). No guarantees are made on the ordering. + * + * ``` + * contract Example { + * // Add the library methods + * using EnumerableSet for EnumerableSet.AddressSet; + * + * // Declare a set state variable + * EnumerableSet.AddressSet private mySet; + * } + * ``` + * + * As of v3.0.0, only sets of type `address` (`AddressSet`) and `uint256` + * (`UintSet`) are supported. + */ +library EnumerableSet { + // To implement this library for multiple types with as little code + // repetition as possible, we write it in terms of a generic Set type with + // bytes32 values. + // The Set implementation uses private functions, and user-facing + // implementations (such as AddressSet) are just wrappers around the + // underlying Set. + // This means that we can only create new EnumerableSets for types that fit + // in bytes32. + + struct Set { + // Storage of set values + bytes32[] _values; + + // Position of the value in the `values` array, plus 1 because index 0 + // means a value is not in the set. + mapping (bytes32 => uint256) _indexes; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function _add(Set storage set, bytes32 value) private returns (bool) { + if (!_contains(set, value)) { + set._values.push(value); + // The value is stored at length-1, but we add 1 to all indexes + // and use 0 as a sentinel value + set._indexes[value] = set._values.length; + return true; + } else { + return false; + } + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function _remove(Set storage set, bytes32 value) private returns (bool) { + // We read and store the value's index to prevent multiple reads from the same storage slot + uint256 valueIndex = set._indexes[value]; + + if (valueIndex != 0) { // Equivalent to contains(set, value) + // To delete an element from the _values array in O(1), we swap the element to delete with the last one in + // the array, and then remove the last element (sometimes called as 'swap and pop'). + // This modifies the order of the array, as noted in {at}. + + uint256 toDeleteIndex = valueIndex - 1; + uint256 lastIndex = set._values.length - 1; + + // When the value to delete is the last one, the swap operation is unnecessary. However, since this occurs + // so rarely, we still do the swap anyway to avoid the gas cost of adding an 'if' statement. + + bytes32 lastvalue = set._values[lastIndex]; + + // Move the last value to the index where the value to delete is + set._values[toDeleteIndex] = lastvalue; + // Update the index for the moved value + set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based + + // Delete the slot where the moved value was stored + set._values.pop(); + + // Delete the index for the deleted slot + delete set._indexes[value]; + + return true; + } else { + return false; + } + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function _contains(Set storage set, bytes32 value) private view returns (bool) { + return set._indexes[value] != 0; + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function _length(Set storage set) private view returns (uint256) { + return set._values.length; + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function _at(Set storage set, uint256 index) private view returns (bytes32) { + require(set._values.length > index, "EnumerableSet: index out of bounds"); + return set._values[index]; + } + + // AddressSet + + struct AddressSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(AddressSet storage set, address value) internal returns (bool) { + return _add(set._inner, bytes32(uint256(value))); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(AddressSet storage set, address value) internal returns (bool) { + return _remove(set._inner, bytes32(uint256(value))); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(AddressSet storage set, address value) internal view returns (bool) { + return _contains(set._inner, bytes32(uint256(value))); + } + + /** + * @dev Returns the number of values in the set. O(1). + */ + function length(AddressSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(AddressSet storage set, uint256 index) internal view returns (address) { + return address(uint256(_at(set._inner, index))); + } + + + // UintSet + + struct UintSet { + Set _inner; + } + + /** + * @dev Add a value to a set. O(1). + * + * Returns true if the value was added to the set, that is if it was not + * already present. + */ + function add(UintSet storage set, uint256 value) internal returns (bool) { + return _add(set._inner, bytes32(value)); + } + + /** + * @dev Removes a value from a set. O(1). + * + * Returns true if the value was removed from the set, that is if it was + * present. + */ + function remove(UintSet storage set, uint256 value) internal returns (bool) { + return _remove(set._inner, bytes32(value)); + } + + /** + * @dev Returns true if the value is in the set. O(1). + */ + function contains(UintSet storage set, uint256 value) internal view returns (bool) { + return _contains(set._inner, bytes32(value)); + } + + /** + * @dev Returns the number of values on the set. O(1). + */ + function length(UintSet storage set) internal view returns (uint256) { + return _length(set._inner); + } + + /** + * @dev Returns the value stored at position `index` in the set. O(1). + * + * Note that there are no guarantees on the ordering of values inside the + * array, and it may change when more values are added or removed. + * + * Requirements: + * + * - `index` must be strictly less than {length}. + */ + function at(UintSet storage set, uint256 index) internal view returns (uint256) { + return uint256(_at(set._inner, index)); + } +} diff --git a/contracts/libraries/utils/ReentrancyGuard.sol b/contracts/libraries/utils/ReentrancyGuard.sol new file mode 100644 index 00000000..42d4de28 --- /dev/null +++ b/contracts/libraries/utils/ReentrancyGuard.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor () internal { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} diff --git a/contracts/libraries/utils/Strings.sol b/contracts/libraries/utils/Strings.sol new file mode 100644 index 00000000..aacce819 --- /dev/null +++ b/contracts/libraries/utils/Strings.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.0; + +/** + * @dev String operations. + */ +library Strings { + /** + * @dev Converts a `uint256` to its ASCII `string` representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + uint256 index = digits - 1; + temp = value; + while (temp != 0) { + buffer[index--] = byte(uint8(48 + temp % 10)); + temp /= 10; + } + return string(buffer); + } +} diff --git a/contracts/oracle/FastPriceFeed.sol b/contracts/oracle/FastPriceFeed.sol new file mode 100644 index 00000000..375466cf --- /dev/null +++ b/contracts/oracle/FastPriceFeed.sol @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: MIT + +import "../libraries/math/SafeMath.sol"; + +import "./interfaces/ISecondaryPriceFeed.sol"; +import "../access/Governable.sol"; + +pragma solidity 0.6.12; + +contract FastPriceFeed is ISecondaryPriceFeed, Governable { + using SafeMath for uint256; + + uint256 public constant PRICE_PRECISION = 10 ** 30; + uint256 public constant COMPACTED_PRECISION = 10 ** 3; + + // uint256(~0) is 256 bits of 1s + // shift the 1s by (256 - 32) to get (256 - 32) 0s followed by 32 1s + uint256 constant public PRICE_BITMASK = uint256(~0) >> (256 - 32); + + bool public isInitialized; + bool public isSpreadEnabled = false; + + uint256 public constant BASIS_POINTS_DIVISOR = 10000; + + uint256 public constant MAX_PRICE_DURATION = 30 minutes; + + uint256 public lastUpdatedAt; + uint256 public priceDuration; + + // volatility basis points + uint256 public volBasisPoints; + // max deviation from primary price + uint256 public maxDeviationBasisPoints; + + mapping (address => uint256) public prices; + + uint256 public minAuthorizations; + uint256 public disableFastPriceVoteCount = 0; + mapping (address => bool) public isSigner; + mapping (address => bool) public disableFastPriceVotes; + + address[] public tokens; + + event SetPrice(address token, uint256 price); + + modifier onlySigner() { + require(isSigner[msg.sender], "FastPriceFeed: forbidden"); + _; + } + + constructor(uint256 _priceDuration, uint256 _maxDeviationBasisPoints) public { + require(_priceDuration <= MAX_PRICE_DURATION, "FastPriceFeed: invalid _priceDuration"); + priceDuration = _priceDuration; + maxDeviationBasisPoints = _maxDeviationBasisPoints; + } + + function initialize(uint256 _minAuthorizations, address[] memory _signers) public onlyGov { + require(!isInitialized, "FastPriceFeed: already initialized"); + isInitialized = true; + + minAuthorizations = _minAuthorizations; + + for (uint256 i = 0; i < _signers.length; i++) { + address signer = _signers[i]; + isSigner[signer] = true; + } + } + + function setPriceDuration(uint256 _priceDuration) external onlyGov { + require(_priceDuration <= MAX_PRICE_DURATION, "FastPriceFeed: invalid _priceDuration"); + priceDuration = _priceDuration; + } + + function setIsSpreadEnabled(bool _isSpreadEnabled) external onlyGov { + isSpreadEnabled = _isSpreadEnabled; + } + + function setVolBasisPoints(uint256 _volBasisPoints) external onlyGov { + volBasisPoints = _volBasisPoints; + } + + function setPrices(address[] memory _tokens, uint256[] memory _prices) external onlyGov { + for (uint256 i = 0; i < _tokens.length; i++) { + address token = _tokens[i]; + prices[token] = _prices[i]; + emit SetPrice(token, _prices[i]); + } + lastUpdatedAt = block.timestamp; + } + + function setTokens(address[] memory _tokens) external onlyGov { + tokens = _tokens; + } + + function setCompactedPrices(uint256[] memory _priceBitArray) external onlyGov { + lastUpdatedAt = block.timestamp; + + for (uint256 i = 0; i < _priceBitArray.length; i++) { + uint256 priceBits = _priceBitArray[i]; + + for (uint256 j = 0; j < 8; j++) { + uint256 index = i * 8 + j; + if (index >= tokens.length) { return; } + + uint256 startBit = 32 * j; + uint256 price = (priceBits >> startBit) & PRICE_BITMASK; + + address token = tokens[i * 8 + j]; + uint256 adjustedPrice = price.mul(PRICE_PRECISION).div(COMPACTED_PRECISION); + prices[token] = adjustedPrice; + emit SetPrice(token, adjustedPrice); + } + } + } + + function disableFastPrice() external onlySigner { + require(!disableFastPriceVotes[msg.sender], "FastPriceFeed: already voted"); + disableFastPriceVotes[msg.sender] = true; + disableFastPriceVoteCount = disableFastPriceVoteCount.add(1); + } + + function enableFastPrice() external onlySigner { + require(disableFastPriceVotes[msg.sender], "FastPriceFeed: already enabled"); + disableFastPriceVotes[msg.sender] = false; + disableFastPriceVoteCount = disableFastPriceVoteCount.sub(1); + } + + function favorFastPrice() public view returns (bool) { + return (disableFastPriceVoteCount < minAuthorizations) && !isSpreadEnabled; + } + + function getPrice(address _token, uint256 _refPrice, bool _maximise) external override view returns (uint256) { + if (block.timestamp > lastUpdatedAt.add(priceDuration)) { return _refPrice; } + + uint256 fastPrice = prices[_token]; + if (fastPrice == 0) { return _refPrice; } + + uint256 maxPrice = _refPrice.mul(BASIS_POINTS_DIVISOR.add(maxDeviationBasisPoints)).div(BASIS_POINTS_DIVISOR); + uint256 minPrice = _refPrice.mul(BASIS_POINTS_DIVISOR.sub(maxDeviationBasisPoints)).div(BASIS_POINTS_DIVISOR); + + if (favorFastPrice()) { + if (fastPrice >= minPrice && fastPrice <= maxPrice) { + if (_maximise) { + if (_refPrice > fastPrice) { + uint256 volPrice = fastPrice.mul(BASIS_POINTS_DIVISOR.add(volBasisPoints)).div(BASIS_POINTS_DIVISOR); + // the volPrice should not be more than _refPrice + return volPrice > _refPrice ? _refPrice : volPrice; + } + return fastPrice; + } + + if (_refPrice < fastPrice) { + uint256 volPrice = fastPrice.mul(BASIS_POINTS_DIVISOR.sub(volBasisPoints)).div(BASIS_POINTS_DIVISOR); + // the volPrice should not be less than _refPrice + return volPrice < _refPrice ? _refPrice : volPrice; + } + + return fastPrice; + } + } + + if (_maximise) { + if (_refPrice > fastPrice) { return _refPrice; } + return fastPrice > maxPrice ? maxPrice : fastPrice; + } + + if (_refPrice < fastPrice) { return _refPrice; } + return fastPrice < minPrice ? minPrice : fastPrice; + } +} diff --git a/contracts/oracle/PriceFeed.sol b/contracts/oracle/PriceFeed.sol new file mode 100644 index 00000000..4a923b2d --- /dev/null +++ b/contracts/oracle/PriceFeed.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "./interfaces/IPriceFeed.sol"; + +contract PriceFeed is IPriceFeed { + int256 public answer; + uint80 public roundId; + string public override description = "PriceFeed"; + address public override aggregator; + + uint256 public decimals; + + address public gov; + + mapping (uint80 => int256) public answers; + mapping (address => bool) public isAdmin; + + constructor() public { + gov = msg.sender; + isAdmin[msg.sender] = true; + } + + function setAdmin(address _account, bool _isAdmin) public { + require(msg.sender == gov, "PriceFeed: forbidden"); + isAdmin[_account] = _isAdmin; + } + + function latestAnswer() public override view returns (int256) { + return answer; + } + + function latestRound() public override view returns (uint80) { + return roundId; + } + + function setLatestAnswer(int256 _answer) public { + require(isAdmin[gov], "PriceFeed: forbidden"); + roundId = roundId + 1; + answer = _answer; + answers[roundId] = _answer; + } + + // returns roundId, answer, startedAt, updatedAt, answeredInRound + function getRoundData(uint80 _roundId) public override view + returns (uint80, int256, uint256, uint256, uint80) + { + return (_roundId, answers[_roundId], 0, 0, 0); + } +} diff --git a/contracts/oracle/interfaces/IChainlinkFlags.sol b/contracts/oracle/interfaces/IChainlinkFlags.sol new file mode 100644 index 00000000..4323e52d --- /dev/null +++ b/contracts/oracle/interfaces/IChainlinkFlags.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IChainlinkFlags { + function getFlag(address) external view returns (bool); +} diff --git a/contracts/oracle/interfaces/IPriceFeed.sol b/contracts/oracle/interfaces/IPriceFeed.sol new file mode 100644 index 00000000..74b59ff0 --- /dev/null +++ b/contracts/oracle/interfaces/IPriceFeed.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IPriceFeed { + function description() external view returns (string memory); + function aggregator() external view returns (address); + function latestAnswer() external view returns (int256); + function latestRound() external view returns (uint80); + function getRoundData(uint80 roundId) external view returns (uint80, int256, uint256, uint256, uint80); +} diff --git a/contracts/oracle/interfaces/ISecondaryPriceFeed.sol b/contracts/oracle/interfaces/ISecondaryPriceFeed.sol new file mode 100644 index 00000000..cc437d1a --- /dev/null +++ b/contracts/oracle/interfaces/ISecondaryPriceFeed.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface ISecondaryPriceFeed { + function getPrice(address _token, uint256 _referencePrice, bool _maximise) external view returns (uint256); +} diff --git a/contracts/peripherals/BalanceUpdater.sol b/contracts/peripherals/BalanceUpdater.sol new file mode 100644 index 00000000..b4b40b93 --- /dev/null +++ b/contracts/peripherals/BalanceUpdater.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/math/SafeMath.sol"; +import "../core/interfaces/IVault.sol"; + +contract BalanceUpdater { + using SafeMath for uint256; + + function updateBalance( + address _vault, + address _token, + address _usdg, + uint256 _usdgAmount + ) public { + IVault vault = IVault(_vault); + IERC20 token = IERC20(_token); + uint256 poolAmount = vault.poolAmounts(_token); + uint256 fee = vault.feeReserves(_token); + uint256 balance = token.balanceOf(_vault); + + uint256 transferAmount = poolAmount.add(fee).sub(balance); + token.transferFrom(msg.sender, _vault, transferAmount); + IERC20(_usdg).transferFrom(msg.sender, _vault, _usdgAmount); + + vault.sellUSDG(_token, msg.sender); + } +} diff --git a/contracts/peripherals/BatchSender.sol b/contracts/peripherals/BatchSender.sol new file mode 100644 index 00000000..a2d0bdbd --- /dev/null +++ b/contracts/peripherals/BatchSender.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/math/SafeMath.sol"; + +contract BatchSender { + using SafeMath for uint256; + + address public admin; + + constructor() public { + admin = msg.sender; + } + + function send(IERC20 _token, address[] memory _accounts, uint256[] memory _amounts) public { + require(msg.sender == admin, "BatchSender: forbidden"); + + for (uint256 i = 0; i < _accounts.length; i++) { + address account = _accounts[i]; + uint256 amount = _amounts[i]; + _token.transferFrom(msg.sender, account, amount); + } + } +} diff --git a/contracts/peripherals/OrderBookReader.sol b/contracts/peripherals/OrderBookReader.sol new file mode 100644 index 00000000..1a6ab60e --- /dev/null +++ b/contracts/peripherals/OrderBookReader.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; + +import "../core/interfaces/IOrderBook.sol"; + +contract OrderBookReader { + using SafeMath for uint256; + + struct Vars { + uint256 i; + uint256 index; + address account; + uint256 uintLength; + uint256 addressLength; + } + + function getIncreaseOrders( + address payable _orderBookAddress, + address _account, + uint256[] memory _indices + ) external view returns (uint256[] memory, address[] memory) { + Vars memory vars = Vars(0, 0, _account, 5, 3); + + uint256[] memory uintProps = new uint256[](vars.uintLength * _indices.length); + address[] memory addressProps = new address[](vars.addressLength * _indices.length); + + IOrderBook orderBook = IOrderBook(_orderBookAddress); + + while (vars.i < _indices.length) { + vars.index = _indices[vars.i]; + ( + address purchaseToken, + uint256 purchaseTokenAmount, + address collateralToken, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold + ) = orderBook.getIncreaseOrder(vars.account, vars.index); + + uintProps[vars.i * vars.uintLength] = uint256(purchaseTokenAmount); + uintProps[vars.i * vars.uintLength + 1] = uint256(sizeDelta); + uintProps[vars.i * vars.uintLength + 2] = uint256(isLong ? 1 : 0); + uintProps[vars.i * vars.uintLength + 3] = uint256(triggerPrice); + uintProps[vars.i * vars.uintLength + 4] = uint256(triggerAboveThreshold ? 1 : 0); + + addressProps[vars.i * vars.addressLength] = (purchaseToken); + addressProps[vars.i * vars.addressLength + 1] = (collateralToken); + addressProps[vars.i * vars.addressLength + 2] = (indexToken); + + vars.i++; + } + + return (uintProps, addressProps); + } + + function getDecreaseOrders( + address payable _orderBookAddress, + address _account, + uint256[] memory _indices + ) external view returns (uint256[] memory, address[] memory) { + Vars memory vars = Vars(0, 0, _account, 5, 2); + + uint256[] memory uintProps = new uint256[](vars.uintLength * _indices.length); + address[] memory addressProps = new address[](vars.addressLength * _indices.length); + + IOrderBook orderBook = IOrderBook(_orderBookAddress); + + while (vars.i < _indices.length) { + vars.index = _indices[vars.i]; + ( + address collateralToken, + uint256 collateralDelta, + address indexToken, + uint256 sizeDelta, + bool isLong, + uint256 triggerPrice, + bool triggerAboveThreshold + ) = orderBook.getDecreaseOrder(vars.account, vars.index); + + uintProps[vars.i * vars.uintLength] = uint256(collateralDelta); + uintProps[vars.i * vars.uintLength + 1] = uint256(sizeDelta); + uintProps[vars.i * vars.uintLength + 2] = uint256(isLong ? 1 : 0); + uintProps[vars.i * vars.uintLength + 3] = uint256(triggerPrice); + uintProps[vars.i * vars.uintLength + 4] = uint256(triggerAboveThreshold ? 1 : 0); + + addressProps[vars.i * vars.addressLength] = (collateralToken); + addressProps[vars.i * vars.addressLength + 1] = (indexToken); + + vars.i++; + } + + return (uintProps, addressProps); + } + + function getSwapOrders( + address payable _orderBookAddress, + address _account, + uint256[] memory _indices + ) external view returns (uint256[] memory, address[] memory) { + Vars memory vars = Vars(0, 0, _account, 4, 3); + + uint256[] memory uintProps = new uint256[](vars.uintLength * _indices.length); + address[] memory addressProps = new address[](vars.addressLength * _indices.length); + + IOrderBook orderBook = IOrderBook(_orderBookAddress); + + while (vars.i < _indices.length) { + vars.index = _indices[vars.i]; + ( + address path0, + address path1, + address path2, + uint256 amountIn, + uint256 minOut, + uint256 triggerRatio, + bool triggerAboveThreshold + ) = orderBook.getSwapOrder(vars.account, vars.index); + + uintProps[vars.i * vars.uintLength] = uint256(amountIn); + uintProps[vars.i * vars.uintLength + 1] = uint256(minOut); + uintProps[vars.i * vars.uintLength + 2] = uint256(triggerRatio); + uintProps[vars.i * vars.uintLength + 3] = uint256(triggerAboveThreshold ? 1 : 0); + + addressProps[vars.i * vars.addressLength] = (path0); + addressProps[vars.i * vars.addressLength + 1] = (path1); + addressProps[vars.i * vars.addressLength + 2] = (path2); + + vars.i++; + } + + return (uintProps, addressProps); + } +} diff --git a/contracts/peripherals/Reader.sol b/contracts/peripherals/Reader.sol new file mode 100644 index 00000000..2c240f85 --- /dev/null +++ b/contracts/peripherals/Reader.sol @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/math/SafeMath.sol"; + +import "../core/interfaces/IVault.sol"; +import "../core/interfaces/IVaultPriceFeed.sol"; +import "../tokens/interfaces/IYieldTracker.sol"; +import "../tokens/interfaces/IYieldToken.sol"; +import "../amm/interfaces/IPancakeFactory.sol"; + +contract Reader { + using SafeMath for uint256; + + uint256 public constant BASIS_POINTS_DIVISOR = 10000; + uint256 public constant POSITION_PROPS_LENGTH = 9; + uint256 public constant PRICE_PRECISION = 10 ** 30; + uint256 public constant USDG_DECIMALS = 18; + + function getMaxAmountIn(IVault _vault, address _tokenIn, address _tokenOut) public view returns (uint256) { + uint256 priceIn = _vault.getMinPrice(_tokenIn); + uint256 priceOut = _vault.getMaxPrice(_tokenOut); + uint256 poolAmount = _vault.poolAmounts(_tokenOut); + uint256 reservedAmount = _vault.reservedAmounts(_tokenOut); + uint256 bufferAmount = _vault.bufferAmounts(_tokenOut); + uint256 availableAmount = poolAmount.sub(reservedAmount > bufferAmount ? reservedAmount : bufferAmount); + + uint256 tokenInDecimals = _vault.tokenDecimals(_tokenIn); + uint256 tokenOutDecimals = _vault.tokenDecimals(_tokenOut); + + return availableAmount.mul(priceOut).div(priceIn).mul(10 ** tokenInDecimals).div(10 ** tokenOutDecimals); + } + + function getAmountOut(IVault _vault, address _tokenIn, address _tokenOut, uint256 _amountIn) public view returns (uint256, uint256) { + uint256 priceIn = _vault.getMinPrice(_tokenIn); + + uint256 tokenInDecimals = _vault.tokenDecimals(_tokenIn); + uint256 tokenOutDecimals = _vault.tokenDecimals(_tokenOut); + + uint256 feeBasisPoints; + { + uint256 usdgAmount = _amountIn.mul(priceIn).div(PRICE_PRECISION); + usdgAmount = usdgAmount.mul(10 ** USDG_DECIMALS).div(10 ** tokenInDecimals); + + bool isStableSwap = _vault.stableTokens(_tokenIn) && _vault.stableTokens(_tokenOut); + uint256 baseBps = isStableSwap ? _vault.stableSwapFeeBasisPoints() : _vault.swapFeeBasisPoints(); + uint256 taxBps = isStableSwap ? _vault.stableTaxBasisPoints() : _vault.taxBasisPoints(); + uint256 feesBasisPoints0 = _vault.getFeeBasisPoints(_tokenIn, usdgAmount, baseBps, taxBps, true); + uint256 feesBasisPoints1 = _vault.getFeeBasisPoints(_tokenOut, usdgAmount, baseBps, taxBps, false); + // use the higher of the two fee basis points + feeBasisPoints = feesBasisPoints0 > feesBasisPoints1 ? feesBasisPoints0 : feesBasisPoints1; + } + + uint256 priceOut = _vault.getMaxPrice(_tokenOut); + uint256 amountOut = _amountIn.mul(priceIn).div(priceOut); + amountOut = amountOut.mul(10 ** tokenOutDecimals).div(10 ** tokenInDecimals); + + uint256 amountOutAfterFees = amountOut.mul(BASIS_POINTS_DIVISOR.sub(feeBasisPoints)).div(BASIS_POINTS_DIVISOR); + uint256 feeAmount = amountOut.sub(amountOutAfterFees); + + return (amountOutAfterFees, feeAmount); + } + + function getFeeBasisPoints(IVault _vault, address _tokenIn, address _tokenOut, uint256 _amountIn) public view returns (uint256, uint256, uint256) { + uint256 priceIn = _vault.getMinPrice(_tokenIn); + uint256 tokenInDecimals = _vault.tokenDecimals(_tokenIn); + + uint256 usdgAmount = _amountIn.mul(priceIn).div(PRICE_PRECISION); + usdgAmount = usdgAmount.mul(10 ** USDG_DECIMALS).div(10 ** tokenInDecimals); + + bool isStableSwap = _vault.stableTokens(_tokenIn) && _vault.stableTokens(_tokenOut); + uint256 baseBps = isStableSwap ? _vault.stableSwapFeeBasisPoints() : _vault.swapFeeBasisPoints(); + uint256 taxBps = isStableSwap ? _vault.stableTaxBasisPoints() : _vault.taxBasisPoints(); + uint256 feesBasisPoints0 = _vault.getFeeBasisPoints(_tokenIn, usdgAmount, baseBps, taxBps, true); + uint256 feesBasisPoints1 = _vault.getFeeBasisPoints(_tokenOut, usdgAmount, baseBps, taxBps, false); + // use the higher of the two fee basis points + uint256 feeBasisPoints = feesBasisPoints0 > feesBasisPoints1 ? feesBasisPoints0 : feesBasisPoints1; + + return (feeBasisPoints, feesBasisPoints0, feesBasisPoints1); + } + + function getFees(address _vault, address[] memory _tokens) public view returns (uint256[] memory) { + uint256[] memory amounts = new uint256[](_tokens.length); + for (uint256 i = 0; i < _tokens.length; i++) { + amounts[i] = IVault(_vault).feeReserves(_tokens[i]); + } + return amounts; + } + + function getTotalStaked(address[] memory _yieldTokens) public view returns (uint256[] memory) { + uint256[] memory amounts = new uint256[](_yieldTokens.length); + for (uint256 i = 0; i < _yieldTokens.length; i++) { + IYieldToken yieldToken = IYieldToken(_yieldTokens[i]); + amounts[i] = yieldToken.totalStaked(); + } + return amounts; + } + + function getStakingInfo(address _account, address[] memory _yieldTrackers) public view returns (uint256[] memory) { + uint256 propsLength = 2; + uint256[] memory amounts = new uint256[](_yieldTrackers.length * propsLength); + for (uint256 i = 0; i < _yieldTrackers.length; i++) { + IYieldTracker yieldTracker = IYieldTracker(_yieldTrackers[i]); + amounts[i * propsLength] = yieldTracker.claimable(_account); + amounts[i * propsLength + 1] = yieldTracker.getTokensPerInterval(); + } + return amounts; + } + + function getPairInfo(address _factory, address[] memory _tokens) public view returns (uint256[] memory) { + uint256 inputLength = 2; + uint256 propsLength = 2; + uint256[] memory amounts = new uint256[](_tokens.length / inputLength * propsLength); + for (uint256 i = 0; i < _tokens.length / inputLength; i++) { + address token0 = _tokens[i * inputLength]; + address token1 = _tokens[i * inputLength + 1]; + address pair = IPancakeFactory(_factory).getPair(token0, token1); + + amounts[i * propsLength] = IERC20(token0).balanceOf(pair); + amounts[i * propsLength + 1] = IERC20(token1).balanceOf(pair); + } + return amounts; + } + + function getFundingRates(address _vault, address _weth, address[] memory _tokens) public view returns (uint256[] memory) { + uint256 propsLength = 2; + uint256[] memory fundingRates = new uint256[](_tokens.length * propsLength); + IVault vault = IVault(_vault); + uint256 fundingRateFactor = vault.fundingRateFactor(); + + for (uint256 i = 0; i < _tokens.length; i++) { + address token = _tokens[i]; + if (token == address(0)) { + token = _weth; + } + uint256 reservedAmount = vault.reservedAmounts(token); + uint256 poolAmount = vault.poolAmounts(token); + + if (poolAmount > 0) { + fundingRates[i * propsLength] = fundingRateFactor.mul(reservedAmount).div(poolAmount); + } + + if (vault.cumulativeFundingRates(token) > 0) { + uint256 nextRate = vault.getNextFundingRate(token); + uint256 baseRate = vault.cumulativeFundingRates(token); + fundingRates[i * propsLength + 1] = baseRate.add(nextRate); + } + } + + return fundingRates; + } + + function getTokenSupply(IERC20 _token, address[] memory _excludedAccounts) public view returns (uint256) { + uint256 supply = _token.totalSupply(); + for (uint256 i = 0; i < _excludedAccounts.length; i++) { + address account = _excludedAccounts[i]; + uint256 balance = _token.balanceOf(account); + supply = supply.sub(balance); + } + return supply; + } + + function getTokenBalances(address _account, address[] memory _tokens) public view returns (uint256[] memory) { + uint256[] memory balances = new uint256[](_tokens.length); + for (uint256 i = 0; i < _tokens.length; i++) { + address token = _tokens[i]; + if (token == address(0)) { + balances[i] = _account.balance; + continue; + } + balances[i] = IERC20(token).balanceOf(_account); + } + return balances; + } + + function getTokenBalancesWithSupplies(address _account, address[] memory _tokens) public view returns (uint256[] memory) { + uint256 propsLength = 2; + uint256[] memory balances = new uint256[](_tokens.length * propsLength); + for (uint256 i = 0; i < _tokens.length; i++) { + address token = _tokens[i]; + if (token == address(0)) { + balances[i * propsLength] = _account.balance; + balances[i * propsLength + 1] = 0; + continue; + } + balances[i * propsLength] = IERC20(token).balanceOf(_account); + balances[i * propsLength + 1] = IERC20(token).totalSupply(); + } + return balances; + } + + function getPrices(IVaultPriceFeed _priceFeed, address[] memory _tokens) public view returns (uint256[] memory) { + uint256 propsLength = 6; + + uint256[] memory amounts = new uint256[](_tokens.length * propsLength); + + for (uint256 i = 0; i < _tokens.length; i++) { + address token = _tokens[i]; + amounts[i * propsLength] = _priceFeed.getPrice(token, true, true, false); + amounts[i * propsLength + 1] = _priceFeed.getPrice(token, false, true, false); + amounts[i * propsLength + 2] = _priceFeed.getPrimaryPrice(token, true); + amounts[i * propsLength + 3] = _priceFeed.getPrimaryPrice(token, false); + amounts[i * propsLength + 4] = _priceFeed.isAdjustmentAdditive(token) ? 1 : 0; + amounts[i * propsLength + 5] = _priceFeed.adjustmentBasisPoints(token); + } + + return amounts; + } + + function getVaultTokenInfo(address _vault, address _weth, uint256 _usdgAmount, address[] memory _tokens) public view returns (uint256[] memory) { + uint256 propsLength = 10; + + IVault vault = IVault(_vault); + IVaultPriceFeed priceFeed = IVaultPriceFeed(vault.priceFeed()); + + uint256[] memory amounts = new uint256[](_tokens.length * propsLength); + for (uint256 i = 0; i < _tokens.length; i++) { + address token = _tokens[i]; + if (token == address(0)) { + token = _weth; + } + amounts[i * propsLength] = vault.poolAmounts(token); + amounts[i * propsLength + 1] = vault.reservedAmounts(token); + amounts[i * propsLength + 2] = vault.usdgAmounts(token); + amounts[i * propsLength + 3] = vault.getRedemptionAmount(token, _usdgAmount); + amounts[i * propsLength + 4] = vault.tokenWeights(token); + amounts[i * propsLength + 5] = vault.getMinPrice(token); + amounts[i * propsLength + 6] = vault.getMaxPrice(token); + amounts[i * propsLength + 7] = vault.guaranteedUsd(token); + amounts[i * propsLength + 8] = priceFeed.getPrice(token, false, false, false); + amounts[i * propsLength + 9] = priceFeed.getPrice(token, true, false, false); + } + + return amounts; + } + + function getPositions(address _vault, address _account, address[] memory _collateralTokens, address[] memory _indexTokens, bool[] memory _isLong) public view returns(uint256[] memory) { + uint256[] memory amounts = new uint256[](_collateralTokens.length * POSITION_PROPS_LENGTH); + + for (uint256 i = 0; i < _collateralTokens.length; i++) { + { + (uint256 size, + uint256 collateral, + uint256 averagePrice, + uint256 entryFundingRate, + /* reserveAmount */, + uint256 realisedPnl, + bool hasRealisedProfit, + uint256 lastIncreasedTime) = IVault(_vault).getPosition(_account, _collateralTokens[i], _indexTokens[i], _isLong[i]); + + amounts[i * POSITION_PROPS_LENGTH] = size; + amounts[i * POSITION_PROPS_LENGTH + 1] = collateral; + amounts[i * POSITION_PROPS_LENGTH + 2] = averagePrice; + amounts[i * POSITION_PROPS_LENGTH + 3] = entryFundingRate; + amounts[i * POSITION_PROPS_LENGTH + 4] = hasRealisedProfit ? 1 : 0; + amounts[i * POSITION_PROPS_LENGTH + 5] = realisedPnl; + amounts[i * POSITION_PROPS_LENGTH + 6] = lastIncreasedTime; + } + + uint256 size = amounts[i * POSITION_PROPS_LENGTH]; + uint256 averagePrice = amounts[i * POSITION_PROPS_LENGTH + 2]; + uint256 lastIncreasedTime = amounts[i * POSITION_PROPS_LENGTH + 6]; + if (averagePrice > 0) { + (bool hasProfit, uint256 delta) = IVault(_vault).getDelta(_indexTokens[i], size, averagePrice, _isLong[i], lastIncreasedTime); + amounts[i * POSITION_PROPS_LENGTH + 7] = hasProfit ? 1 : 0; + amounts[i * POSITION_PROPS_LENGTH + 8] = delta; + } + } + + return amounts; + } +} diff --git a/contracts/peripherals/RewardReader.sol b/contracts/peripherals/RewardReader.sol new file mode 100644 index 00000000..325e1b34 --- /dev/null +++ b/contracts/peripherals/RewardReader.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/math/SafeMath.sol"; + +import "../staking/interfaces/IRewardTracker.sol"; + +contract RewardReader { + using SafeMath for uint256; + + function getDepositBalances(address _account, address[] memory _depositTokens, address[] memory _rewardTrackers) public view returns (uint256[] memory) { + uint256[] memory amounts = new uint256[](_rewardTrackers.length); + for (uint256 i = 0; i < _rewardTrackers.length; i++) { + IRewardTracker rewardTracker = IRewardTracker(_rewardTrackers[i]); + amounts[i] = rewardTracker.depositBalances(_account, _depositTokens[i]); + } + return amounts; + } + + function getStakingInfo(address _account, address[] memory _rewardTrackers) public view returns (uint256[] memory) { + uint256 propsLength = 5; + uint256[] memory amounts = new uint256[](_rewardTrackers.length * propsLength); + for (uint256 i = 0; i < _rewardTrackers.length; i++) { + IRewardTracker rewardTracker = IRewardTracker(_rewardTrackers[i]); + amounts[i * propsLength] = rewardTracker.claimable(_account); + amounts[i * propsLength + 1] = rewardTracker.tokensPerInterval(); + amounts[i * propsLength + 2] = rewardTracker.averageStakedAmounts(_account); + amounts[i * propsLength + 3] = rewardTracker.cumulativeRewards(_account); + amounts[i * propsLength + 4] = IERC20(_rewardTrackers[i]).totalSupply(); + } + return amounts; + } +} diff --git a/contracts/peripherals/Timelock.sol b/contracts/peripherals/Timelock.sol new file mode 100644 index 00000000..f4e4afe1 --- /dev/null +++ b/contracts/peripherals/Timelock.sol @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "./interfaces/ITimelockTarget.sol"; +import "./interfaces/ITimelock.sol"; +import "../core/interfaces/IVault.sol"; +import "../core/interfaces/IVaultPriceFeed.sol"; +import "../core/interfaces/IRouter.sol"; +import "../tokens/interfaces/IYieldToken.sol"; +import "../tokens/interfaces/IBaseToken.sol"; +import "../tokens/interfaces/IMintable.sol"; +import "../tokens/interfaces/IBridge.sol"; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; + +contract Timelock is ITimelock { + using SafeMath for uint256; + + uint256 public constant PRICE_PRECISION = 10 ** 30; + uint256 public constant MAX_BUFFER = 5 days; + uint256 public constant MAX_FEE_BASIS_POINTS = 300; // 3% + + uint256 public buffer; + address public admin; + + address public tokenManager; + uint256 public maxTokenSupply; + + mapping (bytes32 => uint256) public pendingActions; + mapping (address => bool) public excludedTokens; + + event SignalPendingAction(bytes32 action); + event SignalApprove(address token, address spender, uint256 amount, bytes32 action); + event SignalSetGov(address target, address gov, bytes32 action); + event SignalSetPriceFeed(address vault, address priceFeed, bytes32 action); + event SignalAddPlugin(address router, address plugin, bytes32 action); + event SignalVaultSetTokenConfig( + address vault, + address token, + uint256 tokenDecimals, + uint256 tokenWeight, + uint256 minProfitBps, + uint256 maxUsdgAmount, + bool isStable, + bool isShortable + ); + event ClearAction(bytes32 action); + + modifier onlyAdmin() { + require(msg.sender == admin, "Timelock: forbidden"); + _; + } + + modifier onlyTokenManager() { + require(msg.sender == tokenManager, "Timelock: forbidden"); + _; + } + + constructor(uint256 _buffer, address _tokenManager, uint256 _maxTokenSupply) public { + require(_buffer <= MAX_BUFFER, "Timelock: invalid _buffer"); + admin = msg.sender; + buffer = _buffer; + tokenManager = _tokenManager; + maxTokenSupply = _maxTokenSupply; + } + + function setAdmin(address _admin) external override onlyTokenManager { + admin = _admin; + } + + function setBuffer(uint256 _buffer) external onlyAdmin { + require(_buffer <= MAX_BUFFER, "Timelock: invalid _buffer"); + require(_buffer > buffer, "Timelock: buffer cannot be decreased"); + buffer = _buffer; + } + + function mint(address _token, uint256 _amount) external onlyAdmin { + IMintable mintable = IMintable(_token); + + if (!mintable.isMinter(address(this))) { + mintable.setMinter(address(this), true); + } + + mintable.mint(tokenManager, _amount); + require(IERC20(_token).totalSupply() <= maxTokenSupply, "Timelock: maxTokenSupply exceeded"); + } + + function setFees( + address _vault, + uint256 _taxBasisPoints, + uint256 _stableTaxBasisPoints, + uint256 _mintBurnFeeBasisPoints, + uint256 _swapFeeBasisPoints, + uint256 _stableSwapFeeBasisPoints, + uint256 _marginFeeBasisPoints, + uint256 _liquidationFeeUsd, + uint256 _minProfitTime, + bool _hasDynamicFees + ) external onlyAdmin { + require(_taxBasisPoints < MAX_FEE_BASIS_POINTS, "Timelock: invalid _taxBasisPoints"); + require(_stableTaxBasisPoints < MAX_FEE_BASIS_POINTS, "Timelock: invalid _stableTaxBasisPoints"); + require(_mintBurnFeeBasisPoints < MAX_FEE_BASIS_POINTS, "Timelock: invalid _mintBurnFeeBasisPoints"); + require(_swapFeeBasisPoints < MAX_FEE_BASIS_POINTS, "Timelock: invalid _swapFeeBasisPoints"); + require(_stableSwapFeeBasisPoints < MAX_FEE_BASIS_POINTS, "Timelock: invalid _stableSwapFeeBasisPoints"); + require(_marginFeeBasisPoints < MAX_FEE_BASIS_POINTS, "Timelock: invalid _marginFeeBasisPoints"); + require(_liquidationFeeUsd < 10 * PRICE_PRECISION, "Timelock: invalid _liquidationFeeUsd"); + + IVault(_vault).setFees( + _taxBasisPoints, + _stableTaxBasisPoints, + _mintBurnFeeBasisPoints, + _swapFeeBasisPoints, + _stableSwapFeeBasisPoints, + _marginFeeBasisPoints, + _liquidationFeeUsd, + _minProfitTime, + _hasDynamicFees + ); + } + + function setTokenConfig( + address _vault, + address _token, + uint256 _tokenWeight, + uint256 _minProfitBps, + uint256 _maxUsdgAmount + ) external onlyAdmin { + require(_minProfitBps <= 500, "Timelock: invalid _minProfitBps"); + + IVault vault = IVault(_vault); + require(vault.whitelistedTokens(_token), "Timelock: token not yet whitelisted"); + + uint256 tokenDecimals = vault.tokenDecimals(_token); + bool isStable = vault.stableTokens(_token); + bool isShortable = vault.shortableTokens(_token); + + IVault(_vault).setTokenConfig( + _token, + tokenDecimals, + _tokenWeight, + _minProfitBps, + _maxUsdgAmount, + isStable, + isShortable + ); + } + + function removeAdmin(address _token, address _account) external onlyAdmin { + IYieldToken(_token).removeAdmin(_account); + } + + function setBufferAmount(address _vault, address _token, uint256 _amount) external onlyAdmin { + IVault(_vault).setBufferAmount(_token, _amount); + } + + function setIsAmmEnabled(address _priceFeed, bool _isEnabled) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setIsAmmEnabled(_isEnabled); + } + + function setIsSecondaryPriceEnabled(address _priceFeed, bool _isEnabled) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setIsSecondaryPriceEnabled(_isEnabled); + } + + function setMaxStrictPriceDeviation(address _priceFeed, uint256 _maxStrictPriceDeviation) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setMaxStrictPriceDeviation(_maxStrictPriceDeviation); + } + + function setUseV2Pricing(address _priceFeed, bool _useV2Pricing) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setUseV2Pricing(_useV2Pricing); + } + + function setAdjustment(address _priceFeed, address _token, bool _isAdditive, uint256 _adjustmentBps) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setAdjustment(_token, _isAdditive, _adjustmentBps); + } + + function setSpreadBasisPoints(address _priceFeed, address _token, uint256 _spreadBasisPoints) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setSpreadBasisPoints(_token, _spreadBasisPoints); + } + + function setSpreadThresholdBasisPoints(address _priceFeed, uint256 _spreadThresholdBasisPoints) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setSpreadThresholdBasisPoints(_spreadThresholdBasisPoints); + } + + function setFavorPrimaryPrice(address _priceFeed, bool _favorPrimaryPrice) external onlyAdmin { + IVaultPriceFeed(_priceFeed).setFavorPrimaryPrice(_favorPrimaryPrice); + } + + function setPriceSampleSpace(address _priceFeed,uint256 _priceSampleSpace) external onlyAdmin { + require(_priceSampleSpace <= 5, "Invalid _priceSampleSpace"); + IVaultPriceFeed(_priceFeed).setPriceSampleSpace(_priceSampleSpace); + } + + function setIsSwapEnabled(address _vault, bool _isSwapEnabled) external onlyAdmin { + IVault(_vault).setIsSwapEnabled(_isSwapEnabled); + } + + function setIsLeverageEnabled(address _vault, bool _isLeverageEnabled) external onlyAdmin { + IVault(_vault).setIsLeverageEnabled(_isLeverageEnabled); + } + + function setMaxGasPrice(address _vault,uint256 _maxGasPrice) external onlyAdmin { + require(_maxGasPrice > 5000000000, "Invalid _maxGasPrice"); + IVault(_vault).setMaxGasPrice(_maxGasPrice); + } + + function withdrawFees(address _vault,address _token, address _receiver) external onlyAdmin { + IVault(_vault).withdrawFees(_token, _receiver); + } + + function addExcludedToken(address _token) external onlyAdmin { + excludedTokens[_token] = true; + } + + function setInPrivateTransferMode(address _token, bool _inPrivateTransferMode) external onlyAdmin { + if (excludedTokens[_token]) { + // excludedTokens can only have their transfers enabled + require(_inPrivateTransferMode == false, "Timelock: invalid _inPrivateTransferMode"); + } + + IBaseToken(_token).setInPrivateTransferMode(_inPrivateTransferMode); + } + + function testBridge(address _bridge, address _token, uint256 _amount, address _receiver) external onlyAdmin { + require(!excludedTokens[_token], "Timelock: _token is excluded"); + + IBaseToken(_token).setInPrivateTransferMode(false); + + IERC20(_token).transferFrom(admin, address(this), _amount); + IERC20(_token).approve(_bridge, _amount); + IBridge(_bridge).wrap(_amount, _receiver); + + IBaseToken(_token).setInPrivateTransferMode(true); + } + + function transferIn(address _sender, address _token, uint256 _amount) external onlyAdmin { + IERC20(_token).transferFrom(_sender, address(this), _amount); + } + + function signalApprove(address _token, address _spender, uint256 _amount) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount)); + _setPendingAction(action); + emit SignalApprove(_token, _spender, _amount, action); + } + + function approve(address _token, address _spender, uint256 _amount) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount)); + _validateAction(action); + _clearAction(action); + IERC20(_token).approve(_spender, _amount); + } + + function signalSetGov(address _target, address _gov) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("setGov", _target, _gov)); + _setPendingAction(action); + emit SignalSetGov(_target, _gov, action); + } + + function setGov(address _target, address _gov) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("setGov", _target, _gov)); + _validateAction(action); + _clearAction(action); + ITimelockTarget(_target).setGov(_gov); + } + + function signalSetPriceFeed(address _vault, address _priceFeed) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("setPriceFeed", _vault, _priceFeed)); + _setPendingAction(action); + emit SignalSetPriceFeed(_vault, _priceFeed, action); + } + + function setPriceFeed(address _vault, address _priceFeed) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("setPriceFeed", _vault, _priceFeed)); + _validateAction(action); + _clearAction(action); + IVault(_vault).setPriceFeed(_priceFeed); + } + + function signalAddPlugin(address _router, address _plugin) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("addPlugin", _router, _plugin)); + _setPendingAction(action); + emit SignalAddPlugin(_router, _plugin, action); + } + + function addPlugin(address _router, address _plugin) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked("addPlugin", _router, _plugin)); + _validateAction(action); + _clearAction(action); + IRouter(_router).addPlugin(_plugin); + } + + function signalVaultSetTokenConfig( + address _vault, + address _token, + uint256 _tokenDecimals, + uint256 _tokenWeight, + uint256 _minProfitBps, + uint256 _maxUsdgAmount, + bool _isStable, + bool _isShortable + ) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked( + "vaultSetTokenConfig", + _vault, + _token, + _tokenDecimals, + _tokenWeight, + _minProfitBps, + _maxUsdgAmount, + _isStable, + _isShortable + )); + + _setPendingAction(action); + + emit SignalVaultSetTokenConfig( + _vault, + _token, + _tokenDecimals, + _tokenWeight, + _minProfitBps, + _maxUsdgAmount, + _isStable, + _isShortable + ); + } + + function vaultSetTokenConfig( + address _vault, + address _token, + uint256 _tokenDecimals, + uint256 _tokenWeight, + uint256 _minProfitBps, + uint256 _maxUsdgAmount, + bool _isStable, + bool _isShortable + ) external onlyAdmin { + bytes32 action = keccak256(abi.encodePacked( + "vaultSetTokenConfig", + _vault, + _token, + _tokenDecimals, + _tokenWeight, + _minProfitBps, + _maxUsdgAmount, + _isStable, + _isShortable + )); + + _validateAction(action); + _clearAction(action); + + IVault(_vault).setTokenConfig( + _token, + _tokenDecimals, + _tokenWeight, + _minProfitBps, + _maxUsdgAmount, + _isStable, + _isShortable + ); + } + + function cancelAction(bytes32 _action) external onlyAdmin { + _clearAction(_action); + } + + function _setPendingAction(bytes32 _action) private { + pendingActions[_action] = block.timestamp.add(buffer); + emit SignalPendingAction(_action); + } + + function _validateAction(bytes32 _action) private view { + require(pendingActions[_action] != 0, "Timelock: action not signalled"); + require(pendingActions[_action] < block.timestamp, "Timelock: action time not yet passed"); + } + + function _clearAction(bytes32 _action) private { + require(pendingActions[_action] != 0, "Timelock: invalid _action"); + delete pendingActions[_action]; + emit ClearAction(_action); + } +} diff --git a/contracts/peripherals/VaultReader.sol b/contracts/peripherals/VaultReader.sol new file mode 100644 index 00000000..8cd76ec6 --- /dev/null +++ b/contracts/peripherals/VaultReader.sol @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../core/interfaces/IVault.sol"; + +contract VaultReader { + IVault public vault; + + constructor(IVault _vault) public { + vault = _vault; + } + + function isInitialized() external view returns (bool) { + return vault.isInitialized(); + } + function isSwapEnabled() external view returns (bool) { + return vault.isSwapEnabled(); + } + function isLeverageEnabled() external view returns (bool) { + return vault.isLeverageEnabled(); + } + + function router() external view returns (address) { + return vault.router(); + } + function usdg() external view returns (address) { + return vault.usdg(); + } + function gov() external view returns (address) { + return vault.gov(); + } + + function whitelistedTokenCount() external view returns (uint256) { + return vault.whitelistedTokenCount(); + } + function maxLeverage() external view returns (uint256) { + return vault.maxLeverage(); + } + + function minProfitTime() external view returns (uint256) { + return vault.minProfitTime(); + } + function hasDynamicFees() external view returns (bool) { + return vault.hasDynamicFees(); + } + function fundingInterval() external view returns (uint256) { + return vault.fundingInterval(); + } + function totalTokenWeights() external view returns (uint256) { + return vault.totalTokenWeights(); + } + + function inManagerMode() external view returns (bool) { + return vault.inManagerMode(); + } + function inPrivateLiquidationMode() external view returns (bool) { + return vault.inPrivateLiquidationMode(); + } + + function maxGasPrice() external view returns (uint256) { + return vault.maxGasPrice(); + } + + function approvedRouters(address _account, address _router) external view returns (bool) { + return vault.approvedRouters(_account, _router); + } + function isLiquidator(address _account) external view returns (bool) { + return vault.isLiquidator(_account); + } + function isManager(address _account) external view returns (bool) { + return vault.isManager(_account); + } + + function minProfitBasisPoints(address _token) external view returns (uint256) { + return vault.minProfitBasisPoints(_token); + } + function tokenBalances(address _token) external view returns (uint256) { + return vault.tokenBalances(_token); + } + function lastFundingTimes(address _token) external view returns (uint256) { + return vault.lastFundingTimes(_token); + } + + function priceFeed() external view returns (address) { + return vault.priceFeed(); + } + function fundingRateFactor() external view returns (uint256) { + return vault.fundingRateFactor(); + } + + function stableFundingRateFactor() external view returns (uint256) { + return vault.stableFundingRateFactor(); + } + function cumulativeFundingRates(address _token) external view returns (uint256) { + return vault.cumulativeFundingRates(_token); + } + function getFeeBasisPoints(address _token, uint256 _usdgDelta, uint256 _feeBasisPoints, uint256 _taxBasisPoints, bool _increment) external view returns (uint256) { + return vault.getFeeBasisPoints(_token, _usdgDelta, _feeBasisPoints, _taxBasisPoints, _increment); + } + + function liquidationFeeUsd() external view returns (uint256) { + return vault.liquidationFeeUsd(); + } + function taxBasisPoints() external view returns (uint256) { + return vault.taxBasisPoints(); + } + function stableTaxBasisPoints() external view returns (uint256) { + return vault.stableTaxBasisPoints(); + } + function mintBurnFeeBasisPoints() external view returns (uint256) { + return vault.mintBurnFeeBasisPoints(); + } + function swapFeeBasisPoints() external view returns (uint256) { + return vault.swapFeeBasisPoints(); + } + function stableSwapFeeBasisPoints() external view returns (uint256) { + return vault.stableSwapFeeBasisPoints(); + } + function marginFeeBasisPoints() external view returns (uint256) { + return vault.marginFeeBasisPoints(); + } + + function allWhitelistedTokensLength() external view returns (uint256) { + return vault.allWhitelistedTokensLength(); + } + function allWhitelistedTokens(uint256 _index) external view returns (address) { + return vault.allWhitelistedTokens(_index); + } + function whitelistedTokens(address _token) external view returns (bool) { + return vault.whitelistedTokens(_token); + } + function stableTokens(address _token) external view returns (bool) { + return vault.stableTokens(_token); + } + function shortableTokens(address _token) external view returns (bool) { + return vault.shortableTokens(_token); + } + function feeReserves(address _token) external view returns (uint256) { + return vault.feeReserves(_token); + } + function globalShortSizes(address _token) external view returns (uint256) { + return vault.globalShortSizes(_token); + } + function globalShortAveragePrices(address _token) external view returns (uint256) { + return vault.globalShortAveragePrices(_token); + } + function tokenDecimals(address _token) external view returns (uint256) { + return vault.tokenDecimals(_token); + } + function tokenWeights(address _token) external view returns (uint256) { + return vault.tokenWeights(_token); + } + function guaranteedUsd(address _token) external view returns (uint256) { + return vault.guaranteedUsd(_token); + } + function poolAmounts(address _token) external view returns (uint256) { + return vault.poolAmounts(_token); + } + function bufferAmounts(address _token) external view returns (uint256) { + return vault.bufferAmounts(_token); + } + function reservedAmounts(address _token) external view returns (uint256) { + return vault.reservedAmounts(_token); + } + function usdgAmounts(address _token) external view returns (uint256) { + return vault.usdgAmounts(_token); + } + function maxUsdgAmounts(address _token) external view returns (uint256) { + return vault.maxUsdgAmounts(_token); + } + function getMaxPrice(address _token) external view returns (uint256) { + return vault.getMaxPrice(_token); + } + function getMinPrice(address _token) external view returns (uint256) { + return vault.getMinPrice(_token); + } +} diff --git a/contracts/peripherals/interfaces/ITimelock.sol b/contracts/peripherals/interfaces/ITimelock.sol new file mode 100644 index 00000000..d121ae64 --- /dev/null +++ b/contracts/peripherals/interfaces/ITimelock.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface ITimelock { + function setAdmin(address _admin) external; +} diff --git a/contracts/peripherals/interfaces/ITimelockTarget.sol b/contracts/peripherals/interfaces/ITimelockTarget.sol new file mode 100644 index 00000000..63ab2443 --- /dev/null +++ b/contracts/peripherals/interfaces/ITimelockTarget.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface ITimelockTarget { + function setGov(address _gov) external; + function withdrawToken(address _token, address _account, uint256 _amount) external; +} diff --git a/contracts/staking/BonusDistributor.sol b/contracts/staking/BonusDistributor.sol new file mode 100644 index 00000000..295b7e8b --- /dev/null +++ b/contracts/staking/BonusDistributor.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./interfaces/IRewardDistributor.sol"; +import "./interfaces/IRewardTracker.sol"; +import "../access/Governable.sol"; + +contract BonusDistributor is IRewardDistributor, ReentrancyGuard, Governable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + uint256 public constant BASIS_POINTS_DIVISOR = 10000; + uint256 public constant BONUS_DURATION = 365 days; + + uint256 public bonusMultiplierBasisPoints; + + address public override rewardToken; + uint256 public lastDistributionTime; + address public rewardTracker; + + address public admin; + + event Distribute(uint256 amount); + event BonusMultiplierChange(uint256 amount); + + modifier onlyAdmin() { + require(msg.sender == admin, "BonusDistributor: forbidden"); + _; + } + + constructor(address _rewardToken, address _rewardTracker) public { + rewardToken = _rewardToken; + rewardTracker = _rewardTracker; + admin = msg.sender; + } + + function setAdmin(address _admin) external onlyGov { + admin = _admin; + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { + IERC20(_token).safeTransfer(_account, _amount); + } + + function updateLastDistributionTime() external onlyAdmin { + lastDistributionTime = block.timestamp; + } + + function setBonusMultiplier(uint256 _bonusMultiplierBasisPoints) external onlyAdmin { + require(lastDistributionTime != 0, "BonusDistributor: invalid lastDistributionTime"); + IRewardTracker(rewardTracker).updateRewards(); + bonusMultiplierBasisPoints = _bonusMultiplierBasisPoints; + emit BonusMultiplierChange(_bonusMultiplierBasisPoints); + } + + function tokensPerInterval() public view override returns (uint256) { + uint256 supply = IERC20(rewardTracker).totalSupply(); + return supply.mul(bonusMultiplierBasisPoints).div(BASIS_POINTS_DIVISOR).div(BONUS_DURATION); + } + + function pendingRewards() public view override returns (uint256) { + if (block.timestamp == lastDistributionTime) { + return 0; + } + + uint256 supply = IERC20(rewardTracker).totalSupply(); + uint256 timeDiff = block.timestamp.sub(lastDistributionTime); + + return timeDiff.mul(supply).mul(bonusMultiplierBasisPoints).div(BASIS_POINTS_DIVISOR).div(BONUS_DURATION); + } + + function distribute() external override returns (uint256) { + require(msg.sender == rewardTracker, "BonusDistributor: invalid msg.sender"); + uint256 amount = pendingRewards(); + if (amount == 0) { return 0; } + + lastDistributionTime = block.timestamp; + + uint256 balance = IERC20(rewardToken).balanceOf(address(this)); + if (amount > balance) { amount = balance; } + + IERC20(rewardToken).safeTransfer(msg.sender, amount); + + emit Distribute(amount); + return amount; + } +} diff --git a/contracts/staking/RewardDistributor.sol b/contracts/staking/RewardDistributor.sol new file mode 100644 index 00000000..fd610e2f --- /dev/null +++ b/contracts/staking/RewardDistributor.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./interfaces/IRewardDistributor.sol"; +import "./interfaces/IRewardTracker.sol"; +import "../access/Governable.sol"; + +contract RewardDistributor is IRewardDistributor, ReentrancyGuard, Governable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + address public override rewardToken; + uint256 public override tokensPerInterval; + uint256 public lastDistributionTime; + address public rewardTracker; + + address public admin; + + event Distribute(uint256 amount); + event TokensPerIntervalChange(uint256 amount); + + modifier onlyAdmin() { + require(msg.sender == admin, "RewardDistributor: forbidden"); + _; + } + + constructor(address _rewardToken, address _rewardTracker) public { + rewardToken = _rewardToken; + rewardTracker = _rewardTracker; + admin = msg.sender; + } + + function setAdmin(address _admin) external onlyGov { + admin = _admin; + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { + IERC20(_token).safeTransfer(_account, _amount); + } + + function updateLastDistributionTime() external onlyAdmin { + lastDistributionTime = block.timestamp; + } + + function setTokensPerInterval(uint256 _amount) external onlyAdmin { + require(lastDistributionTime != 0, "RewardDistributor: invalid lastDistributionTime"); + IRewardTracker(rewardTracker).updateRewards(); + tokensPerInterval = _amount; + emit TokensPerIntervalChange(_amount); + } + + function pendingRewards() public view override returns (uint256) { + if (block.timestamp == lastDistributionTime) { + return 0; + } + + uint256 timeDiff = block.timestamp.sub(lastDistributionTime); + return tokensPerInterval.mul(timeDiff); + } + + function distribute() external override returns (uint256) { + require(msg.sender == rewardTracker, "RewardDistributor: invalid msg.sender"); + uint256 amount = pendingRewards(); + if (amount == 0) { return 0; } + + lastDistributionTime = block.timestamp; + + uint256 balance = IERC20(rewardToken).balanceOf(address(this)); + if (amount > balance) { amount = balance; } + + IERC20(rewardToken).safeTransfer(msg.sender, amount); + + emit Distribute(amount); + return amount; + } +} diff --git a/contracts/staking/RewardManager.sol b/contracts/staking/RewardManager.sol new file mode 100644 index 00000000..49feb960 --- /dev/null +++ b/contracts/staking/RewardManager.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../access/Governable.sol"; +import "./interfaces/IRewardTracker.sol"; + +contract RewardManager is Governable { + function batchClaimForAccounts( + IRewardTracker _rewardTracker, + address[] memory _accounts, + address _receiver + ) external onlyGov { + for (uint256 i = 0; i < _accounts.length; i++) { + _rewardTracker.claimForAccount(_accounts[i], _receiver); + } + } +} diff --git a/contracts/staking/RewardRouter.sol b/contracts/staking/RewardRouter.sol new file mode 100644 index 00000000..f23ae8c7 --- /dev/null +++ b/contracts/staking/RewardRouter.sol @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; +import "../libraries/utils/Address.sol"; + +import "./interfaces/IRewardTracker.sol"; +import "../tokens/interfaces/IMintable.sol"; +import "../tokens/interfaces/IWETH.sol"; +import "../core/interfaces/IGlpManager.sol"; +import "../access/Governable.sol"; + +contract RewardRouter is ReentrancyGuard, Governable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + using Address for address payable; + + bool public isInitialized; + + address public weth; + + address public gmx; + address public esGmx; + address public bnGmx; + + address public glp; // GMX Liquidity Provider token + + address public stakedGmxTracker; + address public bonusGmxTracker; + address public feeGmxTracker; + + address public stakedGlpTracker; + address public feeGlpTracker; + + address public glpManager; + + event StakeGmx(address account, uint256 amount); + event UnstakeGmx(address account, uint256 amount); + + event StakeGlp(address account, uint256 amount); + event UnstakeGlp(address account, uint256 amount); + + receive() external payable { + require(msg.sender == weth, "Router: invalid sender"); + } + + function initialize( + address _weth, + address _gmx, + address _esGmx, + address _bnGmx, + address _glp, + address _stakedGmxTracker, + address _bonusGmxTracker, + address _feeGmxTracker, + address _feeGlpTracker, + address _stakedGlpTracker, + address _glpManager + ) external onlyGov { + require(!isInitialized, "RewardRouter: already initialized"); + isInitialized = true; + + weth = _weth; + + gmx = _gmx; + esGmx = _esGmx; + bnGmx = _bnGmx; + + glp = _glp; + + stakedGmxTracker = _stakedGmxTracker; + bonusGmxTracker = _bonusGmxTracker; + feeGmxTracker = _feeGmxTracker; + + feeGlpTracker = _feeGlpTracker; + stakedGlpTracker = _stakedGlpTracker; + + glpManager = _glpManager; + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { + IERC20(_token).safeTransfer(_account, _amount); + } + + function batchStakeGmxForAccount(address[] memory _accounts, uint256[] memory _amounts) external nonReentrant onlyGov { + address _gmx = gmx; + for (uint256 i = 0; i < _accounts.length; i++) { + _stakeGmx(msg.sender, _accounts[i], _gmx, _amounts[i]); + } + } + + function stakeGmxForAccount(address _account, uint256 _amount) external nonReentrant onlyGov { + _stakeGmx(msg.sender, _account, gmx, _amount); + } + + function stakeGmx(uint256 _amount) external nonReentrant { + _stakeGmx(msg.sender, msg.sender, gmx, _amount); + } + + function stakeEsGmx(uint256 _amount) external nonReentrant { + _stakeGmx(msg.sender, msg.sender, esGmx, _amount); + } + + function unstakeGmx(uint256 _amount) external nonReentrant { + _unstakeGmx(msg.sender, gmx, _amount); + } + + function unstakeEsGmx(uint256 _amount) external nonReentrant { + _unstakeGmx(msg.sender, esGmx, _amount); + } + + function mintAndStakeGlp(address _token, uint256 _amount, uint256 _minUsdg, uint256 _minGlp) external nonReentrant returns (uint256) { + require(_amount > 0, "RewardRouter: invalid _amount"); + + address account = msg.sender; + uint256 glpAmount = IGlpManager(glpManager).addLiquidityForAccount(account, account, _token, _amount, _minUsdg, _minGlp); + IRewardTracker(feeGlpTracker).stakeForAccount(account, account, glp, glpAmount); + IRewardTracker(stakedGlpTracker).stakeForAccount(account, account, feeGlpTracker, glpAmount); + + emit StakeGlp(account, glpAmount); + + return glpAmount; + } + + function mintAndStakeGlpETH(uint256 _minUsdg, uint256 _minGlp) external payable nonReentrant returns (uint256) { + require(msg.value > 0, "RewardRouter: invalid msg.value"); + + IWETH(weth).deposit{value: msg.value}(); + IERC20(weth).approve(glpManager, msg.value); + + address account = msg.sender; + uint256 glpAmount = IGlpManager(glpManager).addLiquidityForAccount(address(this), account, weth, msg.value, _minUsdg, _minGlp); + + IRewardTracker(feeGlpTracker).stakeForAccount(account, account, glp, glpAmount); + IRewardTracker(stakedGlpTracker).stakeForAccount(account, account, feeGlpTracker, glpAmount); + + emit StakeGlp(account, glpAmount); + + return glpAmount; + } + + function unstakeAndRedeemGlp(address _tokenOut, uint256 _glpAmount, uint256 _minOut, address _receiver) external nonReentrant returns (uint256) { + require(_glpAmount > 0, "RewardRouter: invalid _glpAmount"); + + address account = msg.sender; + IRewardTracker(stakedGlpTracker).unstakeForAccount(account, feeGlpTracker, _glpAmount, account); + IRewardTracker(feeGlpTracker).unstakeForAccount(account, glp, _glpAmount, account); + uint256 amountOut = IGlpManager(glpManager).removeLiquidityForAccount(account, _tokenOut, _glpAmount, _minOut, _receiver); + + emit UnstakeGlp(account, _glpAmount); + + return amountOut; + } + + function unstakeAndRedeemGlpETH(uint256 _glpAmount, uint256 _minOut, address payable _receiver) external nonReentrant returns (uint256) { + require(_glpAmount > 0, "RewardRouter: invalid _glpAmount"); + + address account = msg.sender; + IRewardTracker(stakedGlpTracker).unstakeForAccount(account, feeGlpTracker, _glpAmount, account); + IRewardTracker(feeGlpTracker).unstakeForAccount(account, glp, _glpAmount, account); + uint256 amountOut = IGlpManager(glpManager).removeLiquidityForAccount(account, weth, _glpAmount, _minOut, address(this)); + + IWETH(weth).withdraw(amountOut); + + _receiver.sendValue(amountOut); + + emit UnstakeGlp(account, _glpAmount); + + return amountOut; + } + + function claim() external nonReentrant { + address account = msg.sender; + + IRewardTracker(feeGmxTracker).claimForAccount(account, account); + IRewardTracker(feeGlpTracker).claimForAccount(account, account); + + IRewardTracker(stakedGmxTracker).claimForAccount(account, account); + IRewardTracker(stakedGlpTracker).claimForAccount(account, account); + } + + function claimEsGmx() external nonReentrant { + address account = msg.sender; + + IRewardTracker(stakedGmxTracker).claimForAccount(account, account); + IRewardTracker(stakedGlpTracker).claimForAccount(account, account); + } + + function claimFees() external nonReentrant { + address account = msg.sender; + + IRewardTracker(feeGmxTracker).claimForAccount(account, account); + IRewardTracker(feeGlpTracker).claimForAccount(account, account); + } + + function compound() external nonReentrant { + _compound(msg.sender); + } + + function compoundForAccount(address _account) external nonReentrant onlyGov { + _compound(_account); + } + + function batchCompoundForAccounts(address[] memory _accounts) external nonReentrant onlyGov { + for (uint256 i = 0; i < _accounts.length; i++) { + _compound(_accounts[i]); + } + } + + function _compound(address _account) private { + _compoundGmx(_account); + _compoundGlp(_account); + } + + function _compoundGmx(address _account) private { + uint256 esGmxAmount = IRewardTracker(stakedGmxTracker).claimForAccount(_account, _account); + if (esGmxAmount > 0) { + _stakeGmx(_account, _account, esGmx, esGmxAmount); + } + + uint256 bnGmxAmount = IRewardTracker(bonusGmxTracker).claimForAccount(_account, _account); + if (bnGmxAmount > 0) { + IRewardTracker(feeGmxTracker).stakeForAccount(_account, _account, bnGmx, bnGmxAmount); + } + } + + function _compoundGlp(address _account) private { + uint256 esGmxAmount = IRewardTracker(stakedGlpTracker).claimForAccount(_account, _account); + if (esGmxAmount > 0) { + _stakeGmx(_account, _account, esGmx, esGmxAmount); + } + } + + function _stakeGmx(address _fundingAccount, address _account, address _token, uint256 _amount) private { + require(_amount > 0, "RewardRouter: invalid _amount"); + + IRewardTracker(stakedGmxTracker).stakeForAccount(_fundingAccount, _account, _token, _amount); + IRewardTracker(bonusGmxTracker).stakeForAccount(_account, _account, stakedGmxTracker, _amount); + IRewardTracker(feeGmxTracker).stakeForAccount(_account, _account, bonusGmxTracker, _amount); + + emit StakeGmx(_account, _amount); + } + + function _unstakeGmx(address _account, address _token, uint256 _amount) private { + require(_amount > 0, "RewardRouter: invalid _amount"); + + uint256 balance = IRewardTracker(stakedGmxTracker).stakedAmounts(_account); + + IRewardTracker(feeGmxTracker).unstakeForAccount(_account, bonusGmxTracker, _amount, _account); + IRewardTracker(bonusGmxTracker).unstakeForAccount(_account, stakedGmxTracker, _amount, _account); + IRewardTracker(stakedGmxTracker).unstakeForAccount(_account, _token, _amount, _account); + + uint256 bnGmxAmount = IRewardTracker(bonusGmxTracker).claimForAccount(_account, _account); + if (bnGmxAmount > 0) { + IRewardTracker(feeGmxTracker).stakeForAccount(_account, _account, bnGmx, bnGmxAmount); + } + + uint256 stakedBnGmx = IRewardTracker(feeGmxTracker).depositBalances(_account, bnGmx); + if (stakedBnGmx > 0) { + uint256 reductionAmount = stakedBnGmx.mul(_amount).div(balance); + IRewardTracker(feeGmxTracker).unstakeForAccount(_account, bnGmx, reductionAmount, _account); + IMintable(bnGmx).burn(_account, reductionAmount); + } + + emit UnstakeGmx(_account, _amount); + } +} diff --git a/contracts/staking/RewardTracker.sol b/contracts/staking/RewardTracker.sol new file mode 100644 index 00000000..be7b05a0 --- /dev/null +++ b/contracts/staking/RewardTracker.sol @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./interfaces/IRewardDistributor.sol"; +import "./interfaces/IRewardTracker.sol"; +import "../access/Governable.sol"; + +contract RewardTracker is IERC20, ReentrancyGuard, IRewardTracker, Governable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + uint256 public constant BASIS_POINTS_DIVISOR = 10000; + uint256 public constant PRECISION = 1e30; + + uint8 public constant decimals = 18; + + bool public isInitialized; + + string public name; + string public symbol; + + address public distributor; + mapping (address => bool) public isDepositToken; + mapping (address => mapping (address => uint256)) public override depositBalances; + mapping (address => uint256) public totalDepositSupply; + + uint256 public override totalSupply; + mapping (address => uint256) public balances; + mapping (address => mapping (address => uint256)) public allowances; + + uint256 public cumulativeRewardPerToken; + mapping (address => uint256) public override stakedAmounts; + mapping (address => uint256) public claimableReward; + mapping (address => uint256) public previousCumulatedRewardPerToken; + mapping (address => uint256) public override cumulativeRewards; + mapping (address => uint256) public override averageStakedAmounts; + + bool public inPrivateTransferMode; + bool public inPrivateStakingMode; + bool public inPrivateClaimingMode; + mapping (address => bool) public isHandler; + + event Claim(address receiver, uint256 amount); + + constructor(string memory _name, string memory _symbol) public { + name = _name; + symbol = _symbol; + } + + function initialize( + address[] memory _depositTokens, + address _distributor + ) external onlyGov { + require(!isInitialized, "RewardTracker: already initialized"); + isInitialized = true; + + for (uint256 i = 0; i < _depositTokens.length; i++) { + address depositToken = _depositTokens[i]; + isDepositToken[depositToken] = true; + } + + distributor = _distributor; + } + + function setDepositToken(address _depositToken, bool _isDepositToken) external onlyGov { + isDepositToken[_depositToken] = _isDepositToken; + } + + function setInPrivateTransferMode(bool _inPrivateTransferMode) external onlyGov { + inPrivateTransferMode = _inPrivateTransferMode; + } + + function setInPrivateStakingMode(bool _inPrivateStakingMode) external onlyGov { + inPrivateStakingMode = _inPrivateStakingMode; + } + + function setInPrivateClaimingMode(bool _inPrivateClaimingMode) external onlyGov { + inPrivateClaimingMode = _inPrivateClaimingMode; + } + + function setHandler(address _handler, bool _isActive) external onlyGov { + isHandler[_handler] = _isActive; + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { + IERC20(_token).safeTransfer(_account, _amount); + } + + function balanceOf(address _account) external view override returns (uint256) { + return balances[_account]; + } + + function stake(address _depositToken, uint256 _amount) external override nonReentrant { + if (inPrivateStakingMode) { revert("RewardTracker: action not enabled"); } + _stake(msg.sender, msg.sender, _depositToken, _amount); + } + + function stakeForAccount(address _fundingAccount, address _account, address _depositToken, uint256 _amount) external override nonReentrant { + _validateHandler(); + _stake(_fundingAccount, _account, _depositToken, _amount); + } + + function unstake(address _depositToken, uint256 _amount) external override nonReentrant { + if (inPrivateStakingMode) { revert("RewardTracker: action not enabled"); } + _unstake(msg.sender, _depositToken, _amount, msg.sender); + } + + function unstakeForAccount(address _account, address _depositToken, uint256 _amount, address _receiver) external override nonReentrant { + _validateHandler(); + _unstake(_account, _depositToken, _amount, _receiver); + } + + function transfer(address _recipient, uint256 _amount) external override returns (bool) { + _transfer(msg.sender, _recipient, _amount); + return true; + } + + function allowance(address _owner, address _spender) external view override returns (uint256) { + return allowances[_owner][_spender]; + } + + function approve(address _spender, uint256 _amount) external override returns (bool) { + _approve(msg.sender, _spender, _amount); + return true; + } + + function transferFrom(address _sender, address _recipient, uint256 _amount) external override returns (bool) { + if (isHandler[msg.sender]) { + _transfer(_sender, _recipient, _amount); + return true; + } + + uint256 nextAllowance = allowances[_sender][msg.sender].sub(_amount, "RewardTracker: transfer amount exceeds allowance"); + _approve(_sender, msg.sender, nextAllowance); + _transfer(_sender, _recipient, _amount); + return true; + } + + function tokensPerInterval() external override view returns (uint256) { + return IRewardDistributor(distributor).tokensPerInterval(); + } + + function updateRewards() external override nonReentrant { + _updateRewards(address(0)); + } + + function claim(address _receiver) external override nonReentrant returns (uint256) { + if (inPrivateClaimingMode) { revert("RewardTracker: action not enabled"); } + return _claim(msg.sender, _receiver); + } + + function claimForAccount(address _account, address _receiver) external override nonReentrant returns (uint256) { + _validateHandler(); + return _claim(_account, _receiver); + } + + function claimable(address _account) public override view returns (uint256) { + uint256 stakedAmount = stakedAmounts[_account]; + if (stakedAmount == 0) { + return claimableReward[_account]; + } + uint256 supply = totalSupply; + uint256 pendingRewards = IRewardDistributor(distributor).pendingRewards().mul(PRECISION); + uint256 nextCumulativeRewardPerToken = cumulativeRewardPerToken.add(pendingRewards.div(supply)); + return claimableReward[_account].add( + stakedAmount.mul(nextCumulativeRewardPerToken.sub(previousCumulatedRewardPerToken[_account])).div(PRECISION)); + } + + function rewardToken() public view returns (address) { + return IRewardDistributor(distributor).rewardToken(); + } + + function _claim(address _account, address _receiver) private returns (uint256) { + _updateRewards(_account); + + uint256 tokenAmount = claimableReward[_account]; + claimableReward[_account] = 0; + + if (tokenAmount > 0) { + IERC20(rewardToken()).safeTransfer(_receiver, tokenAmount); + emit Claim(_account, tokenAmount); + } + + return tokenAmount; + } + + function _mint(address _account, uint256 _amount) internal { + require(_account != address(0), "RewardTracker: mint to the zero address"); + + totalSupply = totalSupply.add(_amount); + balances[_account] = balances[_account].add(_amount); + + emit Transfer(address(0), _account, _amount); + } + + function _burn(address _account, uint256 _amount) internal { + require(_account != address(0), "RewardTracker: burn from the zero address"); + + balances[_account] = balances[_account].sub(_amount, "RewardTracker: burn amount exceeds balance"); + totalSupply = totalSupply.sub(_amount); + + emit Transfer(_account, address(0), _amount); + } + + function _transfer(address _sender, address _recipient, uint256 _amount) private { + require(_sender != address(0), "RewardTracker: transfer from the zero address"); + require(_recipient != address(0), "RewardTracker: transfer to the zero address"); + + if (inPrivateTransferMode) { _validateHandler(); } + + balances[_sender] = balances[_sender].sub(_amount, "RewardTracker: transfer amount exceeds balance"); + balances[_recipient] = balances[_recipient].add(_amount); + + emit Transfer(_sender, _recipient,_amount); + } + + function _approve(address _owner, address _spender, uint256 _amount) private { + require(_owner != address(0), "RewardTracker: approve from the zero address"); + require(_spender != address(0), "RewardTracker: approve to the zero address"); + + allowances[_owner][_spender] = _amount; + + emit Approval(_owner, _spender, _amount); + } + + function _validateHandler() private view { + require(isHandler[msg.sender], "RewardTracker: forbidden"); + } + + function _stake(address _fundingAccount, address _account, address _depositToken, uint256 _amount) private { + require(_amount > 0, "RewardTracker: invalid _amount"); + require(isDepositToken[_depositToken], "RewardTracker: invalid _depositToken"); + + IERC20(_depositToken).safeTransferFrom(_fundingAccount, address(this), _amount); + + _updateRewards(_account); + + stakedAmounts[_account] = stakedAmounts[_account].add(_amount); + depositBalances[_account][_depositToken] = depositBalances[_account][_depositToken].add(_amount); + totalDepositSupply[_depositToken] = totalDepositSupply[_depositToken].add(_amount); + + _mint(_account, _amount); + } + + function _unstake(address _account, address _depositToken, uint256 _amount, address _receiver) private { + require(_amount > 0, "RewardTracker: invalid _amount"); + require(isDepositToken[_depositToken], "RewardTracker: invalid _depositToken"); + + _updateRewards(_account); + + uint256 stakedAmount = stakedAmounts[_account]; + require(stakedAmounts[_account] >= _amount, "RewardTracker: _amount exceeds stakedAmount"); + + stakedAmounts[_account] = stakedAmount.sub(_amount); + + uint256 depositBalance = depositBalances[_account][_depositToken]; + require(depositBalance >= _amount, "RewardTracker: _amount exceeds depositBalance"); + depositBalances[_account][_depositToken] = depositBalance.sub(_amount); + totalDepositSupply[_depositToken] = totalDepositSupply[_depositToken].sub(_amount); + + _burn(_account, _amount); + IERC20(_depositToken).safeTransfer(_receiver, _amount); + } + + function _updateRewards(address _account) private { + uint256 blockReward = IRewardDistributor(distributor).distribute(); + + uint256 supply = totalSupply; + uint256 _cumulativeRewardPerToken = cumulativeRewardPerToken; + if (supply > 0 && blockReward > 0) { + _cumulativeRewardPerToken = _cumulativeRewardPerToken.add(blockReward.mul(PRECISION).div(supply)); + cumulativeRewardPerToken = _cumulativeRewardPerToken; + } + + // cumulativeRewardPerToken can only increase + // so if cumulativeRewardPerToken is zero, it means there are no rewards yet + if (_cumulativeRewardPerToken == 0) { + return; + } + + if (_account != address(0)) { + uint256 stakedAmount = stakedAmounts[_account]; + uint256 accountReward = stakedAmount.mul(_cumulativeRewardPerToken.sub(previousCumulatedRewardPerToken[_account])).div(PRECISION); + uint256 _claimableReward = claimableReward[_account].add(accountReward); + + claimableReward[_account] = _claimableReward; + previousCumulatedRewardPerToken[_account] = _cumulativeRewardPerToken; + + if (_claimableReward > 0 && stakedAmounts[_account] > 0) { + uint256 nextCumulativeReward = cumulativeRewards[_account].add(accountReward); + + averageStakedAmounts[_account] = averageStakedAmounts[_account].mul(cumulativeRewards[_account]).div(nextCumulativeReward) + .add(stakedAmount.mul(accountReward).div(nextCumulativeReward)); + + cumulativeRewards[_account] = nextCumulativeReward; + } + } + } +} diff --git a/contracts/staking/Vester.sol b/contracts/staking/Vester.sol new file mode 100644 index 00000000..e0123255 --- /dev/null +++ b/contracts/staking/Vester.sol @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./interfaces/IRewardTracker.sol"; +import "../access/Governable.sol"; + +contract Vester is IERC20, ReentrancyGuard, Governable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + string public name; + string public symbol; + uint8 public decimals = 18; + + uint256 public vestingDuration; + + address public esToken; + address public pairToken; + address public claimableToken; + + address public rewardTracker; + + uint256 public override totalSupply; + uint256 public pairSupply; + + bool public hasMaxVestableAmount; + + mapping (address => uint256) public balances; + mapping (address => uint256) public pairAmounts; + mapping (address => uint256) public cumulativeClaimAmounts; + mapping (address => uint256) public claimedAmounts; + mapping (address => uint256) public lastVestingTimes; + + mapping (address => bool) public isHandler; + + event Claim(address receiver, uint256 amount); + event Deposit(address account, uint256 amount); + event Withdraw(address account, uint256 claimedAmount, uint256 balance); + event PairTransfer(address indexed from, address indexed to, uint256 value); + + constructor ( + string memory _name, + string memory _symbol, + uint256 _vestingDuration, + address _esToken, + address _pairToken, + address _claimableToken, + address _rewardTracker + ) public { + name = _name; + symbol = _symbol; + + vestingDuration = _vestingDuration; + + esToken = _esToken; + pairToken = _pairToken; + claimableToken = _claimableToken; + + rewardTracker = _rewardTracker; + + if (rewardTracker != address(0)) { + hasMaxVestableAmount = true; + } + } + + function setHandler(address _handler, bool _isActive) external onlyGov { + isHandler[_handler] = _isActive; + } + + function setHasMaxVestableAmount(bool _hasMaxVestableAmount) external onlyGov { + hasMaxVestableAmount = _hasMaxVestableAmount; + } + + function deposit(uint256 _amount) external nonReentrant { + _deposit(msg.sender, _amount); + } + + function depositForAccount(address _account, uint256 _amount) external nonReentrant { + _validateHandler(); + _deposit(_account, _amount); + } + + function claim(address _receiver) external nonReentrant returns (uint256) { + return _claim(msg.sender, _receiver); + } + + function claimForAccount(address _account, address _receiver) external nonReentrant returns (uint256) { + _validateHandler(); + return _claim(_account, _receiver); + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { + IERC20(_token).safeTransfer(_account, _amount); + } + + function withdraw(address _receiver) external nonReentrant { + address account = msg.sender; + _claim(account, _receiver); + + uint256 claimedAmount = cumulativeClaimAmounts[account]; + uint256 balance = balances[account]; + uint256 totalVested = balance.add(claimedAmount); + require(totalVested > 0, "Vester: vested amount is zero"); + + if (hasPairToken()) { + uint256 pairAmount = pairAmounts[account]; + _burnPair(account, pairAmount); + IERC20(pairToken).safeTransfer(_receiver, pairAmount); + } + + IERC20(esToken).safeTransfer(_receiver, balance); + _burn(account, balance, totalVested); + + delete cumulativeClaimAmounts[account]; + delete claimedAmounts[account]; + delete lastVestingTimes[account]; + + emit Withdraw(account, claimedAmount, balance); + } + + function claimable(address _account) public view returns (uint256) { + uint256 amount = cumulativeClaimAmounts[_account].sub(claimedAmounts[_account]); + uint256 nextClaimable = _getNextClaimableAmount(_account); + return amount.add(nextClaimable); + } + + function getMaxVestableAmount(address _account) public view returns (uint256) { + if (!hasRewardTracker()) { return 0; } + return IRewardTracker(rewardTracker).cumulativeRewards(_account); + } + + function getPairAmount(address _account, uint256 _esAmount) public view returns (uint256) { + if (!hasRewardTracker()) { return 0; } + + uint256 averageStakedAmount = IRewardTracker(rewardTracker).averageStakedAmounts(_account); + uint256 cumulativeReward = IRewardTracker(rewardTracker).cumulativeRewards(_account); + if (cumulativeReward == 0) { return 0; } + + return _esAmount.mul(averageStakedAmount).div(cumulativeReward); + } + + function hasRewardTracker() public view returns (bool) { + return rewardTracker != address(0); + } + + function hasPairToken() public view returns (bool) { + return pairToken != address(0); + } + + function getTotalVested(address _account) public view returns (uint256) { + return balances[_account].add(cumulativeClaimAmounts[_account]); + } + + function balanceOf(address _account) public view override returns (uint256) { + return balances[_account]; + } + + // empty implementation, tokens are non-transferrable + function transfer(address /* recipient */, uint256 /* amount */) public override returns (bool) { + revert("Vester: non-transferrable"); + } + + // empty implementation, tokens are non-transferrable + function allowance(address /* owner */, address /* spender */) public view virtual override returns (uint256) { + return 0; + } + + // empty implementation, tokens are non-transferrable + function approve(address /* spender */, uint256 /* amount */) public virtual override returns (bool) { + revert("Vester: non-transferrable"); + } + + // empty implementation, tokens are non-transferrable + function transferFrom(address /* sender */, address /* recipient */, uint256 /* amount */) public virtual override returns (bool) { + revert("Vester: non-transferrable"); + } + + function _mint(address _account, uint256 _amount) private { + require(_account != address(0), "Vester: mint to the zero address"); + + totalSupply = totalSupply.add(_amount); + balances[_account] = balances[_account].add(_amount); + + emit Transfer(address(0), _account, _amount); + } + + function _mintPair(address _account, uint256 _amount) private { + require(_account != address(0), "Vester: mint to the zero address"); + + pairSupply = pairSupply.add(_amount); + pairAmounts[_account] = pairAmounts[_account].add(_amount); + + emit PairTransfer(address(0), _account, _amount); + } + + function _burn(address _account, uint256 _amount, uint256 burnAmountForEvent) private { + require(_account != address(0), "Vester: burn from the zero address"); + + balances[_account] = balances[_account].sub(_amount, "Vester: burn amount exceeds balance"); + totalSupply = totalSupply.sub(_amount); + + emit Transfer(_account, address(0), burnAmountForEvent); + } + + function _burnPair(address _account, uint256 _amount) private { + require(_account != address(0), "Vester: burn from the zero address"); + + pairAmounts[_account] = pairAmounts[_account].sub(_amount, "Vester: burn amount exceeds balance"); + pairSupply = pairSupply.sub(_amount); + + emit PairTransfer(_account, address(0), _amount); + } + + function _deposit(address _account, uint256 _amount) private { + require(_amount > 0, "Vester: invalid _amount"); + + _updateVesting(_account); + + IERC20(esToken).safeTransferFrom(_account, address(this), _amount); + + if (hasPairToken()) { + uint256 pairAmount = pairAmounts[_account]; + uint256 nextPairAmount = getPairAmount(_account, _amount); + if (nextPairAmount > pairAmount) { + uint256 pairAmountDiff = nextPairAmount.sub(pairAmount); + IERC20(pairToken).safeTransferFrom(_account, address(this), pairAmountDiff); + _mintPair(_account, pairAmountDiff); + } + } + + _mint(_account, _amount); + + if (hasMaxVestableAmount) { + uint256 maxAmount = getMaxVestableAmount(_account); + require(getTotalVested(_account) <= maxAmount, "Vester: max vestable amount exceeded"); + } + + emit Deposit(_account, _amount); + } + + function _updateVesting(address _account) private { + uint256 amount = _getNextClaimableAmount(_account); + lastVestingTimes[_account] = block.timestamp; + + if (amount == 0) { + return; + } + + // transfer claimableAmount from balances to cumulativeClaimAmounts + balances[_account] = balances[_account].sub(amount); + cumulativeClaimAmounts[_account] = cumulativeClaimAmounts[_account].add(amount); + } + + function _getNextClaimableAmount(address _account) private view returns (uint256) { + uint256 timeDiff = block.timestamp.sub(lastVestingTimes[_account]); + + uint256 balance = balances[_account]; + if (balance == 0) { return 0; } + + uint256 cumulativeClaimAmount = cumulativeClaimAmounts[_account]; + + uint256 totalVested = balance.add(cumulativeClaimAmount); + uint256 claimableAmount = totalVested.mul(timeDiff).div(vestingDuration); + + if (claimableAmount < balance) { + return claimableAmount; + } + + return balance; + } + + function _claim(address _account, address _receiver) private returns (uint256) { + _updateVesting(_account); + uint256 amount = claimable(_account); + claimedAmounts[_account] = claimedAmounts[_account].add(amount); + IERC20(claimableToken).safeTransfer(_receiver, amount); + emit Claim(_account, amount); + } + + function _validateHandler() private view { + require(isHandler[msg.sender], "Vester: forbidden"); + } +} diff --git a/contracts/staking/interfaces/IRewardDistributor.sol b/contracts/staking/interfaces/IRewardDistributor.sol new file mode 100644 index 00000000..fa0a5d77 --- /dev/null +++ b/contracts/staking/interfaces/IRewardDistributor.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IRewardDistributor { + function rewardToken() external view returns (address); + function tokensPerInterval() external view returns (uint256); + function pendingRewards() external view returns (uint256); + function distribute() external returns (uint256); +} diff --git a/contracts/staking/interfaces/IRewardTracker.sol b/contracts/staking/interfaces/IRewardTracker.sol new file mode 100644 index 00000000..69699639 --- /dev/null +++ b/contracts/staking/interfaces/IRewardTracker.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IRewardTracker { + function depositBalances(address _account, address _depositToken) external view returns (uint256); + function stakedAmounts(address _account) external view returns (uint256); + function updateRewards() external; + function stake(address _depositToken, uint256 _amount) external; + function stakeForAccount(address _fundingAccount, address _account, address _depositToken, uint256 _amount) external; + function unstake(address _depositToken, uint256 _amount) external; + function unstakeForAccount(address _account, address _depositToken, uint256 _amount, address _receiver) external; + function tokensPerInterval() external view returns (uint256); + function claim(address _receiver) external returns (uint256); + function claimForAccount(address _account, address _receiver) external returns (uint256); + function claimable(address _account) external view returns (uint256); + function averageStakedAmounts(address _account) external view returns (uint256); + function cumulativeRewards(address _account) external view returns (uint256); +} diff --git a/contracts/tokens/BaseToken.sol b/contracts/tokens/BaseToken.sol new file mode 100644 index 00000000..bdfd4e5c --- /dev/null +++ b/contracts/tokens/BaseToken.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; + +import "./interfaces/IYieldTracker.sol"; +import "./interfaces/IBaseToken.sol"; + +contract BaseToken is IERC20, IBaseToken { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + string public name; + string public symbol; + uint8 public constant decimals = 18; + + uint256 public override totalSupply; + uint256 public nonStakingSupply; + + address public gov; + + mapping (address => uint256) public balances; + mapping (address => mapping (address => uint256)) public allowances; + + address[] public yieldTrackers; + mapping (address => bool) public nonStakingAccounts; + mapping (address => bool) public admins; + + bool public inPrivateTransferMode; + mapping (address => bool) public isHandler; + + modifier onlyGov() { + require(msg.sender == gov, "BaseToken: forbidden"); + _; + } + + modifier onlyAdmin() { + require(admins[msg.sender], "BaseToken: forbidden"); + _; + } + + constructor(string memory _name, string memory _symbol, uint256 _initialSupply) public { + name = _name; + symbol = _symbol; + gov = msg.sender; + _mint(msg.sender, _initialSupply); + } + + function setGov(address _gov) external onlyGov { + gov = _gov; + } + + function setInfo(string memory _name, string memory _symbol) external onlyGov { + name = _name; + symbol = _symbol; + } + + function setYieldTrackers(address[] memory _yieldTrackers) external onlyGov { + yieldTrackers = _yieldTrackers; + } + + function addAdmin(address _account) external onlyGov { + admins[_account] = true; + } + + function removeAdmin(address _account) external override onlyGov { + admins[_account] = false; + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { + IERC20(_token).safeTransfer(_account, _amount); + } + + function setInPrivateTransferMode(bool _inPrivateTransferMode) external override onlyGov { + inPrivateTransferMode = _inPrivateTransferMode; + } + + function setHandler(address _handler, bool _isActive) external onlyGov { + isHandler[_handler] = _isActive; + } + + function addNonStakingAccount(address _account) external onlyAdmin { + require(!nonStakingAccounts[_account], "BaseToken: _account already marked"); + _updateRewards(_account); + nonStakingAccounts[_account] = true; + nonStakingSupply = nonStakingSupply.add(balances[_account]); + } + + function removeNonStakingAccount(address _account) external onlyAdmin { + require(nonStakingAccounts[_account], "BaseToken: _account not marked"); + _updateRewards(_account); + nonStakingAccounts[_account] = false; + nonStakingSupply = nonStakingSupply.sub(balances[_account]); + } + + function recoverClaim(address _account, address _receiver) external onlyAdmin { + for (uint256 i = 0; i < yieldTrackers.length; i++) { + address yieldTracker = yieldTrackers[i]; + IYieldTracker(yieldTracker).claim(_account, _receiver); + } + } + + function claim(address _receiver) external { + for (uint256 i = 0; i < yieldTrackers.length; i++) { + address yieldTracker = yieldTrackers[i]; + IYieldTracker(yieldTracker).claim(msg.sender, _receiver); + } + } + + function totalStaked() external view override returns (uint256) { + return totalSupply.sub(nonStakingSupply); + } + + function balanceOf(address _account) external view override returns (uint256) { + return balances[_account]; + } + + function stakedBalance(address _account) external view override returns (uint256) { + if (nonStakingAccounts[_account]) { + return 0; + } + return balances[_account]; + } + + function transfer(address _recipient, uint256 _amount) external override returns (bool) { + _transfer(msg.sender, _recipient, _amount); + return true; + } + + function allowance(address _owner, address _spender) external view override returns (uint256) { + return allowances[_owner][_spender]; + } + + function approve(address _spender, uint256 _amount) external override returns (bool) { + _approve(msg.sender, _spender, _amount); + return true; + } + + function transferFrom(address _sender, address _recipient, uint256 _amount) external override returns (bool) { + if (isHandler[msg.sender]) { + _transfer(_sender, _recipient, _amount); + return true; + } + uint256 nextAllowance = allowances[_sender][msg.sender].sub(_amount, "BaseToken: transfer amount exceeds allowance"); + _approve(_sender, msg.sender, nextAllowance); + _transfer(_sender, _recipient, _amount); + return true; + } + + function _mint(address _account, uint256 _amount) internal { + require(_account != address(0), "BaseToken: mint to the zero address"); + + _updateRewards(_account); + + totalSupply = totalSupply.add(_amount); + balances[_account] = balances[_account].add(_amount); + + if (nonStakingAccounts[_account]) { + nonStakingSupply = nonStakingSupply.add(_amount); + } + + emit Transfer(address(0), _account, _amount); + } + + function _burn(address _account, uint256 _amount) internal { + require(_account != address(0), "BaseToken: burn from the zero address"); + + _updateRewards(_account); + + balances[_account] = balances[_account].sub(_amount, "BaseToken: burn amount exceeds balance"); + totalSupply = totalSupply.sub(_amount); + + if (nonStakingAccounts[_account]) { + nonStakingSupply = nonStakingSupply.sub(_amount); + } + + emit Transfer(_account, address(0), _amount); + } + + function _transfer(address _sender, address _recipient, uint256 _amount) private { + require(_sender != address(0), "BaseToken: transfer from the zero address"); + require(_recipient != address(0), "BaseToken: transfer to the zero address"); + + if (inPrivateTransferMode) { + require(isHandler[msg.sender], "BaseToken: msg.sender not whitelisted"); + } + + _updateRewards(_sender); + _updateRewards(_recipient); + + balances[_sender] = balances[_sender].sub(_amount, "BaseToken: transfer amount exceeds balance"); + balances[_recipient] = balances[_recipient].add(_amount); + + if (nonStakingAccounts[_sender]) { + nonStakingSupply = nonStakingSupply.sub(_amount); + } + if (nonStakingAccounts[_recipient]) { + nonStakingSupply = nonStakingSupply.add(_amount); + } + + emit Transfer(_sender, _recipient,_amount); + } + + function _approve(address _owner, address _spender, uint256 _amount) private { + require(_owner != address(0), "BaseToken: approve from the zero address"); + require(_spender != address(0), "BaseToken: approve to the zero address"); + + allowances[_owner][_spender] = _amount; + + emit Approval(_owner, _spender, _amount); + } + + function _updateRewards(address _account) private { + for (uint256 i = 0; i < yieldTrackers.length; i++) { + address yieldTracker = yieldTrackers[i]; + IYieldTracker(yieldTracker).updateRewards(_account); + } + } +} diff --git a/contracts/tokens/Bridge.sol b/contracts/tokens/Bridge.sol new file mode 100644 index 00000000..5d230c9f --- /dev/null +++ b/contracts/tokens/Bridge.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "../access/Governable.sol"; + +contract Bridge is ReentrancyGuard, Governable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + address public token; + address public wToken; + + constructor(address _token, address _wToken) public { + token = _token; + wToken = _wToken; + } + + function wrap(uint256 _amount, address _receiver) external nonReentrant { + IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); + IERC20(wToken).safeTransfer(_receiver, _amount); + } + + function unwrap(uint256 _amount, address _receiver) external nonReentrant { + IERC20(wToken).safeTransferFrom(msg.sender, address(this), _amount); + IERC20(token).safeTransfer(_receiver, _amount); + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { + IERC20(_token).safeTransfer(_account, _amount); + } +} diff --git a/contracts/tokens/FaucetToken.sol b/contracts/tokens/FaucetToken.sol new file mode 100644 index 00000000..adfbcaa7 --- /dev/null +++ b/contracts/tokens/FaucetToken.sol @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/math/SafeMath.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract FaucetToken is IERC20 { + using SafeMath for uint256; + + uint256 public DROPLET_INTERVAL = 8 hours; + + address public _gov; + uint256 public _dropletAmount; + bool public _isFaucetEnabled; + + mapping (address => uint256) public _claimedAt; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + mapping (address => uint256) private _balances; + mapping (address => mapping (address => uint256)) private _allowances; + + /** + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during + * construction. + */ + constructor( + string memory name, + string memory symbol, + uint8 decimals, + uint256 dropletAmount + ) public { + _name = name; + _symbol = symbol; + _decimals = decimals; + _gov = msg.sender; + _dropletAmount = dropletAmount; + } + + function mint(address account, uint256 amount) public { + require(msg.sender == _gov, "FaucetToken: forbidden"); + _mint(account, amount); + } + + function enableFaucet() public { + require(msg.sender == _gov, "FaucetToken: forbidden"); + _isFaucetEnabled = true; + } + + function disableFaucet() public { + require(msg.sender == _gov, "FaucetToken: forbidden"); + _isFaucetEnabled = false; + } + + function setDropletAmount(uint256 dropletAmount) public { + require(msg.sender == _gov, "FaucetToken: forbidden"); + _dropletAmount = dropletAmount; + } + + function claimDroplet() public { + require(_isFaucetEnabled, "FaucetToken: faucet not enabled"); + require(_claimedAt[msg.sender].add(DROPLET_INTERVAL) <= block.timestamp, "FaucetToken: droplet not available yet"); + _claimedAt[msg.sender] = block.timestamp; + _mint(msg.sender, _dropletAmount); + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is + * called. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Sets {decimals} to a value other than the default one of 18. + * + * WARNING: This function should only be called from the constructor. Most + * applications that interact with token contracts will not expect + * {decimals} to ever change, and may work incorrectly if it does. + */ + function _setupDecimals(uint8 decimals_) internal { + _decimals = decimals_; + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } +} diff --git a/contracts/tokens/MintableBaseToken.sol b/contracts/tokens/MintableBaseToken.sol new file mode 100644 index 00000000..dda2dc4e --- /dev/null +++ b/contracts/tokens/MintableBaseToken.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "./BaseToken.sol"; +import "./interfaces/IMintable.sol"; + +contract MintableBaseToken is BaseToken, IMintable { + + mapping (address => bool) public override isMinter; + + constructor(string memory _name, string memory _symbol, uint256 _initialSupply) public BaseToken(_name, _symbol, _initialSupply) { + } + + modifier onlyMinter() { + require(isMinter[msg.sender], "MintableBaseToken: forbidden"); + _; + } + + function setMinter(address _minter, bool _isActive) external override onlyGov { + isMinter[_minter] = _isActive; + } + + function mint(address _account, uint256 _amount) external override onlyMinter { + _mint(_account, _amount); + } + + function burn(address _account, uint256 _amount) external override onlyMinter { + _burn(_account, _amount); + } +} diff --git a/contracts/tokens/TimeDistributor.sol b/contracts/tokens/TimeDistributor.sol new file mode 100644 index 00000000..13f9a2f5 --- /dev/null +++ b/contracts/tokens/TimeDistributor.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; + +import "./interfaces/IDistributor.sol"; + +contract TimeDistributor is IDistributor { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + uint256 public constant DISTRIBUTION_INTERVAL = 1 hours; + address public gov; + address public admin; + + mapping (address => address) public rewardTokens; + mapping (address => uint256) public override tokensPerInterval; + mapping (address => uint256) public lastDistributionTime; + + event Distribute(address receiver, uint256 amount); + event DistributionChange(address receiver, uint256 amount, address rewardToken); + event TokensPerIntervalChange(address receiver, uint256 amount); + + modifier onlyGov() { + require(msg.sender == gov, "TimeDistributor: forbidden"); + _; + } + + modifier onlyAdmin() { + require(msg.sender == admin, "TimeDistributor: forbidden"); + _; + } + + constructor() public { + gov = msg.sender; + admin = msg.sender; + } + + function setGov(address _gov) external onlyGov { + gov = _gov; + } + + function setTokensPerInterval(address _receiver, uint256 _amount) external onlyAdmin { + if (lastDistributionTime[_receiver] != 0) { + uint256 intervals = getIntervals(_receiver); + require(intervals == 0, "TimeDistributor: pending distribution"); + } + + tokensPerInterval[_receiver] = _amount; + _updateLastDistributionTime(_receiver); + emit TokensPerIntervalChange(_receiver, _amount); + } + + function updateLastDistributionTime(address _receiver) external onlyAdmin { + _updateLastDistributionTime(_receiver); + } + + function setDistribution( + address[] calldata _receivers, + uint256[] calldata _amounts, + address[] calldata _rewardTokens + ) external onlyGov { + for (uint256 i = 0; i < _receivers.length; i++) { + address receiver = _receivers[i]; + + if (lastDistributionTime[receiver] != 0) { + uint256 intervals = getIntervals(receiver); + require(intervals == 0, "TimeDistributor: pending distribution"); + } + + uint256 amount = _amounts[i]; + address rewardToken = _rewardTokens[i]; + tokensPerInterval[receiver] = amount; + rewardTokens[receiver] = rewardToken; + _updateLastDistributionTime(receiver); + emit DistributionChange(receiver, amount, rewardToken); + } + } + + function distribute() external override returns (uint256) { + address receiver = msg.sender; + uint256 intervals = getIntervals(receiver); + + if (intervals == 0) { return 0; } + + uint256 amount = getDistributionAmount(receiver); + _updateLastDistributionTime(receiver); + + if (amount == 0) { return 0; } + + IERC20(rewardTokens[receiver]).safeTransfer(receiver, amount); + + emit Distribute(receiver, amount); + return amount; + } + + function getRewardToken(address _receiver) external override view returns (address) { + return rewardTokens[_receiver]; + } + + function getDistributionAmount(address _receiver) public override view returns (uint256) { + uint256 _tokensPerInterval = tokensPerInterval[_receiver]; + if (_tokensPerInterval == 0) { return 0; } + + uint256 intervals = getIntervals(_receiver); + uint256 amount = _tokensPerInterval.mul(intervals); + + if (IERC20(rewardTokens[_receiver]).balanceOf(address(this)) < amount) { return 0; } + + return amount; + } + + function getIntervals(address _receiver) public view returns (uint256) { + uint256 timeDiff = block.timestamp.sub(lastDistributionTime[_receiver]); + return timeDiff.div(DISTRIBUTION_INTERVAL); + } + + function _updateLastDistributionTime(address _receiver) private { + lastDistributionTime[_receiver] = block.timestamp.div(DISTRIBUTION_INTERVAL).mul(DISTRIBUTION_INTERVAL); + } +} diff --git a/contracts/tokens/Token.sol b/contracts/tokens/Token.sol new file mode 100644 index 00000000..623d37d6 --- /dev/null +++ b/contracts/tokens/Token.sol @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/math/SafeMath.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract Token is IERC20 { + using SafeMath for uint256; + + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + /** + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during + * construction. + */ + constructor() public { + _name = "Token"; + _symbol = "TOKEN"; + _decimals = 18; + } + + function mint(address account, uint256 amount) public { + _mint(account, amount); + } + + function withdrawToken(address token, address account, uint256 amount) public { + IERC20(token).transfer(account, amount); + } + + function deposit() public payable { + _mint(msg.sender, msg.value); + } + + function withdraw(uint256 amount) public { + require(_balances[msg.sender] >= amount); + _burn(msg.sender, amount); + msg.sender.transfer(amount); + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is + * called. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Sets {decimals} to a value other than the default one of 18. + * + * WARNING: This function should only be called from the constructor. Most + * applications that interact with token contracts will not expect + * {decimals} to ever change, and may work incorrectly if it does. + */ + function _setupDecimals(uint8 decimals_) internal { + _decimals = decimals_; + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } +} diff --git a/contracts/tokens/USDG.sol b/contracts/tokens/USDG.sol new file mode 100644 index 00000000..a478ad7b --- /dev/null +++ b/contracts/tokens/USDG.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "./interfaces/IUSDG.sol"; +import "./YieldToken.sol"; + +contract USDG is YieldToken, IUSDG { + + mapping (address => bool) public vaults; + + modifier onlyVault() { + require(vaults[msg.sender], "USDG: forbidden"); + _; + } + + constructor(address _vault) public YieldToken("USD Gambit", "USDG", 0) { + vaults[_vault] = true; + } + + function addVault(address _vault) external onlyGov { + vaults[_vault] = true; + } + + function removeVault(address _vault) external onlyGov { + vaults[_vault] = false; + } + + function mint(address _account, uint256 _amount) external override onlyVault { + _mint(_account, _amount); + } + + function burn(address _account, uint256 _amount) external override onlyVault { + _burn(_account, _amount); + } +} diff --git a/contracts/tokens/WETH.sol b/contracts/tokens/WETH.sol new file mode 100644 index 00000000..94293415 --- /dev/null +++ b/contracts/tokens/WETH.sol @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/math/SafeMath.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract WETH is IERC20 { + using SafeMath for uint256; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + mapping (address => uint256) private _balances; + mapping (address => mapping (address => uint256)) private _allowances; + + /** + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during + * construction. + */ + constructor( + string memory name, + string memory symbol, + uint8 decimals + ) public { + _name = name; + _symbol = symbol; + _decimals = decimals; + } + + function deposit() public payable { + _balances[msg.sender] = _balances[msg.sender].add(msg.value); + } + + function withdraw(uint256 amount) public { + require(_balances[msg.sender] >= amount); + _balances[msg.sender] = _balances[msg.sender].sub(amount); + msg.sender.transfer(amount); + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is + * called. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Sets {decimals} to a value other than the default one of 18. + * + * WARNING: This function should only be called from the constructor. Most + * applications that interact with token contracts will not expect + * {decimals} to ever change, and may work incorrectly if it does. + */ + function _setupDecimals(uint8 decimals_) internal { + _decimals = decimals_; + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } + + function _msgSender() internal view virtual returns (address payable) { + return msg.sender; + } +} diff --git a/contracts/tokens/YieldFarm.sol b/contracts/tokens/YieldFarm.sol new file mode 100644 index 00000000..a0893d39 --- /dev/null +++ b/contracts/tokens/YieldFarm.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./YieldToken.sol"; + +contract YieldFarm is YieldToken, ReentrancyGuard { + using SafeERC20 for IERC20; + + address public stakingToken; + + constructor(string memory _name, string memory _symbol, address _stakingToken) public YieldToken(_name, _symbol, 0) { + stakingToken = _stakingToken; + } + + function stake(uint256 _amount) external nonReentrant { + IERC20(stakingToken).safeTransferFrom(msg.sender, address(this), _amount); + _mint(msg.sender, _amount); + } + + function unstake(uint256 _amount) external nonReentrant { + _burn(msg.sender, _amount); + IERC20(stakingToken).safeTransfer(msg.sender, _amount); + } +} diff --git a/contracts/tokens/YieldToken.sol b/contracts/tokens/YieldToken.sol new file mode 100644 index 00000000..786c00d5 --- /dev/null +++ b/contracts/tokens/YieldToken.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; + +import "./interfaces/IYieldTracker.sol"; +import "./interfaces/IYieldToken.sol"; + +contract YieldToken is IERC20, IYieldToken { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + string public name; + string public symbol; + uint8 public constant decimals = 18; + + uint256 public override totalSupply; + uint256 public nonStakingSupply; + + address public gov; + + mapping (address => uint256) public balances; + mapping (address => mapping (address => uint256)) public allowances; + + address[] public yieldTrackers; + mapping (address => bool) public nonStakingAccounts; + mapping (address => bool) public admins; + + bool public inWhitelistMode; + mapping (address => bool) public whitelistedHandlers; + + modifier onlyGov() { + require(msg.sender == gov, "YieldToken: forbidden"); + _; + } + + modifier onlyAdmin() { + require(admins[msg.sender], "YieldToken: forbidden"); + _; + } + + constructor(string memory _name, string memory _symbol, uint256 _initialSupply) public { + name = _name; + symbol = _symbol; + gov = msg.sender; + admins[msg.sender] = true; + _mint(msg.sender, _initialSupply); + } + + function setGov(address _gov) external onlyGov { + gov = _gov; + } + + function setInfo(string memory _name, string memory _symbol) external onlyGov { + name = _name; + symbol = _symbol; + } + + function setYieldTrackers(address[] memory _yieldTrackers) external onlyGov { + yieldTrackers = _yieldTrackers; + } + + function addAdmin(address _account) external onlyGov { + admins[_account] = true; + } + + function removeAdmin(address _account) external override onlyGov { + admins[_account] = false; + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { + IERC20(_token).safeTransfer(_account, _amount); + } + + function setInWhitelistMode(bool _inWhitelistMode) external onlyGov { + inWhitelistMode = _inWhitelistMode; + } + + function setWhitelistedHandler(address _handler, bool _isWhitelisted) external onlyGov { + whitelistedHandlers[_handler] = _isWhitelisted; + } + + function addNonStakingAccount(address _account) external onlyAdmin { + require(!nonStakingAccounts[_account], "YieldToken: _account already marked"); + _updateRewards(_account); + nonStakingAccounts[_account] = true; + nonStakingSupply = nonStakingSupply.add(balances[_account]); + } + + function removeNonStakingAccount(address _account) external onlyAdmin { + require(nonStakingAccounts[_account], "YieldToken: _account not marked"); + _updateRewards(_account); + nonStakingAccounts[_account] = false; + nonStakingSupply = nonStakingSupply.sub(balances[_account]); + } + + function recoverClaim(address _account, address _receiver) external onlyAdmin { + for (uint256 i = 0; i < yieldTrackers.length; i++) { + address yieldTracker = yieldTrackers[i]; + IYieldTracker(yieldTracker).claim(_account, _receiver); + } + } + + function claim(address _receiver) external { + for (uint256 i = 0; i < yieldTrackers.length; i++) { + address yieldTracker = yieldTrackers[i]; + IYieldTracker(yieldTracker).claim(msg.sender, _receiver); + } + } + + function totalStaked() external view override returns (uint256) { + return totalSupply.sub(nonStakingSupply); + } + + function balanceOf(address _account) external view override returns (uint256) { + return balances[_account]; + } + + function stakedBalance(address _account) external view override returns (uint256) { + if (nonStakingAccounts[_account]) { + return 0; + } + return balances[_account]; + } + + function transfer(address _recipient, uint256 _amount) external override returns (bool) { + _transfer(msg.sender, _recipient, _amount); + return true; + } + + function allowance(address _owner, address _spender) external view override returns (uint256) { + return allowances[_owner][_spender]; + } + + function approve(address _spender, uint256 _amount) external override returns (bool) { + _approve(msg.sender, _spender, _amount); + return true; + } + + function transferFrom(address _sender, address _recipient, uint256 _amount) external override returns (bool) { + uint256 nextAllowance = allowances[_sender][msg.sender].sub(_amount, "YieldToken: transfer amount exceeds allowance"); + _approve(_sender, msg.sender, nextAllowance); + _transfer(_sender, _recipient, _amount); + return true; + } + + function _mint(address _account, uint256 _amount) internal { + require(_account != address(0), "YieldToken: mint to the zero address"); + + _updateRewards(_account); + + totalSupply = totalSupply.add(_amount); + balances[_account] = balances[_account].add(_amount); + + if (nonStakingAccounts[_account]) { + nonStakingSupply = nonStakingSupply.add(_amount); + } + + emit Transfer(address(0), _account, _amount); + } + + function _burn(address _account, uint256 _amount) internal { + require(_account != address(0), "YieldToken: burn from the zero address"); + + _updateRewards(_account); + + balances[_account] = balances[_account].sub(_amount, "YieldToken: burn amount exceeds balance"); + totalSupply = totalSupply.sub(_amount); + + if (nonStakingAccounts[_account]) { + nonStakingSupply = nonStakingSupply.sub(_amount); + } + + emit Transfer(_account, address(0), _amount); + } + + function _transfer(address _sender, address _recipient, uint256 _amount) private { + require(_sender != address(0), "YieldToken: transfer from the zero address"); + require(_recipient != address(0), "YieldToken: transfer to the zero address"); + + if (inWhitelistMode) { + require(whitelistedHandlers[msg.sender], "YieldToken: msg.sender not whitelisted"); + } + + _updateRewards(_sender); + _updateRewards(_recipient); + + balances[_sender] = balances[_sender].sub(_amount, "YieldToken: transfer amount exceeds balance"); + balances[_recipient] = balances[_recipient].add(_amount); + + if (nonStakingAccounts[_sender]) { + nonStakingSupply = nonStakingSupply.sub(_amount); + } + if (nonStakingAccounts[_recipient]) { + nonStakingSupply = nonStakingSupply.add(_amount); + } + + emit Transfer(_sender, _recipient,_amount); + } + + function _approve(address _owner, address _spender, uint256 _amount) private { + require(_owner != address(0), "YieldToken: approve from the zero address"); + require(_spender != address(0), "YieldToken: approve to the zero address"); + + allowances[_owner][_spender] = _amount; + + emit Approval(_owner, _spender, _amount); + } + + function _updateRewards(address _account) private { + for (uint256 i = 0; i < yieldTrackers.length; i++) { + address yieldTracker = yieldTrackers[i]; + IYieldTracker(yieldTracker).updateRewards(_account); + } + } +} diff --git a/contracts/tokens/YieldTracker.sol b/contracts/tokens/YieldTracker.sol new file mode 100644 index 00000000..9b0306b5 --- /dev/null +++ b/contracts/tokens/YieldTracker.sol @@ -0,0 +1,117 @@ +//SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +import "../libraries/math/SafeMath.sol"; +import "../libraries/token/IERC20.sol"; +import "../libraries/token/SafeERC20.sol"; +import "../libraries/utils/ReentrancyGuard.sol"; + +import "./interfaces/IDistributor.sol"; +import "./interfaces/IYieldTracker.sol"; +import "./interfaces/IYieldToken.sol"; + +// code adapated from https://github.com/trusttoken/smart-contracts/blob/master/contracts/truefi/TrueFarm.sol +contract YieldTracker is IYieldTracker, ReentrancyGuard { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + uint256 public constant PRECISION = 1e30; + + address public gov; + address public yieldToken; + address public distributor; + + uint256 public cumulativeRewardPerToken; + mapping (address => uint256) public claimableReward; + mapping (address => uint256) public previousCumulatedRewardPerToken; + + event Claim(address receiver, uint256 amount); + + modifier onlyGov() { + require(msg.sender == gov, "YieldTracker: forbidden"); + _; + } + + constructor(address _yieldToken) public { + gov = msg.sender; + yieldToken = _yieldToken; + } + + function setGov(address _gov) external onlyGov { + gov = _gov; + } + + function setDistributor(address _distributor) external onlyGov { + distributor = _distributor; + } + + // to help users who accidentally send their tokens to this contract + function withdrawToken(address _token, address _account, uint256 _amount) external onlyGov { + IERC20(_token).safeTransfer(_account, _amount); + } + + function claim(address _account, address _receiver) external override returns (uint256) { + require(msg.sender == yieldToken, "YieldTracker: forbidden"); + updateRewards(_account); + + uint256 tokenAmount = claimableReward[_account]; + claimableReward[_account] = 0; + + address rewardToken = IDistributor(distributor).getRewardToken(address(this)); + IERC20(rewardToken).safeTransfer(_receiver, tokenAmount); + emit Claim(_account, tokenAmount); + + return tokenAmount; + } + + function getTokensPerInterval() external override view returns (uint256) { + return IDistributor(distributor).tokensPerInterval(address(this)); + } + + function claimable(address _account) external override view returns (uint256) { + uint256 stakedBalance = IYieldToken(yieldToken).stakedBalance(_account); + if (stakedBalance == 0) { + return claimableReward[_account]; + } + uint256 pendingRewards = IDistributor(distributor).getDistributionAmount(address(this)).mul(PRECISION); + uint256 totalStaked = IYieldToken(yieldToken).totalStaked(); + uint256 nextCumulativeRewardPerToken = cumulativeRewardPerToken.add(pendingRewards.div(totalStaked)); + return claimableReward[_account].add( + stakedBalance.mul(nextCumulativeRewardPerToken.sub(previousCumulatedRewardPerToken[_account])).div(PRECISION)); + } + + function updateRewards(address _account) public override nonReentrant { + uint256 blockReward; + + if (distributor != address(0)) { + blockReward = IDistributor(distributor).distribute(); + } + + uint256 _cumulativeRewardPerToken = cumulativeRewardPerToken; + uint256 totalStaked = IYieldToken(yieldToken).totalStaked(); + // only update cumulativeRewardPerToken when there are stakers, i.e. when totalStaked > 0 + // if blockReward == 0, then there will be no change to cumulativeRewardPerToken + if (totalStaked > 0 && blockReward > 0) { + _cumulativeRewardPerToken = _cumulativeRewardPerToken.add(blockReward.mul(PRECISION).div(totalStaked)); + cumulativeRewardPerToken = _cumulativeRewardPerToken; + } + + // cumulativeRewardPerToken can only increase + // so if cumulativeRewardPerToken is zero, it means there are no rewards yet + if (_cumulativeRewardPerToken == 0) { + return; + } + + if (_account != address(0)) { + uint256 stakedBalance = IYieldToken(yieldToken).stakedBalance(_account); + uint256 _previousCumulatedReward = previousCumulatedRewardPerToken[_account]; + uint256 _claimableReward = claimableReward[_account].add( + stakedBalance.mul(_cumulativeRewardPerToken.sub(_previousCumulatedReward)).div(PRECISION) + ); + + claimableReward[_account] = _claimableReward; + previousCumulatedRewardPerToken[_account] = _cumulativeRewardPerToken; + } + } +} diff --git a/contracts/tokens/interfaces/IBaseToken.sol b/contracts/tokens/interfaces/IBaseToken.sol new file mode 100644 index 00000000..3d351765 --- /dev/null +++ b/contracts/tokens/interfaces/IBaseToken.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IBaseToken { + function totalStaked() external view returns (uint256); + function stakedBalance(address _account) external view returns (uint256); + function removeAdmin(address _account) external; + function setInPrivateTransferMode(bool _inPrivateTransferMode) external; +} diff --git a/contracts/tokens/interfaces/IBridge.sol b/contracts/tokens/interfaces/IBridge.sol new file mode 100644 index 00000000..fd0f3e77 --- /dev/null +++ b/contracts/tokens/interfaces/IBridge.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IBridge { + function wrap(uint256 _amount, address _receiver) external; + function unwrap(uint256 _amount, address _receiver) external; +} diff --git a/contracts/tokens/interfaces/IDistributor.sol b/contracts/tokens/interfaces/IDistributor.sol new file mode 100644 index 00000000..14e9589a --- /dev/null +++ b/contracts/tokens/interfaces/IDistributor.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IDistributor { + function distribute() external returns (uint256); + function getRewardToken(address _receiver) external view returns (address); + function getDistributionAmount(address _receiver) external view returns (uint256); + function tokensPerInterval(address _receiver) external view returns (uint256); +} diff --git a/contracts/tokens/interfaces/IGLP.sol b/contracts/tokens/interfaces/IGLP.sol new file mode 100644 index 00000000..1bdb146f --- /dev/null +++ b/contracts/tokens/interfaces/IGLP.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IGLP { + function mint(address _account, uint256 _amount) external; + function burn(address _account, uint256 _amount) external; +} diff --git a/contracts/tokens/interfaces/IMintable.sol b/contracts/tokens/interfaces/IMintable.sol new file mode 100644 index 00000000..4bf879b2 --- /dev/null +++ b/contracts/tokens/interfaces/IMintable.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IMintable { + function isMinter(address _account) external returns (bool); + function setMinter(address _minter, bool _isActive) external; + function mint(address _account, uint256 _amount) external; + function burn(address _account, uint256 _amount) external; +} diff --git a/contracts/tokens/interfaces/IUSDG.sol b/contracts/tokens/interfaces/IUSDG.sol new file mode 100644 index 00000000..639dc029 --- /dev/null +++ b/contracts/tokens/interfaces/IUSDG.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IUSDG { + function mint(address _account, uint256 _amount) external; + function burn(address _account, uint256 _amount) external; +} diff --git a/contracts/tokens/interfaces/IWETH.sol b/contracts/tokens/interfaces/IWETH.sol new file mode 100644 index 00000000..34cf0927 --- /dev/null +++ b/contracts/tokens/interfaces/IWETH.sol @@ -0,0 +1,9 @@ +//SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IWETH { + function deposit() external payable; + function transfer(address to, uint value) external returns (bool); + function withdraw(uint) external; +} diff --git a/contracts/tokens/interfaces/IYieldToken.sol b/contracts/tokens/interfaces/IYieldToken.sol new file mode 100644 index 00000000..971d0529 --- /dev/null +++ b/contracts/tokens/interfaces/IYieldToken.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IYieldToken { + function totalStaked() external view returns (uint256); + function stakedBalance(address _account) external view returns (uint256); + function removeAdmin(address _account) external; +} diff --git a/contracts/tokens/interfaces/IYieldTracker.sol b/contracts/tokens/interfaces/IYieldTracker.sol new file mode 100644 index 00000000..19ae08b5 --- /dev/null +++ b/contracts/tokens/interfaces/IYieldTracker.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.6.12; + +interface IYieldTracker { + function claim(address _account, address _receiver) external returns (uint256); + function updateRewards(address _account) external; + function getTokensPerInterval() external view returns (uint256); + function claimable(address _account) external view returns (uint256); +} diff --git a/data/whitelist.js b/data/whitelist.js new file mode 100644 index 00000000..1348d207 --- /dev/null +++ b/data/whitelist.js @@ -0,0 +1,7 @@ +const WHITELIST = [] + +function getWhitelist() { + return WHITELIST +} + +module.exports = { getWhitelist } diff --git a/hardhat.config.js b/hardhat.config.js new file mode 100644 index 00000000..89988a77 --- /dev/null +++ b/hardhat.config.js @@ -0,0 +1,89 @@ +require("@nomiclabs/hardhat-waffle") +require("@nomiclabs/hardhat-etherscan") +require("hardhat-contract-sizer") +require('@typechain/hardhat') + + +const { + BSC_URL, + BSC_DEPLOY_KEY, + BSCSCAN_API_KEY, + ETHERSCAN_API_KEY, + BSC_TESTNET_URL, + BSC_TESTNET_DEPLOY_KEY, + ARBITRUM_TESTNET_DEPLOY_KEY, + ARBITRUM_TESTNET_URL, + ARBITRUM_DEPLOY_KEY, + ARBITRUM_URL, + MAINNET_URL, + MAINNET_DEPLOY_KEY +} = require("./env.json") + +// This is a sample Hardhat task. To learn how to create your own go to +// https://hardhat.org/guides/create-task.html +task("accounts", "Prints the list of accounts", async () => { + const accounts = await ethers.getSigners() + + for (const account of accounts) { + console.info(account.address) + } +}) + +// You need to export an object to set up your config +// Go to https://hardhat.org/config/ to learn more + +/** + * @type import('hardhat/config').HardhatUserConfig + */ +module.exports = { + networks: { + hardhat: { + allowUnlimitedContractSize: true + }, + bsc: { + url: BSC_URL, + chainId: 56, + gasPrice: 10000000000, + accounts: [BSC_DEPLOY_KEY] + }, + testnet: { + url: BSC_TESTNET_URL, + chainId: 97, + gasPrice: 20000000000, + accounts: [BSC_TESTNET_DEPLOY_KEY] + }, + arbitrumTestnet: { + url: ARBITRUM_TESTNET_URL, + gasPrice: 10000000000, + chainId: 421611, + accounts: [ARBITRUM_TESTNET_DEPLOY_KEY] + }, + arbitrum: { + url: ARBITRUM_URL, + gasPrice: 300000000000, + chainId: 42161, + accounts: [ARBITRUM_DEPLOY_KEY] + }, + mainnet: { + url: MAINNET_URL, + gasPrice: 50000000000, + accounts: [MAINNET_DEPLOY_KEY] + } + }, + etherscan: { + apiKey: BSCSCAN_API_KEY + }, + solidity: { + version: "0.6.12", + settings: { + optimizer: { + enabled: true, + runs: 1 + } + } + }, + typechain: { + outDir: "typechain", + target: "ethers-v5", + }, +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..1933c06b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14246 @@ +{ + "name": "gambit-contracts", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@ensdomains/ens": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@ensdomains/ens/-/ens-0.4.5.tgz", + "integrity": "sha512-JSvpj1iNMFjK6K+uVl4unqMoa9rf5jopb8cya5UGBWz23Nw8hSNT7efgUx4BTlAPAgpNlEioUfeTyQ6J9ZvTVw==", + "dev": true, + "requires": { + "bluebird": "^3.5.2", + "eth-ens-namehash": "^2.0.8", + "solc": "^0.4.20", + "testrpc": "0.0.1", + "web3-utils": "^1.0.0-beta.31" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "require-from-string": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", + "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "solc": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.26.tgz", + "integrity": "sha512-o+c6FpkiHd+HPjmjEVpQgH7fqZ14tJpXhho+/bQXlXbliLIS/xjXb42Vxh+qQY1WCSTMQ0+a5vR9vi0MfhU6mA==", + "dev": true, + "requires": { + "fs-extra": "^0.30.0", + "memorystream": "^0.3.1", + "require-from-string": "^1.1.0", + "semver": "^5.3.0", + "yargs": "^4.7.1" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, + "yargs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz", + "integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=", + "dev": true, + "requires": { + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "lodash.assign": "^4.0.3", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.1", + "which-module": "^1.0.0", + "window-size": "^0.2.0", + "y18n": "^3.2.1", + "yargs-parser": "^2.4.1" + } + }, + "yargs-parser": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz", + "integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "lodash.assign": "^4.0.6" + } + } + } + }, + "@ensdomains/resolver": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@ensdomains/resolver/-/resolver-0.2.4.tgz", + "integrity": "sha512-bvaTH34PMCbv6anRa9I/0zjLJgY4EuznbEMgbV77JBCQ9KNC46rzi0avuxpOfu+xDjPEtSFGqVEOr5GlUSGudA==", + "dev": true + }, + "@ethereum-waffle/chai": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ethereum-waffle/chai/-/chai-3.4.0.tgz", + "integrity": "sha512-GVaFKuFbFUclMkhHtQTDnWBnBQMJc/pAbfbFj/nnIK237WPLsO3KDDslA7m+MNEyTAOFrcc0CyfruAGGXAQw3g==", + "dev": true, + "requires": { + "@ethereum-waffle/provider": "^3.4.0", + "ethers": "^5.0.0" + } + }, + "@ethereum-waffle/compiler": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ethereum-waffle/compiler/-/compiler-3.4.0.tgz", + "integrity": "sha512-a2wxGOoB9F1QFRE+Om7Cz2wn+pxM/o7a0a6cbwhaS2lECJgFzeN9xEkVrKahRkF4gEfXGcuORg4msP0Asxezlw==", + "dev": true, + "requires": { + "@resolver-engine/imports": "^0.3.3", + "@resolver-engine/imports-fs": "^0.3.3", + "@typechain/ethers-v5": "^2.0.0", + "@types/mkdirp": "^0.5.2", + "@types/node-fetch": "^2.5.5", + "ethers": "^5.0.1", + "mkdirp": "^0.5.1", + "node-fetch": "^2.6.1", + "solc": "^0.6.3", + "ts-generator": "^0.1.1", + "typechain": "^3.0.0" + }, + "dependencies": { + "@typechain/ethers-v5": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-2.0.0.tgz", + "integrity": "sha512-0xdCkyGOzdqh4h5JSf+zoWx85IusEjDcPIwNEHP8mrWSnCae4rvrqB+/gtpdNfX7zjlFlZiMeePn2r63EI3Lrw==", + "dev": true, + "requires": { + "ethers": "^5.0.2" + } + }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true + }, + "ts-essentials": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-6.0.7.tgz", + "integrity": "sha512-2E4HIIj4tQJlIHuATRHayv0EfMGK3ris/GRk1E3CFnsZzeNV+hUmelbaTZHLtXaZppM5oLhHRtO04gINC4Jusw==", + "dev": true + }, + "typechain": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/typechain/-/typechain-3.0.0.tgz", + "integrity": "sha512-ft4KVmiN3zH4JUFu2WJBrwfHeDf772Tt2d8bssDTo/YcckKW2D+OwFrHXRC6hJvO3mHjFQTihoMV6fJOi0Hngg==", + "dev": true, + "requires": { + "command-line-args": "^4.0.7", + "debug": "^4.1.1", + "fs-extra": "^7.0.0", + "js-sha3": "^0.8.0", + "lodash": "^4.17.15", + "ts-essentials": "^6.0.3", + "ts-generator": "^0.1.1" + } + } + } + }, + "@ethereum-waffle/ens": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@ethereum-waffle/ens/-/ens-3.3.0.tgz", + "integrity": "sha512-zVIH/5cQnIEgJPg1aV8+ehYicpcfuAisfrtzYh1pN3UbfeqPylFBeBaIZ7xj/xYzlJjkrek/h9VfULl6EX9Aqw==", + "dev": true, + "requires": { + "@ensdomains/ens": "^0.4.4", + "@ensdomains/resolver": "^0.2.4", + "ethers": "^5.0.1" + } + }, + "@ethereum-waffle/mock-contract": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@ethereum-waffle/mock-contract/-/mock-contract-3.3.0.tgz", + "integrity": "sha512-apwq0d+2nQxaNwsyLkE+BNMBhZ1MKGV28BtI9WjD3QD2Ztdt1q9II4sKA4VrLTUneYSmkYbJZJxw89f+OpJGyw==", + "dev": true, + "requires": { + "@ethersproject/abi": "^5.0.1", + "ethers": "^5.0.1" + } + }, + "@ethereum-waffle/provider": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ethereum-waffle/provider/-/provider-3.4.0.tgz", + "integrity": "sha512-QgseGzpwlzmaHXhqfdzthCGu5a6P1SBF955jQHf/rBkK1Y7gGo2ukt3rXgxgfg/O5eHqRU+r8xw5MzVyVaBscQ==", + "dev": true, + "requires": { + "@ethereum-waffle/ens": "^3.3.0", + "ethers": "^5.0.1", + "ganache-core": "^2.13.2", + "patch-package": "^6.2.2", + "postinstall-postinstall": "^2.1.0" + } + }, + "@ethereumjs/block": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/block/-/block-3.4.0.tgz", + "integrity": "sha512-umKAoTX32yXzErpIksPHodFc/5y8bmZMnOl6hWy5Vd8xId4+HKFUOyEiN16Y97zMwFRysRpcrR6wBejfqc6Bmg==", + "dev": true, + "requires": { + "@ethereumjs/common": "^2.4.0", + "@ethereumjs/tx": "^3.3.0", + "ethereumjs-util": "^7.1.0", + "merkle-patricia-tree": "^4.2.0" + } + }, + "@ethereumjs/blockchain": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/blockchain/-/blockchain-5.4.0.tgz", + "integrity": "sha512-wAuKLaew6PL52kH8YPXO7PbjjKV12jivRSyHQehkESw4slSLLfYA6Jv7n5YxyT2ajD7KNMPVh7oyF/MU6HcOvg==", + "dev": true, + "requires": { + "@ethereumjs/block": "^3.4.0", + "@ethereumjs/common": "^2.4.0", + "@ethereumjs/ethash": "^1.0.0", + "debug": "^2.2.0", + "ethereumjs-util": "^7.1.0", + "level-mem": "^5.0.1", + "lru-cache": "^5.1.1", + "rlp": "^2.2.4", + "semaphore-async-await": "^1.5.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "@ethereumjs/common": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-2.4.0.tgz", + "integrity": "sha512-UdkhFWzWcJCZVsj1O/H8/oqj/0RVYjLc1OhPjBrQdALAkQHpCp8xXI4WLnuGTADqTdJZww0NtgwG+TRPkXt27w==", + "dev": true, + "requires": { + "crc-32": "^1.2.0", + "ethereumjs-util": "^7.1.0" + } + }, + "@ethereumjs/ethash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/ethash/-/ethash-1.0.0.tgz", + "integrity": "sha512-iIqnGG6NMKesyOxv2YctB2guOVX18qMAWlj3QlZyrc+GqfzLqoihti+cVNQnyNxr7eYuPdqwLQOFuPe6g/uKjw==", + "dev": true, + "requires": { + "@types/levelup": "^4.3.0", + "buffer-xor": "^2.0.1", + "ethereumjs-util": "^7.0.7", + "miller-rabin": "^4.0.0" + } + }, + "@ethereumjs/tx": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-3.3.0.tgz", + "integrity": "sha512-yTwEj2lVzSMgE6Hjw9Oa1DZks/nKTWM8Wn4ykDNapBPua2f4nXO3qKnni86O6lgDj5fVNRqbDsD0yy7/XNGDEA==", + "dev": true, + "requires": { + "@ethereumjs/common": "^2.4.0", + "ethereumjs-util": "^7.1.0" + } + }, + "@ethereumjs/vm": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@ethereumjs/vm/-/vm-5.5.2.tgz", + "integrity": "sha512-AydZ4wfvZAsBuFzs3xVSA2iU0hxhL8anXco3UW3oh9maVC34kTEytOfjHf06LTEfN0MF9LDQ4ciLa7If6ZN/sg==", + "dev": true, + "requires": { + "@ethereumjs/block": "^3.4.0", + "@ethereumjs/blockchain": "^5.4.0", + "@ethereumjs/common": "^2.4.0", + "@ethereumjs/tx": "^3.3.0", + "async-eventemitter": "^0.2.4", + "core-js-pure": "^3.0.1", + "debug": "^2.2.0", + "ethereumjs-util": "^7.1.0", + "functional-red-black-tree": "^1.0.1", + "mcl-wasm": "^0.7.1", + "merkle-patricia-tree": "^4.2.0", + "rustbn.js": "~0.2.0", + "util.promisify": "^1.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "@ethersproject/abi": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.4.1.tgz", + "integrity": "sha512-9mhbjUk76BiSluiiW4BaYyI58KSbDMMQpCLdsAR+RsT2GyATiNYxVv+pGWRrekmsIdY3I+hOqsYQSTkc8L/mcg==", + "dev": true, + "requires": { + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/hash": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/strings": "^5.4.0" + } + }, + "@ethersproject/abstract-provider": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz", + "integrity": "sha512-3EedfKI3LVpjSKgAxoUaI+gB27frKsxzm+r21w9G60Ugk+3wVLQwhi1LsEJAKNV7WoZc8CIpNrATlL1QFABjtQ==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/networks": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", + "@ethersproject/web": "^5.4.0" + } + }, + "@ethersproject/abstract-signer": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz", + "integrity": "sha512-SkkFL5HVq1k4/25dM+NWP9MILgohJCgGv5xT5AcRruGz4ILpfHeBtO/y6j+Z3UN/PAjDeb4P7E51Yh8wcGNLGA==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0" + } + }, + "@ethersproject/address": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.4.0.tgz", + "integrity": "sha512-SD0VgOEkcACEG/C6xavlU1Hy3m5DGSXW3CUHkaaEHbAPPsgi0coP5oNPsxau8eTlZOk/bpa/hKeCNoK5IzVI2Q==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/rlp": "^5.4.0" + } + }, + "@ethersproject/base64": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.4.0.tgz", + "integrity": "sha512-CjQw6E17QDSSC5jiM9YpF7N1aSCHmYGMt9bWD8PWv6YPMxjsys2/Q8xLrROKI3IWJ7sFfZ8B3flKDTM5wlWuZQ==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0" + } + }, + "@ethersproject/basex": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.4.0.tgz", + "integrity": "sha512-J07+QCVJ7np2bcpxydFVf/CuYo9mZ7T73Pe7KQY4c1lRlrixMeblauMxHXD0MPwFmUHZIILDNViVkykFBZylbg==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/properties": "^5.4.0" + } + }, + "@ethersproject/bignumber": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.4.1.tgz", + "integrity": "sha512-fJhdxqoQNuDOk6epfM7yD6J8Pol4NUCy1vkaGAkuujZm0+lNow//MKu1hLhRiYV4BsOHyBv5/lsTjF+7hWwhJg==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "bn.js": "^4.11.9" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "@ethersproject/bytes": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.4.0.tgz", + "integrity": "sha512-H60ceqgTHbhzOj4uRc/83SCN9d+BSUnOkrr2intevqdtEMO1JFVZ1XL84OEZV+QjV36OaZYxtnt4lGmxcGsPfA==", + "dev": true, + "requires": { + "@ethersproject/logger": "^5.4.0" + } + }, + "@ethersproject/constants": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.4.0.tgz", + "integrity": "sha512-tzjn6S7sj9+DIIeKTJLjK9WGN2Tj0P++Z8ONEIlZjyoTkBuODN+0VfhAyYksKi43l1Sx9tX2VlFfzjfmr5Wl3Q==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.4.0" + } + }, + "@ethersproject/contracts": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.4.1.tgz", + "integrity": "sha512-m+z2ZgPy4pyR15Je//dUaymRUZq5MtDajF6GwFbGAVmKz/RF+DNIPwF0k5qEcL3wPGVqUjFg2/krlCRVTU4T5w==", + "dev": true, + "requires": { + "@ethersproject/abi": "^5.4.0", + "@ethersproject/abstract-provider": "^5.4.0", + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/transactions": "^5.4.0" + } + }, + "@ethersproject/hash": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.4.0.tgz", + "integrity": "sha512-xymAM9tmikKgbktOCjW60Z5sdouiIIurkZUr9oW5NOex5uwxrbsYG09kb5bMcNjlVeJD3yPivTNzViIs1GCbqA==", + "dev": true, + "requires": { + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/strings": "^5.4.0" + } + }, + "@ethersproject/hdnode": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.4.0.tgz", + "integrity": "sha512-pKxdS0KAaeVGfZPp1KOiDLB0jba11tG6OP1u11QnYfb7pXn6IZx0xceqWRr6ygke8+Kw74IpOoSi7/DwANhy8Q==", + "dev": true, + "requires": { + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/basex": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/pbkdf2": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/sha2": "^5.4.0", + "@ethersproject/signing-key": "^5.4.0", + "@ethersproject/strings": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", + "@ethersproject/wordlists": "^5.4.0" + } + }, + "@ethersproject/json-wallets": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.4.0.tgz", + "integrity": "sha512-igWcu3fx4aiczrzEHwG1xJZo9l1cFfQOWzTqwRw/xcvxTk58q4f9M7cjh51EKphMHvrJtcezJ1gf1q1AUOfEQQ==", + "dev": true, + "requires": { + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/hdnode": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/pbkdf2": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/random": "^5.4.0", + "@ethersproject/strings": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "@ethersproject/keccak256": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.4.0.tgz", + "integrity": "sha512-FBI1plWet+dPUvAzPAeHzRKiPpETQzqSUWR1wXJGHVWi4i8bOSrpC3NwpkPjgeXG7MnugVc1B42VbfnQikyC/A==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "js-sha3": "0.5.7" + } + }, + "@ethersproject/logger": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.4.1.tgz", + "integrity": "sha512-DZ+bRinnYLPw1yAC64oRl0QyVZj43QeHIhVKfD/+YwSz4wsv1pfwb5SOFjz+r710YEWzU6LrhuSjpSO+6PeE4A==", + "dev": true + }, + "@ethersproject/networks": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.4.2.tgz", + "integrity": "sha512-eekOhvJyBnuibfJnhtK46b8HimBc5+4gqpvd1/H9LEl7Q7/qhsIhM81dI9Fcnjpk3jB1aTy6bj0hz3cifhNeYw==", + "dev": true, + "requires": { + "@ethersproject/logger": "^5.4.0" + } + }, + "@ethersproject/pbkdf2": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz", + "integrity": "sha512-x94aIv6tiA04g6BnazZSLoRXqyusawRyZWlUhKip2jvoLpzJuLb//KtMM6PEovE47pMbW+Qe1uw+68ameJjB7g==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/sha2": "^5.4.0" + } + }, + "@ethersproject/properties": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.4.1.tgz", + "integrity": "sha512-cyCGlF8wWlIZyizsj2PpbJ9I7rIlUAfnHYwy/T90pdkSn/NFTa5YWZx2wTJBe9V7dD65dcrrEMisCRUJiq6n3w==", + "dev": true, + "requires": { + "@ethersproject/logger": "^5.4.0" + } + }, + "@ethersproject/providers": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.4.5.tgz", + "integrity": "sha512-1GkrvkiAw3Fj28cwi1Sqm8ED1RtERtpdXmRfwIBGmqBSN5MoeRUHuwHPppMtbPayPgpFcvD7/Gdc9doO5fGYgw==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.4.0", + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/basex": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/hash": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/networks": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/random": "^5.4.0", + "@ethersproject/rlp": "^5.4.0", + "@ethersproject/sha2": "^5.4.0", + "@ethersproject/strings": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", + "@ethersproject/web": "^5.4.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "@ethersproject/random": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.4.0.tgz", + "integrity": "sha512-pnpWNQlf0VAZDEOVp1rsYQosmv2o0ITS/PecNw+mS2/btF8eYdspkN0vIXrCMtkX09EAh9bdk8GoXmFXM1eAKw==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0" + } + }, + "@ethersproject/rlp": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.4.0.tgz", + "integrity": "sha512-0I7MZKfi+T5+G8atId9QaQKHRvvasM/kqLyAH4XxBCBchAooH2EX5rL9kYZWwcm3awYV+XC7VF6nLhfeQFKVPg==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0" + } + }, + "@ethersproject/sha2": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.4.0.tgz", + "integrity": "sha512-siheo36r1WD7Cy+bDdE1BJ8y0bDtqXCOxRMzPa4bV1TGt/eTUUt03BHoJNB6reWJD8A30E/pdJ8WFkq+/uz4Gg==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "hash.js": "1.1.7" + } + }, + "@ethersproject/signing-key": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.4.0.tgz", + "integrity": "sha512-q8POUeywx6AKg2/jX9qBYZIAmKSB4ubGXdQ88l40hmATj29JnG5pp331nAWwwxPn2Qao4JpWHNZsQN+bPiSW9A==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "bn.js": "^4.11.9", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "@ethersproject/solidity": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.4.0.tgz", + "integrity": "sha512-XFQTZ7wFSHOhHcV1DpcWj7VXECEiSrBuv7JErJvB9Uo+KfCdc3QtUZV+Vjh/AAaYgezUEKbCtE6Khjm44seevQ==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/sha2": "^5.4.0", + "@ethersproject/strings": "^5.4.0" + } + }, + "@ethersproject/strings": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.4.0.tgz", + "integrity": "sha512-k/9DkH5UGDhv7aReXLluFG5ExurwtIpUfnDNhQA29w896Dw3i4uDTz01Quaptbks1Uj9kI8wo9tmW73wcIEaWA==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/logger": "^5.4.0" + } + }, + "@ethersproject/transactions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.4.0.tgz", + "integrity": "sha512-s3EjZZt7xa4BkLknJZ98QGoIza94rVjaEed0rzZ/jB9WrIuu/1+tjvYCWzVrystXtDswy7TPBeIepyXwSYa4WQ==", + "dev": true, + "requires": { + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/rlp": "^5.4.0", + "@ethersproject/signing-key": "^5.4.0" + } + }, + "@ethersproject/units": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.4.0.tgz", + "integrity": "sha512-Z88krX40KCp+JqPCP5oPv5p750g+uU6gopDYRTBGcDvOASh6qhiEYCRatuM/suC4S2XW9Zz90QI35MfSrTIaFg==", + "dev": true, + "requires": { + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/constants": "^5.4.0", + "@ethersproject/logger": "^5.4.0" + } + }, + "@ethersproject/wallet": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.4.0.tgz", + "integrity": "sha512-wU29majLjM6AjCjpat21mPPviG+EpK7wY1+jzKD0fg3ui5fgedf2zEu1RDgpfIMsfn8fJHJuzM4zXZ2+hSHaSQ==", + "dev": true, + "requires": { + "@ethersproject/abstract-provider": "^5.4.0", + "@ethersproject/abstract-signer": "^5.4.0", + "@ethersproject/address": "^5.4.0", + "@ethersproject/bignumber": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/hash": "^5.4.0", + "@ethersproject/hdnode": "^5.4.0", + "@ethersproject/json-wallets": "^5.4.0", + "@ethersproject/keccak256": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/random": "^5.4.0", + "@ethersproject/signing-key": "^5.4.0", + "@ethersproject/transactions": "^5.4.0", + "@ethersproject/wordlists": "^5.4.0" + } + }, + "@ethersproject/web": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.4.0.tgz", + "integrity": "sha512-1bUusGmcoRLYgMn6c1BLk1tOKUIFuTg8j+6N8lYlbMpDesnle+i3pGSagGNvwjaiLo4Y5gBibwctpPRmjrh4Og==", + "dev": true, + "requires": { + "@ethersproject/base64": "^5.4.0", + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/strings": "^5.4.0" + } + }, + "@ethersproject/wordlists": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.4.0.tgz", + "integrity": "sha512-FemEkf6a+EBKEPxlzeVgUaVSodU7G0Na89jqKjmWMlDB0tomoU8RlEMgUvXyqtrg8N4cwpLh8nyRnm1Nay1isA==", + "dev": true, + "requires": { + "@ethersproject/bytes": "^5.4.0", + "@ethersproject/hash": "^5.4.0", + "@ethersproject/logger": "^5.4.0", + "@ethersproject/properties": "^5.4.0", + "@ethersproject/strings": "^5.4.0" + } + }, + "@nomiclabs/hardhat-ethers": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.0.2.tgz", + "integrity": "sha512-6quxWe8wwS4X5v3Au8q1jOvXYEPkS1Fh+cME5u6AwNdnI4uERvPlVjlgRWzpnb+Rrt1l/cEqiNRH9GlsBMSDQg==", + "dev": true + }, + "@nomiclabs/hardhat-etherscan": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-2.1.6.tgz", + "integrity": "sha512-gCvT5fj8GbXS9+ACS3BzrX0pzYHHZqAHCb+NcipOkl2cy48FakUXlzrCf4P4sTH+Y7W10OgT62ezD1sJ+/NikQ==", + "dev": true, + "requires": { + "@ethersproject/abi": "^5.1.2", + "@ethersproject/address": "^5.0.2", + "cbor": "^5.0.2", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "node-fetch": "^2.6.0", + "semver": "^6.3.0" + } + }, + "@nomiclabs/hardhat-waffle": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.1.tgz", + "integrity": "sha512-2YR2V5zTiztSH9n8BYWgtv3Q+EL0N5Ltm1PAr5z20uAY4SkkfylJ98CIqt18XFvxTD5x4K2wKBzddjV9ViDAZQ==", + "dev": true, + "requires": { + "@types/sinon-chai": "^3.2.3", + "@types/web3": "1.0.19" + } + }, + "@resolver-engine/core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@resolver-engine/core/-/core-0.3.3.tgz", + "integrity": "sha512-eB8nEbKDJJBi5p5SrvrvILn4a0h42bKtbCTri3ZxCGt6UvoQyp7HnGOfki944bUjBSHKK3RvgfViHn+kqdXtnQ==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "is-url": "^1.2.4", + "request": "^2.85.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@resolver-engine/fs": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@resolver-engine/fs/-/fs-0.3.3.tgz", + "integrity": "sha512-wQ9RhPUcny02Wm0IuJwYMyAG8fXVeKdmhm8xizNByD4ryZlx6PP6kRen+t/haF43cMfmaV7T3Cx6ChOdHEhFUQ==", + "dev": true, + "requires": { + "@resolver-engine/core": "^0.3.3", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@resolver-engine/imports": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@resolver-engine/imports/-/imports-0.3.3.tgz", + "integrity": "sha512-anHpS4wN4sRMwsAbMXhMfOD/y4a4Oo0Cw/5+rue7hSwGWsDOQaAU1ClK1OxjUC35/peazxEl8JaSRRS+Xb8t3Q==", + "dev": true, + "requires": { + "@resolver-engine/core": "^0.3.3", + "debug": "^3.1.0", + "hosted-git-info": "^2.6.0", + "path-browserify": "^1.0.0", + "url": "^0.11.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@resolver-engine/imports-fs": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@resolver-engine/imports-fs/-/imports-fs-0.3.3.tgz", + "integrity": "sha512-7Pjg/ZAZtxpeyCFlZR5zqYkz+Wdo84ugB5LApwriT8XFeQoLwGUj4tZFFvvCuxaNCcqZzCYbonJgmGObYBzyCA==", + "dev": true, + "requires": { + "@resolver-engine/fs": "^0.3.3", + "@resolver-engine/imports": "^0.3.3", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@sentry/core": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", + "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", + "dev": true, + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/hub": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", + "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", + "dev": true, + "requires": { + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/minimal": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", + "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", + "dev": true, + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/node": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", + "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", + "dev": true, + "requires": { + "@sentry/core": "5.30.0", + "@sentry/hub": "5.30.0", + "@sentry/tracing": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + } + }, + "@sentry/tracing": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", + "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", + "dev": true, + "requires": { + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sentry/types": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", + "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", + "dev": true + }, + "@sentry/utils": { + "version": "5.30.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", + "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", + "dev": true, + "requires": { + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" + } + }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@solidity-parser/parser": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.11.1.tgz", + "integrity": "sha512-H8BSBoKE8EubJa0ONqecA2TviT3TnHeC4NpgnAHSUiuhZoQBfPB4L2P9bs8R6AoTW10Endvh3vc+fomVMIDIYQ==", + "dev": true + }, + "@typechain/ethers-v5": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-7.0.1.tgz", + "integrity": "sha512-mXEJ7LG0pOYO+MRPkHtbf30Ey9X2KAsU0wkeoVvjQIn7iAY6tB3k3s+82bbmJAUMyENbQ04RDOZit36CgSG6Gg==", + "dev": true + }, + "@typechain/hardhat": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@typechain/hardhat/-/hardhat-2.3.0.tgz", + "integrity": "sha512-zERrtNol86L4DX60ktnXxP7Cq8rSZHPaQvsChyiQQVuvVs2FTLm24Yi+MYnfsIdbUBIXZG7SxDWhtCF5I0tJNQ==", + "dev": true, + "requires": { + "fs-extra": "^9.1.0" + }, + "dependencies": { + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, + "@types/abstract-leveldown": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/abstract-leveldown/-/abstract-leveldown-5.0.2.tgz", + "integrity": "sha512-+jA1XXF3jsz+Z7FcuiNqgK53hTa/luglT2TyTpKPqoYbxVY+mCPF22Rm+q3KPBrMHJwNXFrTViHszBOfU4vftQ==", + "dev": true + }, + "@types/bn.js": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.0.tgz", + "integrity": "sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/chai": { + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz", + "integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==", + "dev": true + }, + "@types/level-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/level-errors/-/level-errors-3.0.0.tgz", + "integrity": "sha512-/lMtoq/Cf/2DVOm6zE6ORyOM+3ZVm/BvzEZVxUhf6bgh8ZHglXlBqxbxSlJeVp8FCbD3IVvk/VbsaNmDjrQvqQ==", + "dev": true + }, + "@types/levelup": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@types/levelup/-/levelup-4.3.3.tgz", + "integrity": "sha512-K+OTIjJcZHVlZQN1HmU64VtrC0jC3dXWQozuEIR9zVvltIk90zaGPM2AgT+fIkChpzHhFE3YnvFLCbLtzAmexA==", + "dev": true, + "requires": { + "@types/abstract-leveldown": "*", + "@types/level-errors": "*", + "@types/node": "*" + } + }, + "@types/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", + "dev": true + }, + "@types/mkdirp": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-0.5.2.tgz", + "integrity": "sha512-U5icWpv7YnZYGsN4/cmh3WD2onMY0aJIiTE6+51TwJCttdHvtCYmkBNOobHlXwrJRL0nkH9jH4kD+1FAdMN4Tg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "16.7.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.7.10.tgz", + "integrity": "sha512-S63Dlv4zIPb8x6MMTgDq5WWRJQe56iBEY0O3SOFA9JrRienkOVDXSXBjjJw6HTNQYSE2JI6GMCR6LVbIMHJVvA==", + "dev": true + }, + "@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==", + "dev": true + }, + "@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/sinon": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.2.tgz", + "integrity": "sha512-BHn8Bpkapj8Wdfxvh2jWIUoaYB/9/XhsL0oOvBfRagJtKlSl9NWPcFOz2lRukI9szwGxFtYZCTejJSqsGDbdmw==", + "dev": true, + "requires": { + "@sinonjs/fake-timers": "^7.1.0" + } + }, + "@types/sinon-chai": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.5.tgz", + "integrity": "sha512-bKQqIpew7mmIGNRlxW6Zli/QVyc3zikpGzCa797B/tRnD9OtHvZ/ts8sYXV+Ilj9u3QRaUEM8xrjgd1gwm1BpQ==", + "dev": true, + "requires": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, + "@types/underscore": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.11.3.tgz", + "integrity": "sha512-Fl1TX1dapfXyDqFg2ic9M+vlXRktcPJrc4PR7sRc7sdVrjavg/JHlbUXBt8qWWqhJrmSqg3RNAkAPRiOYw6Ahw==", + "dev": true + }, + "@types/web3": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@types/web3/-/web3-1.0.19.tgz", + "integrity": "sha512-fhZ9DyvDYDwHZUp5/STa9XW2re0E8GxoioYJ4pEUZ13YHpApSagixj7IAdoYH5uAK+UalGq6Ml8LYzmgRA/q+A==", + "dev": true, + "requires": { + "@types/bn.js": "*", + "@types/underscore": "*" + } + }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "abstract-leveldown": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.3.0.tgz", + "integrity": "sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true + }, + "aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0=", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-back": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", + "dev": true, + "requires": { + "typical": "^2.6.1" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "async-eventemitter": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", + "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", + "dev": true, + "requires": { + "async": "^2.4.0" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + } + } + }, + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "dev": true + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "blakejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.1.tgz", + "integrity": "sha512-bLG6PHOCZJKNshTjGRBvET0vTciwQE6zFKOKKXPDJfwFBd4Ac0yBfPZqcGvGJap50l7ktvlpFqc2jGVaUgbJgg==", + "dev": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "dependencies": { + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + } + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "dev": true, + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "buffer-to-arraybuffer": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", + "integrity": "sha1-YGSkD6dutDxyOrqe+PbhIW0QURo=", + "dev": true + }, + "buffer-xor": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-2.0.2.tgz", + "integrity": "sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "cbor": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-5.2.0.tgz", + "integrity": "sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A==", + "dev": true, + "requires": { + "bignumber.js": "^9.0.1", + "nofilter": "^1.0.4" + } + }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cli-table3": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", + "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^4.2.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true + }, + "command-line-args": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-4.0.7.tgz", + "integrity": "sha512-aUdPvQRAyBvQd2n7jXcsMDz68ckBJELXNzBybCHOibUWEg0mWTnaYCSRU8h9R+aNRSvDihJtssSRCiDRpLaezA==", + "dev": true, + "requires": { + "array-back": "^2.0.0", + "find-replace": "^1.0.3", + "typical": "^2.6.1" + } + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "dev": true + }, + "core-js-pure": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.17.2.tgz", + "integrity": "sha512-2VV7DlIbooyTI7Bh+yzOOWL9tGwLnQKHno7qATE+fqZzDKYr6llVjVQOzpD/QLZFgXDPb8T71pJokHEZHEYJhQ==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "crc-32": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", + "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", + "dev": true, + "requires": { + "exit-on-epipe": "~1.0.1", + "printj": "~1.1.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deferred-leveldown": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", + "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", + "dev": true, + "requires": { + "abstract-leveldown": "~6.2.1", + "inherits": "^2.0.3" + }, + "dependencies": { + "abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "encoding-down": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", + "dev": true, + "requires": { + "abstract-leveldown": "^6.2.1", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", + "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "eth-ens-namehash": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", + "integrity": "sha1-IprEbsqG1S4MmR58sq74P/D2i88=", + "dev": true, + "requires": { + "idna-uts46-hx": "^2.3.1", + "js-sha3": "^0.5.7" + } + }, + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "eth-sig-util": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.5.4.tgz", + "integrity": "sha512-aCMBwp8q/4wrW4QLsF/HYBOSA7TpLKmkVwP3pYQNkEEseW2Rr8Z5Uxc9/h6HX+OG3tuHo+2bINVSihIeBfym6A==", + "dev": true, + "requires": { + "ethereumjs-abi": "0.6.8", + "ethereumjs-util": "^5.1.1", + "tweetnacl": "^1.0.3", + "tweetnacl-util": "^0.15.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + } + } + }, + "ethereum-bloom-filters": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", + "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", + "dev": true, + "requires": { + "js-sha3": "^0.8.0" + }, + "dependencies": { + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true + } + } + }, + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "ethereum-waffle": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ethereum-waffle/-/ethereum-waffle-3.4.0.tgz", + "integrity": "sha512-ADBqZCkoSA5Isk486ntKJVjFEawIiC+3HxNqpJqONvh3YXBTNiRfXvJtGuAFLXPG91QaqkGqILEHANAo7j/olQ==", + "dev": true, + "requires": { + "@ethereum-waffle/chai": "^3.4.0", + "@ethereum-waffle/compiler": "^3.4.0", + "@ethereum-waffle/mock-contract": "^3.3.0", + "@ethereum-waffle/provider": "^3.4.0", + "ethers": "^5.0.1" + } + }, + "ethereumjs-abi": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", + "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", + "dev": true, + "requires": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + }, + "dependencies": { + "@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "ethereumjs-util": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.0.tgz", + "integrity": "sha512-kR+vhu++mUDARrsMMhsjjzPduRVAeundLGXucGRHF3B4oEltOUspfgCVco4kckucj3FMlLaZHUl9n7/kdmr6Tw==", + "dev": true, + "requires": { + "@types/bn.js": "^5.1.0", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.4" + }, + "dependencies": { + "bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", + "dev": true + } + } + }, + "ethers": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.4.6.tgz", + "integrity": "sha512-F7LXARyB/Px3AQC6/QKedWZ8eqCkgOLORqL4B/F0Mag/K+qJSFGqsR36EaOZ6fKg3ZonI+pdbhb4A8Knt/43jQ==", + "dev": true, + "requires": { + "@ethersproject/abi": "5.4.1", + "@ethersproject/abstract-provider": "5.4.1", + "@ethersproject/abstract-signer": "5.4.1", + "@ethersproject/address": "5.4.0", + "@ethersproject/base64": "5.4.0", + "@ethersproject/basex": "5.4.0", + "@ethersproject/bignumber": "5.4.1", + "@ethersproject/bytes": "5.4.0", + "@ethersproject/constants": "5.4.0", + "@ethersproject/contracts": "5.4.1", + "@ethersproject/hash": "5.4.0", + "@ethersproject/hdnode": "5.4.0", + "@ethersproject/json-wallets": "5.4.0", + "@ethersproject/keccak256": "5.4.0", + "@ethersproject/logger": "5.4.1", + "@ethersproject/networks": "5.4.2", + "@ethersproject/pbkdf2": "5.4.0", + "@ethersproject/properties": "5.4.1", + "@ethersproject/providers": "5.4.5", + "@ethersproject/random": "5.4.0", + "@ethersproject/rlp": "5.4.0", + "@ethersproject/sha2": "5.4.0", + "@ethersproject/signing-key": "5.4.0", + "@ethersproject/solidity": "5.4.0", + "@ethersproject/strings": "5.4.0", + "@ethersproject/transactions": "5.4.0", + "@ethersproject/units": "5.4.0", + "@ethersproject/wallet": "5.4.0", + "@ethersproject/web": "5.4.0", + "@ethersproject/wordlists": "5.4.0" + } + }, + "ethjs-unit": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", + "integrity": "sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk=", + "dev": true, + "requires": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", + "dev": true + } + } + }, + "ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "dev": true, + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + } + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "exit-on-epipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-replace": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-1.0.3.tgz", + "integrity": "sha1-uI5zZNLZyVlVnziMZmcNYTBEH6A=", + "dev": true, + "requires": { + "array-back": "^1.0.4", + "test-value": "^2.1.0" + }, + "dependencies": { + "array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=", + "dev": true, + "requires": { + "typical": "^2.6.0" + } + } + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "requires": { + "micromatch": "^4.0.2" + } + }, + "flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "follow-redirects": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", + "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fp-ts": { + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", + "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", + "dev": true + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "ganache-core": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/ganache-core/-/ganache-core-2.13.2.tgz", + "integrity": "sha512-tIF5cR+ANQz0+3pHWxHjIwHqFXcVo0Mb+kcsNhglNFALcYo49aQpnS9dqHartqPfMFjiHh/qFoD3mYK0d/qGgw==", + "dev": true, + "requires": { + "abstract-leveldown": "3.0.0", + "async": "2.6.2", + "bip39": "2.5.0", + "cachedown": "1.0.0", + "clone": "2.1.2", + "debug": "3.2.6", + "encoding-down": "5.0.4", + "eth-sig-util": "3.0.0", + "ethereumjs-abi": "0.6.8", + "ethereumjs-account": "3.0.0", + "ethereumjs-block": "2.2.2", + "ethereumjs-common": "1.5.0", + "ethereumjs-tx": "2.1.2", + "ethereumjs-util": "6.2.1", + "ethereumjs-vm": "4.2.0", + "ethereumjs-wallet": "0.6.5", + "heap": "0.2.6", + "keccak": "3.0.1", + "level-sublevel": "6.6.4", + "levelup": "3.1.1", + "lodash": "4.17.20", + "lru-cache": "5.1.1", + "merkle-patricia-tree": "3.0.0", + "patch-package": "6.2.2", + "seedrandom": "3.0.1", + "source-map-support": "0.5.12", + "tmp": "0.1.0", + "web3": "1.2.11", + "web3-provider-engine": "14.2.1", + "websocket": "1.0.32" + }, + "dependencies": { + "@ethersproject/abi": { + "version": "5.0.0-beta.153", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.0-beta.153.tgz", + "integrity": "sha512-aXweZ1Z7vMNzJdLpR1CZUAIgnwjrZeUSvN9syCwlBaEBUFJmFY+HHnfuTI5vIhVs/mRkfJVrbEyl51JZQqyjAg==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/address": ">=5.0.0-beta.128", + "@ethersproject/bignumber": ">=5.0.0-beta.130", + "@ethersproject/bytes": ">=5.0.0-beta.129", + "@ethersproject/constants": ">=5.0.0-beta.128", + "@ethersproject/hash": ">=5.0.0-beta.128", + "@ethersproject/keccak256": ">=5.0.0-beta.127", + "@ethersproject/logger": ">=5.0.0-beta.129", + "@ethersproject/properties": ">=5.0.0-beta.131", + "@ethersproject/strings": ">=5.0.0-beta.130" + } + }, + "@ethersproject/abstract-provider": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.8.tgz", + "integrity": "sha512-fqJXkewcGdi8LogKMgRyzc/Ls2js07yor7+g9KfPs09uPOcQLg7cc34JN+lk34HH9gg2HU0DIA5797ZR8znkfw==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/networks": "^5.0.7", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/transactions": "^5.0.9", + "@ethersproject/web": "^5.0.12" + } + }, + "@ethersproject/abstract-signer": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.10.tgz", + "integrity": "sha512-irx7kH7FDAeW7QChDPW19WsxqeB1d3XLyOLSXm0bfPqL1SS07LXWltBJUBUxqC03ORpAOcM3JQj57DU8JnVY2g==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/abstract-provider": "^5.0.8", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7" + } + }, + "@ethersproject/address": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.9.tgz", + "integrity": "sha512-gKkmbZDMyGbVjr8nA5P0md1GgESqSGH7ILIrDidPdNXBl4adqbuA3OAuZx/O2oGpL6PtJ9BDa0kHheZ1ToHU3w==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/rlp": "^5.0.7" + } + }, + "@ethersproject/base64": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.7.tgz", + "integrity": "sha512-S5oh5DVfCo06xwJXT8fQC68mvJfgScTl2AXvbYMsHNfIBTDb084Wx4iA9MNlEReOv6HulkS+gyrUM/j3514rSw==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bytes": "^5.0.9" + } + }, + "@ethersproject/bignumber": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.13.tgz", + "integrity": "sha512-b89bX5li6aK492yuPP5mPgRVgIxxBP7ksaBtKX5QQBsrZTpNOjf/MR4CjcUrAw8g+RQuD6kap9lPjFgY4U1/5A==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "bn.js": "^4.4.0" + } + }, + "@ethersproject/bytes": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", + "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/logger": "^5.0.8" + } + }, + "@ethersproject/constants": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.8.tgz", + "integrity": "sha512-sCc73pFBsl59eDfoQR5OCEZCRv5b0iywadunti6MQIr5lt3XpwxK1Iuzd8XSFO02N9jUifvuZRrt0cY0+NBgTg==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bignumber": "^5.0.13" + } + }, + "@ethersproject/hash": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.10.tgz", + "integrity": "sha512-Tf0bvs6YFhw28LuHnhlDWyr0xfcDxSXdwM4TcskeBbmXVSKLv3bJQEEEBFUcRX0fJuslR3gCVySEaSh7vuMx5w==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/abstract-signer": "^5.0.10", + "@ethersproject/address": "^5.0.9", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/strings": "^5.0.8" + } + }, + "@ethersproject/keccak256": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.7.tgz", + "integrity": "sha512-zpUBmofWvx9PGfc7IICobgFQSgNmTOGTGLUxSYqZzY/T+b4y/2o5eqf/GGmD7qnTGzKQ42YlLNo+LeDP2qe55g==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bytes": "^5.0.9", + "js-sha3": "0.5.7" + } + }, + "@ethersproject/logger": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", + "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==", + "dev": true, + "optional": true + }, + "@ethersproject/networks": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.7.tgz", + "integrity": "sha512-dI14QATndIcUgcCBL1c5vUr/YsI5cCHLN81rF7PU+yS7Xgp2/Rzbr9+YqpC6NBXHFUASjh6GpKqsVMpufAL0BQ==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/logger": "^5.0.8" + } + }, + "@ethersproject/properties": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.7.tgz", + "integrity": "sha512-812H1Rus2vjw0zbasfDI1GLNPDsoyX1pYqiCgaR1BuyKxUTbwcH1B+214l6VGe1v+F6iEVb7WjIwMjKhb4EUsg==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/logger": "^5.0.8" + } + }, + "@ethersproject/rlp": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.7.tgz", + "integrity": "sha512-ulUTVEuV7PT4jJTPpfhRHK57tkLEDEY9XSYJtrSNHOqdwMvH0z7BM2AKIMq4LVDlnu4YZASdKrkFGEIO712V9w==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8" + } + }, + "@ethersproject/signing-key": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.8.tgz", + "integrity": "sha512-YKxQM45eDa6WAD+s3QZPdm1uW1MutzVuyoepdRRVmMJ8qkk7iOiIhUkZwqKLNxKzEJijt/82ycuOREc9WBNAKg==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "elliptic": "6.5.3" + } + }, + "@ethersproject/strings": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.8.tgz", + "integrity": "sha512-5IsdXf8tMY8QuHl8vTLnk9ehXDDm6x9FB9S9Og5IA1GYhLe5ZewydXSjlJlsqU2t9HRbfv97OJZV/pX8DVA/Hw==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/logger": "^5.0.8" + } + }, + "@ethersproject/transactions": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.9.tgz", + "integrity": "sha512-0Fu1yhdFBkrbMjenEr+39tmDxuHmaw0pe9Jb18XuKoItj7Z3p7+UzdHLr2S/okvHDHYPbZE5gtANDdQ3ZL1nBA==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/address": "^5.0.9", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/rlp": "^5.0.7", + "@ethersproject/signing-key": "^5.0.8" + } + }, + "@ethersproject/web": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.12.tgz", + "integrity": "sha512-gVxS5iW0bgidZ76kr7LsTxj4uzN5XpCLzvZrLp8TP+4YgxHfCeetFyQkRPgBEAJdNrexdSBayvyJvzGvOq0O8g==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/base64": "^5.0.7", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/strings": "^5.0.8" + } + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, + "optional": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "optional": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@types/bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.14.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", + "dev": true + }, + "@types/pbkdf2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", + "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/secp256k1": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.1.tgz", + "integrity": "sha512-+ZjSA8ELlOp8SlKi0YLB2tz9d5iPNEmOBd+8Rz21wTMdaXQIa9b6TEnD6l5qKOCypE7FSyPyck12qZJxSDNoog==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "abstract-leveldown": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-3.0.0.tgz", + "integrity": "sha512-KUWx9UWGQD12zsmLNj64/pndaz4iJh/Pj7nopgkfDG6RlCcbMZvT6+9l7dchK4idog2Is8VdC/PvNbFuFmalIQ==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "optional": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "aes-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz", + "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==", + "dev": true, + "optional": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true, + "optional": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "async-eventemitter": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", + "integrity": "sha512-pd20BwL7Yt1zwDFy+8MX8F1+WCT8aQeKj0kQnTrH9WaeRETlRamVhD0JtRPmrV4GfOJ2F9CvdQkZeZhnh2TuHw==", + "dev": true, + "requires": { + "async": "^2.4.0" + } + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + } + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "requires": { + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "requires": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "requires": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "^0.10.0" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-preset-env": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", + "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-to-generator": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.23.0", + "babel-plugin-transform-es2015-classes": "^6.23.0", + "babel-plugin-transform-es2015-computed-properties": "^6.22.0", + "babel-plugin-transform-es2015-destructuring": "^6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", + "babel-plugin-transform-es2015-for-of": "^6.23.0", + "babel-plugin-transform-es2015-function-name": "^6.22.0", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.22.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", + "babel-plugin-transform-es2015-modules-umd": "^6.23.0", + "babel-plugin-transform-es2015-object-super": "^6.22.0", + "babel-plugin-transform-es2015-parameters": "^6.23.0", + "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", + "babel-plugin-transform-exponentiation-operator": "^6.22.0", + "babel-plugin-transform-regenerator": "^6.22.0", + "browserslist": "^3.2.6", + "invariant": "^2.2.2", + "semver": "^5.3.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + } + } + }, + "babelify": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", + "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", + "dev": true, + "requires": { + "babel-core": "^6.0.14", + "object-assign": "^4.0.0" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "backoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", + "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", + "dev": true, + "requires": { + "precond": "0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + } + } + }, + "bignumber.js": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "dev": true, + "optional": true + }, + "bip39": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-2.5.0.tgz", + "integrity": "sha512-xwIx/8JKoT2+IPJpFEfXoWdYwP7UVAoUxxLNfGCfVowaJE7yg1Y5B1BVPqlUNsBq5/nGwmFkwRJ8xDW4sX8OdA==", + "dev": true, + "requires": { + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1", + "safe-buffer": "^5.0.1", + "unorm": "^1.3.3" + } + }, + "blakejs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz", + "integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U=", + "dev": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "optional": true + }, + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "optional": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true, + "optional": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "optional": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "optional": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", + "dev": true, + "optional": true + } + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "browserslist": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000844", + "electron-to-chromium": "^1.3.47" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "dev": true, + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dev": true, + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-to-arraybuffer": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz", + "integrity": "sha1-YGSkD6dutDxyOrqe+PbhIW0QURo=", + "dev": true, + "optional": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "bufferutil": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", + "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", + "dev": true, + "requires": { + "node-gyp-build": "^4.2.0" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true, + "optional": true + }, + "bytewise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", + "integrity": "sha1-HRPL/3F65xWAlKqIGzXQgbOHJT4=", + "dev": true, + "requires": { + "bytewise-core": "^1.2.2", + "typewise": "^1.0.3" + } + }, + "bytewise-core": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", + "integrity": "sha1-P7QQx+kVWOsasiqCg0V3qmvWHUI=", + "dev": true, + "requires": { + "typewise-core": "^1.2" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "optional": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "optional": true + } + } + }, + "cachedown": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cachedown/-/cachedown-1.0.0.tgz", + "integrity": "sha1-1D8DbkUQaWsxJG19sx6/D3rDLRU=", + "dev": true, + "requires": { + "abstract-leveldown": "^2.4.1", + "lru-cache": "^3.2.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + }, + "lru-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", + "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", + "dev": true, + "requires": { + "pseudomap": "^1.0.1" + } + } + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caniuse-lite": { + "version": "1.0.30001174", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001174.tgz", + "integrity": "sha512-tqClL/4ThQq6cfFXH3oJL4rifFBeM6gTkphjao5kgwMaW9yn0tKgQLAEfKzDwj6HQWCB/aWo8kTFlSvIN8geEA==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "checkpoint-store": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz", + "integrity": "sha1-BOTLUWuRQziTWB5tRgGnjpVS6gY=", + "dev": true, + "requires": { + "functional-red-black-tree": "^1.0.1" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "optional": true + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cids": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/cids/-/cids-0.7.5.tgz", + "integrity": "sha512-zT7mPeghoWAu+ppn8+BS1tQ5qGmbMfB4AregnQjA/qHY3GC1m1ptI9GkWNlgeu38r7CuRdXB47uY2XgAYt6QVA==", + "dev": true, + "optional": true, + "requires": { + "buffer": "^5.5.0", + "class-is": "^1.1.0", + "multibase": "~0.6.0", + "multicodec": "^1.0.0", + "multihashes": "~0.4.15" + }, + "dependencies": { + "multicodec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-1.0.4.tgz", + "integrity": "sha512-NDd7FeS3QamVtbgfvu5h7fd1IlbaC4EQ0/pgU4zqE2vdHCmBGsUa0TiM8/TdSeG6BMPC92OOCf8F1ocE/Wkrrg==", + "dev": true, + "optional": true, + "requires": { + "buffer": "^5.6.0", + "varint": "^5.0.0" + } + } + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-is": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz", + "integrity": "sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw==", + "dev": true, + "optional": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "optional": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true + } + } + }, + "content-hash": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/content-hash/-/content-hash-2.5.2.tgz", + "integrity": "sha512-FvIQKy0S1JaWV10sMsA7TRx8bpU+pqPkhbsfvOJAdjRXvYxEckAwQWGwtRjiaJfh+E0DvcWUGqcdjwMGFjsSdw==", + "dev": true, + "optional": true, + "requires": { + "cids": "^0.7.1", + "multicodec": "^0.5.5", + "multihashes": "^0.4.15" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true, + "optional": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true, + "optional": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true, + "optional": true + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true, + "optional": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "dev": true + }, + "core-js-pure": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.2.tgz", + "integrity": "sha512-v6zfIQqL/pzTVAbZvYUozsxNfxcFb6Ks3ZfEbuneJl3FW9Jb8F6vLWB6f+qTmAu72msUdyb84V8d/yBFf7FNnw==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "optional": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-fetch": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.3.tgz", + "integrity": "sha512-PrWWNH3yL2NYIb/7WF/5vFG3DCQiXDOVf8k3ijatbrtnwNuhMWLC7YF7uqf53tbTFDzHIUD8oITw4Bxt8ST3Nw==", + "dev": true, + "requires": { + "node-fetch": "2.1.2", + "whatwg-fetch": "2.0.4" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "optional": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "optional": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true, + "optional": true + }, + "deferred-leveldown": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-4.0.2.tgz", + "integrity": "sha512-5fMC8ek8alH16QiV0lTCis610D1Zt1+LA4MS4d63JgS32lrCjTFDUFz2ao09/j2I4Bqb5jL4FZYwu7Jz0XO1ww==", + "dev": true, + "requires": { + "abstract-leveldown": "~5.0.0", + "inherits": "^2.0.3" + }, + "dependencies": { + "abstract-leveldown": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", + "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + } + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true, + "optional": true + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "optional": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true, + "optional": true + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "dev": true + }, + "dotignore": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", + "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true, + "optional": true + }, + "electron-to-chromium": { + "version": "1.3.636", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.636.tgz", + "integrity": "sha512-Adcvng33sd3gTjNIDNXGD1G4H6qCImIy2euUJAQHtLNplEKU5WEz5KRJxupRNIIT8sD5oFZLTKBWAf12Bsz24A==", + "dev": true + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "optional": true + }, + "encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "requires": { + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } + }, + "encoding-down": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-5.0.4.tgz", + "integrity": "sha512-8CIZLDcSKxgzT+zX8ZVfgNbu8Md2wq/iqa1Y7zyVR18QBEAc0Nmzuvj/N5ykSKpfGzjM8qxbaFntLPwnVoUhZw==", + "dev": true, + "requires": { + "abstract-leveldown": "^5.0.0", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0", + "xtend": "^4.0.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", + "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + } + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true, + "optional": true + }, + "eth-block-tracker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-3.0.1.tgz", + "integrity": "sha512-WUVxWLuhMmsfenfZvFO5sbl1qFY2IqUlw/FPVmjjdElpqLsZtSG+wPe9Dz7W/sB6e80HgFKknOmKk2eNlznHug==", + "dev": true, + "requires": { + "eth-query": "^2.1.0", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.3", + "ethjs-util": "^0.1.3", + "json-rpc-engine": "^3.6.0", + "pify": "^2.3.0", + "tape": "^4.6.3" + }, + "dependencies": { + "ethereumjs-tx": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz", + "integrity": "sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==", + "dev": true, + "requires": { + "ethereum-common": "^0.0.18", + "ethereumjs-util": "^5.0.0" + } + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "eth-ens-namehash": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz", + "integrity": "sha1-IprEbsqG1S4MmR58sq74P/D2i88=", + "dev": true, + "optional": true, + "requires": { + "idna-uts46-hx": "^2.3.1", + "js-sha3": "^0.5.7" + } + }, + "eth-json-rpc-infura": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/eth-json-rpc-infura/-/eth-json-rpc-infura-3.2.1.tgz", + "integrity": "sha512-W7zR4DZvyTn23Bxc0EWsq4XGDdD63+XPUCEhV2zQvQGavDVC4ZpFDK4k99qN7bd7/fjj37+rxmuBOBeIqCA5Mw==", + "dev": true, + "requires": { + "cross-fetch": "^2.1.1", + "eth-json-rpc-middleware": "^1.5.0", + "json-rpc-engine": "^3.4.0", + "json-rpc-error": "^2.0.0" + } + }, + "eth-json-rpc-middleware": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", + "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "dev": true, + "requires": { + "async": "^2.5.0", + "eth-query": "^2.1.2", + "eth-tx-summary": "^3.1.2", + "ethereumjs-block": "^1.6.0", + "ethereumjs-tx": "^1.3.3", + "ethereumjs-util": "^5.1.2", + "ethereumjs-vm": "^2.1.0", + "fetch-ponyfill": "^4.0.0", + "json-rpc-engine": "^3.6.0", + "json-rpc-error": "^2.0.0", + "json-stable-stringify": "^1.0.1", + "promise-to-callback": "^1.0.0", + "tape": "^4.6.3" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", + "integrity": "sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + }, + "deferred-leveldown": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", + "integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==", + "dev": true, + "requires": { + "abstract-leveldown": "~2.6.0" + } + }, + "ethereumjs-account": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-2.0.5.tgz", + "integrity": "sha512-bgDojnXGjhMwo6eXQC0bY6UK2liSFUSMwwylOmQvZbSl/D7NXQ3+vrGO46ZeOgjGfxXmgIeVNDIiHw7fNZM4VA==", + "dev": true, + "requires": { + "ethereumjs-util": "^5.0.0", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "ethereumjs-block": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz", + "integrity": "sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg==", + "dev": true, + "requires": { + "async": "^2.0.1", + "ethereum-common": "0.2.0", + "ethereumjs-tx": "^1.2.2", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "dependencies": { + "ethereum-common": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz", + "integrity": "sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA==", + "dev": true + } + } + }, + "ethereumjs-tx": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz", + "integrity": "sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==", + "dev": true, + "requires": { + "ethereum-common": "^0.0.18", + "ethereumjs-util": "^5.0.0" + } + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "ethereumjs-vm": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz", + "integrity": "sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw==", + "dev": true, + "requires": { + "async": "^2.1.2", + "async-eventemitter": "^0.2.2", + "ethereumjs-account": "^2.0.3", + "ethereumjs-block": "~2.2.0", + "ethereumjs-common": "^1.1.0", + "ethereumjs-util": "^6.0.0", + "fake-merkle-patricia-tree": "^1.0.1", + "functional-red-black-tree": "^1.0.1", + "merkle-patricia-tree": "^2.3.2", + "rustbn.js": "~0.2.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "ethereumjs-block": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz", + "integrity": "sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg==", + "dev": true, + "requires": { + "async": "^2.0.1", + "ethereumjs-common": "^1.5.0", + "ethereumjs-tx": "^2.1.1", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "dependencies": { + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethereumjs-tx": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz", + "integrity": "sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw==", + "dev": true, + "requires": { + "ethereumjs-common": "^1.5.0", + "ethereumjs-util": "^6.0.0" + } + }, + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "level-codec": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz", + "integrity": "sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ==", + "dev": true + }, + "level-errors": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-1.0.5.tgz", + "integrity": "sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig==", + "dev": true, + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz", + "integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "level-errors": "^1.0.3", + "readable-stream": "^1.0.33", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "level-ws": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-0.0.0.tgz", + "integrity": "sha1-Ny5RIXeSSgBCSwtDrvK7QkltIos=", + "dev": true, + "requires": { + "readable-stream": "~1.0.15", + "xtend": "~2.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "levelup": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", + "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", + "dev": true, + "requires": { + "deferred-leveldown": "~1.2.1", + "level-codec": "~7.0.0", + "level-errors": "~1.0.3", + "level-iterator-stream": "~1.3.0", + "prr": "~1.0.1", + "semver": "~5.4.1", + "xtend": "~4.0.0" + } + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=", + "dev": true + }, + "memdown": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", + "integrity": "sha1-tOThkhdGZP+65BNhqlAPMRnv4hU=", + "dev": true, + "requires": { + "abstract-leveldown": "~2.7.1", + "functional-red-black-tree": "^1.0.1", + "immediate": "^3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + } + } + }, + "merkle-patricia-tree": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz", + "integrity": "sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==", + "dev": true, + "requires": { + "async": "^1.4.2", + "ethereumjs-util": "^5.0.0", + "level-ws": "0.0.0", + "levelup": "^1.2.1", + "memdown": "^1.0.0", + "readable-stream": "^2.0.0", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "eth-lib": { + "version": "0.1.29", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.1.29.tgz", + "integrity": "sha512-bfttrr3/7gG4E02HoWTDUcDDslN003OlOoBxk9virpAZQ1ja/jDgwkWB8QfJF7ojuEowrqy+lzp9VcJG7/k5bQ==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "nano-json-stream-parser": "^0.1.2", + "servify": "^0.1.12", + "ws": "^3.0.0", + "xhr-request-promise": "^0.1.2" + } + }, + "eth-query": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/eth-query/-/eth-query-2.1.2.tgz", + "integrity": "sha1-1nQdkAAQa1FRDHLbktY2VFam2l4=", + "dev": true, + "requires": { + "json-rpc-random-id": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "eth-sig-util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-3.0.0.tgz", + "integrity": "sha512-4eFkMOhpGbTxBQ3AMzVf0haUX2uTur7DpWiHzWyTURa28BVJJtOkcb9Ok5TV0YvEPG61DODPW7ZUATbJTslioQ==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "elliptic": "^6.4.0", + "ethereumjs-abi": "0.6.5", + "ethereumjs-util": "^5.1.1", + "tweetnacl": "^1.0.0", + "tweetnacl-util": "^0.15.0" + }, + "dependencies": { + "ethereumjs-abi": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz", + "integrity": "sha1-WmN+8Wq0NHP6cqKa2QhxQFs/UkE=", + "dev": true, + "requires": { + "bn.js": "^4.10.0", + "ethereumjs-util": "^4.3.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz", + "integrity": "sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w==", + "dev": true, + "requires": { + "bn.js": "^4.8.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "rlp": "^2.0.0" + } + } + } + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "eth-tx-summary": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/eth-tx-summary/-/eth-tx-summary-3.2.4.tgz", + "integrity": "sha512-NtlDnaVZah146Rm8HMRUNMgIwG/ED4jiqk0TME9zFheMl1jOp6jL1m0NKGjJwehXQ6ZKCPr16MTr+qspKpEXNg==", + "dev": true, + "requires": { + "async": "^2.1.2", + "clone": "^2.0.0", + "concat-stream": "^1.5.1", + "end-of-stream": "^1.1.0", + "eth-query": "^2.0.2", + "ethereumjs-block": "^1.4.1", + "ethereumjs-tx": "^1.1.1", + "ethereumjs-util": "^5.0.1", + "ethereumjs-vm": "^2.6.0", + "through2": "^2.0.3" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", + "integrity": "sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + }, + "deferred-leveldown": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", + "integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==", + "dev": true, + "requires": { + "abstract-leveldown": "~2.6.0" + } + }, + "ethereumjs-account": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-2.0.5.tgz", + "integrity": "sha512-bgDojnXGjhMwo6eXQC0bY6UK2liSFUSMwwylOmQvZbSl/D7NXQ3+vrGO46ZeOgjGfxXmgIeVNDIiHw7fNZM4VA==", + "dev": true, + "requires": { + "ethereumjs-util": "^5.0.0", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "ethereumjs-block": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz", + "integrity": "sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg==", + "dev": true, + "requires": { + "async": "^2.0.1", + "ethereum-common": "0.2.0", + "ethereumjs-tx": "^1.2.2", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "dependencies": { + "ethereum-common": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz", + "integrity": "sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA==", + "dev": true + } + } + }, + "ethereumjs-tx": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz", + "integrity": "sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==", + "dev": true, + "requires": { + "ethereum-common": "^0.0.18", + "ethereumjs-util": "^5.0.0" + } + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "ethereumjs-vm": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz", + "integrity": "sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw==", + "dev": true, + "requires": { + "async": "^2.1.2", + "async-eventemitter": "^0.2.2", + "ethereumjs-account": "^2.0.3", + "ethereumjs-block": "~2.2.0", + "ethereumjs-common": "^1.1.0", + "ethereumjs-util": "^6.0.0", + "fake-merkle-patricia-tree": "^1.0.1", + "functional-red-black-tree": "^1.0.1", + "merkle-patricia-tree": "^2.3.2", + "rustbn.js": "~0.2.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "ethereumjs-block": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz", + "integrity": "sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg==", + "dev": true, + "requires": { + "async": "^2.0.1", + "ethereumjs-common": "^1.5.0", + "ethereumjs-tx": "^2.1.1", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "dependencies": { + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethereumjs-tx": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz", + "integrity": "sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw==", + "dev": true, + "requires": { + "ethereumjs-common": "^1.5.0", + "ethereumjs-util": "^6.0.0" + } + }, + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "level-codec": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz", + "integrity": "sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ==", + "dev": true + }, + "level-errors": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-1.0.5.tgz", + "integrity": "sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig==", + "dev": true, + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz", + "integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "level-errors": "^1.0.3", + "readable-stream": "^1.0.33", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "level-ws": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-0.0.0.tgz", + "integrity": "sha1-Ny5RIXeSSgBCSwtDrvK7QkltIos=", + "dev": true, + "requires": { + "readable-stream": "~1.0.15", + "xtend": "~2.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "levelup": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", + "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", + "dev": true, + "requires": { + "deferred-leveldown": "~1.2.1", + "level-codec": "~7.0.0", + "level-errors": "~1.0.3", + "level-iterator-stream": "~1.3.0", + "prr": "~1.0.1", + "semver": "~5.4.1", + "xtend": "~4.0.0" + } + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=", + "dev": true + }, + "memdown": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", + "integrity": "sha1-tOThkhdGZP+65BNhqlAPMRnv4hU=", + "dev": true, + "requires": { + "abstract-leveldown": "~2.7.1", + "functional-red-black-tree": "^1.0.1", + "immediate": "^3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + } + } + }, + "merkle-patricia-tree": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz", + "integrity": "sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==", + "dev": true, + "requires": { + "async": "^1.4.2", + "ethereumjs-util": "^5.0.0", + "level-ws": "0.0.0", + "levelup": "^1.2.1", + "memdown": "^1.0.0", + "readable-stream": "^2.0.0", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "ethashjs": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ethashjs/-/ethashjs-0.0.8.tgz", + "integrity": "sha512-/MSbf/r2/Ld8o0l15AymjOTlPqpN8Cr4ByUEA9GtR4x0yAh3TdtDzEg29zMjXCNPI7u6E5fOQdj/Cf9Tc7oVNw==", + "dev": true, + "requires": { + "async": "^2.1.2", + "buffer-xor": "^2.0.1", + "ethereumjs-util": "^7.0.2", + "miller-rabin": "^4.0.0" + }, + "dependencies": { + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", + "dev": true + }, + "buffer-xor": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-2.0.2.tgz", + "integrity": "sha512-eHslX0bin3GB+Lx2p7lEYRShRewuNZL3fUl4qlVJGGiwoPGftmt8JQgk2Y9Ji5/01TnVDo33E5b5O3vUB1HdqQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "ethereumjs-util": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.7.tgz", + "integrity": "sha512-vU5rtZBlZsgkTw3o6PDKyB8li2EgLavnAbsKcfsH2YhHH1Le+PP8vEiMnAnvgc1B6uMoaM5GDCrVztBw0Q5K9g==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^5.1.2", + "create-hash": "^1.1.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.4" + } + } + } + }, + "ethereum-bloom-filters": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.7.tgz", + "integrity": "sha512-cDcJJSJ9GMAcURiAWO3DxIEhTL/uWqlQnvgKpuYQzYPrt/izuGU+1ntQmHt0IRq6ADoSYHFnB+aCEFIldjhkMQ==", + "dev": true, + "optional": true, + "requires": { + "js-sha3": "^0.8.0" + }, + "dependencies": { + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true, + "optional": true + } + } + }, + "ethereum-common": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz", + "integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=", + "dev": true + }, + "ethereum-cryptography": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", + "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", + "dev": true, + "requires": { + "@types/pbkdf2": "^3.0.0", + "@types/secp256k1": "^4.0.1", + "blakejs": "^1.1.0", + "browserify-aes": "^1.2.0", + "bs58check": "^2.1.2", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "hash.js": "^1.1.7", + "keccak": "^3.0.0", + "pbkdf2": "^3.0.17", + "randombytes": "^2.1.0", + "safe-buffer": "^5.1.2", + "scrypt-js": "^3.0.0", + "secp256k1": "^4.0.1", + "setimmediate": "^1.0.5" + } + }, + "ethereumjs-abi": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", + "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", + "dev": true, + "requires": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + } + }, + "ethereumjs-account": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-3.0.0.tgz", + "integrity": "sha512-WP6BdscjiiPkQfF9PVfMcwx/rDvfZTjFKY0Uwc09zSQr9JfIVH87dYIJu0gNhBhpmovV4yq295fdllS925fnBA==", + "dev": true, + "requires": { + "ethereumjs-util": "^6.0.0", + "rlp": "^2.2.1", + "safe-buffer": "^5.1.1" + } + }, + "ethereumjs-block": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz", + "integrity": "sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg==", + "dev": true, + "requires": { + "async": "^2.0.1", + "ethereumjs-common": "^1.5.0", + "ethereumjs-tx": "^2.1.1", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", + "integrity": "sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + }, + "deferred-leveldown": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", + "integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==", + "dev": true, + "requires": { + "abstract-leveldown": "~2.6.0" + } + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "level-codec": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz", + "integrity": "sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ==", + "dev": true + }, + "level-errors": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-1.0.5.tgz", + "integrity": "sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig==", + "dev": true, + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz", + "integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "level-errors": "^1.0.3", + "readable-stream": "^1.0.33", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "level-ws": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-0.0.0.tgz", + "integrity": "sha1-Ny5RIXeSSgBCSwtDrvK7QkltIos=", + "dev": true, + "requires": { + "readable-stream": "~1.0.15", + "xtend": "~2.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "levelup": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", + "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", + "dev": true, + "requires": { + "deferred-leveldown": "~1.2.1", + "level-codec": "~7.0.0", + "level-errors": "~1.0.3", + "level-iterator-stream": "~1.3.0", + "prr": "~1.0.1", + "semver": "~5.4.1", + "xtend": "~4.0.0" + } + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=", + "dev": true + }, + "memdown": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", + "integrity": "sha1-tOThkhdGZP+65BNhqlAPMRnv4hU=", + "dev": true, + "requires": { + "abstract-leveldown": "~2.7.1", + "functional-red-black-tree": "^1.0.1", + "immediate": "^3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + } + } + }, + "merkle-patricia-tree": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz", + "integrity": "sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==", + "dev": true, + "requires": { + "async": "^1.4.2", + "ethereumjs-util": "^5.0.0", + "level-ws": "0.0.0", + "levelup": "^1.2.1", + "memdown": "^1.0.0", + "readable-stream": "^2.0.0", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "ethereumjs-blockchain": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/ethereumjs-blockchain/-/ethereumjs-blockchain-4.0.4.tgz", + "integrity": "sha512-zCxaRMUOzzjvX78DTGiKjA+4h2/sF0OYL1QuPux0DHpyq8XiNoF5GYHtb++GUxVlMsMfZV7AVyzbtgcRdIcEPQ==", + "dev": true, + "requires": { + "async": "^2.6.1", + "ethashjs": "~0.0.7", + "ethereumjs-block": "~2.2.2", + "ethereumjs-common": "^1.5.0", + "ethereumjs-util": "^6.1.0", + "flow-stoplight": "^1.0.0", + "level-mem": "^3.0.1", + "lru-cache": "^5.1.1", + "rlp": "^2.2.2", + "semaphore": "^1.1.0" + } + }, + "ethereumjs-common": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/ethereumjs-common/-/ethereumjs-common-1.5.0.tgz", + "integrity": "sha512-SZOjgK1356hIY7MRj3/ma5qtfr/4B5BL+G4rP/XSMYr2z1H5el4RX5GReYCKmQmYI/nSBmRnwrZ17IfHuG0viQ==", + "dev": true + }, + "ethereumjs-tx": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz", + "integrity": "sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw==", + "dev": true, + "requires": { + "ethereumjs-common": "^1.5.0", + "ethereumjs-util": "^6.0.0" + } + }, + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + }, + "ethereumjs-vm": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-4.2.0.tgz", + "integrity": "sha512-X6qqZbsY33p5FTuZqCnQ4+lo957iUJMM6Mpa6bL4UW0dxM6WmDSHuI4j/zOp1E2TDKImBGCJA9QPfc08PaNubA==", + "dev": true, + "requires": { + "async": "^2.1.2", + "async-eventemitter": "^0.2.2", + "core-js-pure": "^3.0.1", + "ethereumjs-account": "^3.0.0", + "ethereumjs-block": "^2.2.2", + "ethereumjs-blockchain": "^4.0.3", + "ethereumjs-common": "^1.5.0", + "ethereumjs-tx": "^2.1.2", + "ethereumjs-util": "^6.2.0", + "fake-merkle-patricia-tree": "^1.0.1", + "functional-red-black-tree": "^1.0.1", + "merkle-patricia-tree": "^2.3.2", + "rustbn.js": "~0.2.0", + "safe-buffer": "^5.1.1", + "util.promisify": "^1.0.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", + "integrity": "sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + }, + "deferred-leveldown": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", + "integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==", + "dev": true, + "requires": { + "abstract-leveldown": "~2.6.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "level-codec": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz", + "integrity": "sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ==", + "dev": true + }, + "level-errors": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-1.0.5.tgz", + "integrity": "sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig==", + "dev": true, + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz", + "integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "level-errors": "^1.0.3", + "readable-stream": "^1.0.33", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "level-ws": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-0.0.0.tgz", + "integrity": "sha1-Ny5RIXeSSgBCSwtDrvK7QkltIos=", + "dev": true, + "requires": { + "readable-stream": "~1.0.15", + "xtend": "~2.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "levelup": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", + "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", + "dev": true, + "requires": { + "deferred-leveldown": "~1.2.1", + "level-codec": "~7.0.0", + "level-errors": "~1.0.3", + "level-iterator-stream": "~1.3.0", + "prr": "~1.0.1", + "semver": "~5.4.1", + "xtend": "~4.0.0" + } + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=", + "dev": true + }, + "memdown": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", + "integrity": "sha1-tOThkhdGZP+65BNhqlAPMRnv4hU=", + "dev": true, + "requires": { + "abstract-leveldown": "~2.7.1", + "functional-red-black-tree": "^1.0.1", + "immediate": "^3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + } + } + }, + "merkle-patricia-tree": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz", + "integrity": "sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==", + "dev": true, + "requires": { + "async": "^1.4.2", + "ethereumjs-util": "^5.0.0", + "level-ws": "0.0.0", + "levelup": "^1.2.1", + "memdown": "^1.0.0", + "readable-stream": "^2.0.0", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "ethereumjs-wallet": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/ethereumjs-wallet/-/ethereumjs-wallet-0.6.5.tgz", + "integrity": "sha512-MDwjwB9VQVnpp/Dc1XzA6J1a3wgHQ4hSvA1uWNatdpOrtCbPVuQSKSyRnjLvS0a+KKMw2pvQ9Ybqpb3+eW8oNA==", + "dev": true, + "optional": true, + "requires": { + "aes-js": "^3.1.1", + "bs58check": "^2.1.2", + "ethereum-cryptography": "^0.1.3", + "ethereumjs-util": "^6.0.0", + "randombytes": "^2.0.6", + "safe-buffer": "^5.1.2", + "scryptsy": "^1.2.1", + "utf8": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "ethjs-unit": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", + "integrity": "sha1-xmWSHkduh7ziqdWIpv4EBbLEFpk=", + "dev": true, + "optional": true, + "requires": { + "bn.js": "4.11.6", + "number-to-bn": "1.7.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", + "dev": true, + "optional": true + } + } + }, + "ethjs-util": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", + "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", + "dev": true, + "requires": { + "is-hex-prefixed": "1.0.0", + "strip-hex-prefix": "1.0.0" + } + }, + "eventemitter3": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", + "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "dev": true, + "optional": true + }, + "events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "optional": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true, + "optional": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true + } + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fake-merkle-patricia-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz", + "integrity": "sha1-S4w6z7Ugr635hgsfFM2M40As3dM=", + "dev": true, + "requires": { + "checkpoint-store": "^1.1.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fetch-ponyfill": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz", + "integrity": "sha1-rjzl9zLGReq4fkroeTQUcJsjmJM=", + "dev": true, + "requires": { + "node-fetch": "~1.7.1" + }, + "dependencies": { + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + } + } + }, + "find-yarn-workspace-root": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-1.2.1.tgz", + "integrity": "sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q==", + "dev": true, + "requires": { + "fs-extra": "^4.0.3", + "micromatch": "^3.1.4" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "flow-stoplight": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flow-stoplight/-/flow-stoplight-1.0.0.tgz", + "integrity": "sha1-SiksW8/4s5+mzAyxqFPYbyfu/3s=", + "dev": true + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true, + "optional": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "optional": true + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-intrinsic": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", + "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "optional": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dev": true, + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "optional": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "dependencies": { + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "optional": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "dev": true, + "optional": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "dev": true, + "optional": true, + "requires": { + "has-symbol-support-x": "^1.4.1" + } + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "heap": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", + "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true, + "optional": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "optional": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true, + "optional": true + } + } + }, + "http-https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/http-https/-/http-https-1.0.0.tgz", + "integrity": "sha1-L5CN1fHbQGjAWM1ubUzjkskTOJs=", + "dev": true, + "optional": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "idna-uts46-hx": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", + "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", + "dev": true, + "optional": true, + "requires": { + "punycode": "2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "dev": true, + "optional": true + } + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "immediate": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", + "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "optional": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-arguments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", + "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true + }, + "is-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fn/-/is-fn-1.0.0.tgz", + "integrity": "sha1-lUPV3nvPWwiiLsiiC65uKG1RDYw=", + "dev": true + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true + }, + "is-hex-prefixed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "optional": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "optional": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true, + "optional": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "dev": true, + "optional": true, + "requires": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + } + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=", + "dev": true, + "optional": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true, + "optional": true + }, + "json-rpc-engine": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-3.8.0.tgz", + "integrity": "sha512-6QNcvm2gFuuK4TKU1uwfH0Qd/cOSb9c1lls0gbnIhciktIUQJwz6NQNAW4B1KiGPenv7IKu97V222Yo1bNhGuA==", + "dev": true, + "requires": { + "async": "^2.0.1", + "babel-preset-env": "^1.7.0", + "babelify": "^7.3.0", + "json-rpc-error": "^2.0.0", + "promise-to-callback": "^1.0.0", + "safe-event-emitter": "^1.0.1" + } + }, + "json-rpc-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-rpc-error/-/json-rpc-error-2.0.0.tgz", + "integrity": "sha1-p6+cICg4tekFxyUOVH8a/3cligI=", + "dev": true, + "requires": { + "inherits": "^2.0.1" + } + }, + "json-rpc-random-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz", + "integrity": "sha1-uknZat7RRE27jaPSA3SKy7zeyMg=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "keccak": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "optional": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11" + } + }, + "level-codec": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", + "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "dev": true, + "requires": { + "buffer": "^5.6.0" + } + }, + "level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "dev": true, + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-2.0.3.tgz", + "integrity": "sha512-I6Heg70nfF+e5Y3/qfthJFexhRw/Gi3bIymCoXAlijZdAcLaPuWSJs3KXyTYf23ID6g0o2QF62Yh+grOXY3Rig==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.5", + "xtend": "^4.0.0" + } + }, + "level-mem": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/level-mem/-/level-mem-3.0.1.tgz", + "integrity": "sha512-LbtfK9+3Ug1UmvvhR2DqLqXiPW1OJ5jEh0a3m9ZgAipiwpSxGj/qaVVy54RG5vAQN1nCuXqjvprCuKSCxcJHBg==", + "dev": true, + "requires": { + "level-packager": "~4.0.0", + "memdown": "~3.0.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", + "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=", + "dev": true + }, + "memdown": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-3.0.0.tgz", + "integrity": "sha512-tbV02LfZMWLcHcq4tw++NuqMO+FZX8tNJEiD2aNRm48ZZusVg5N8NART+dmBkepJVye986oixErf7jfXboMGMA==", + "dev": true, + "requires": { + "abstract-leveldown": "~5.0.0", + "functional-red-black-tree": "~1.0.1", + "immediate": "~3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "level-packager": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-4.0.1.tgz", + "integrity": "sha512-svCRKfYLn9/4CoFfi+d8krOtrp6RoX8+xm0Na5cgXMqSyRru0AnDYdLl+YI8u1FyS6gGZ94ILLZDE5dh2but3Q==", + "dev": true, + "requires": { + "encoding-down": "~5.0.0", + "levelup": "^3.0.0" + } + }, + "level-post": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/level-post/-/level-post-1.0.7.tgz", + "integrity": "sha512-PWYqG4Q00asOrLhX7BejSajByB4EmG2GaKHfj3h5UmmZ2duciXLPGYWIjBzLECFWUGOZWlm5B20h/n3Gs3HKew==", + "dev": true, + "requires": { + "ltgt": "^2.1.2" + } + }, + "level-sublevel": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/level-sublevel/-/level-sublevel-6.6.4.tgz", + "integrity": "sha512-pcCrTUOiO48+Kp6F1+UAzF/OtWqLcQVTVF39HLdZ3RO8XBoXt+XVPKZO1vVr1aUoxHZA9OtD2e1v7G+3S5KFDA==", + "dev": true, + "requires": { + "bytewise": "~1.1.0", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0", + "level-iterator-stream": "^2.0.3", + "ltgt": "~2.1.1", + "pull-defer": "^0.2.2", + "pull-level": "^2.0.3", + "pull-stream": "^3.6.8", + "typewiselite": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "level-ws": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-1.0.0.tgz", + "integrity": "sha512-RXEfCmkd6WWFlArh3X8ONvQPm8jNpfA0s/36M4QzLqrLEIt1iJE9WBHLZ5vZJK6haMjJPJGJCQWfjMNnRcq/9Q==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.2.8", + "xtend": "^4.0.1" + } + }, + "levelup": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-3.1.1.tgz", + "integrity": "sha512-9N10xRkUU4dShSRRFTBdNaBxofz+PGaIZO962ckboJZiNmLuhVT6FZ6ZKAsICKfUBO76ySaYU6fJWX/jnj3Lcg==", + "dev": true, + "requires": { + "deferred-leveldown": "~4.0.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~3.0.0", + "xtend": "~4.0.0" + }, + "dependencies": { + "level-iterator-stream": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-3.0.1.tgz", + "integrity": "sha512-nEIQvxEED9yRThxvOrq8Aqziy4EGzrxSZK+QzEFAVuJvQ8glfyZ96GB6BoI4sBbLfjMXm2w4vu3Tkcm9obcY0g==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "xtend": "^4.0.0" + } + } + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "looper": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/looper/-/looper-2.0.0.tgz", + "integrity": "sha1-Zs0Md0rz1P7axTeU90LbVtqPCew=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "optional": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "ltgt": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.1.3.tgz", + "integrity": "sha1-EIUaBtmWS5cReEQcI8nlJpjuzjQ=", + "dev": true + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "optional": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true, + "optional": true + }, + "merkle-patricia-tree": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-3.0.0.tgz", + "integrity": "sha512-soRaMuNf/ILmw3KWbybaCjhx86EYeBbD8ph0edQCTed0JN/rxDt1EBN52Ajre3VyGo+91f8+/rfPIRQnnGMqmQ==", + "dev": true, + "requires": { + "async": "^2.6.1", + "ethereumjs-util": "^5.2.0", + "level-mem": "^3.0.1", + "level-ws": "^1.0.0", + "readable-stream": "^3.0.6", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" + }, + "dependencies": { + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true, + "optional": true + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true + }, + "mime-db": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", + "dev": true + }, + "mime-types": { + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "dev": true, + "requires": { + "mime-db": "1.45.0" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "optional": true + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "dev": true, + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mkdirp-promise": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz", + "integrity": "sha1-6bj2jlUsaKnBcTuEiD96HdA5uKE=", + "dev": true, + "optional": true, + "requires": { + "mkdirp": "*" + } + }, + "mock-fs": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.13.0.tgz", + "integrity": "sha512-DD0vOdofJdoaRNtnWcrXe6RQbpHkPPmtqGq14uRX0F8ZKJ5nv89CVTYl/BZdppDxBDaV0hl75htg3abpEWlPZA==", + "dev": true, + "optional": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "multibase": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.6.1.tgz", + "integrity": "sha512-pFfAwyTjbbQgNc3G7D48JkJxWtoJoBMaR4xQUOuB8RnCgRqaYmWNFeJTTvrJ2w51bjLq2zTby6Rqj9TQ9elSUw==", + "dev": true, + "optional": true, + "requires": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + }, + "multicodec": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-0.5.7.tgz", + "integrity": "sha512-PscoRxm3f+88fAtELwUnZxGDkduE2HD9Q6GHUOywQLjOGT/HAdhjLDYNZ1e7VR0s0TP0EwZ16LNUTFpoBGivOA==", + "dev": true, + "optional": true, + "requires": { + "varint": "^5.0.0" + } + }, + "multihashes": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-0.4.21.tgz", + "integrity": "sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw==", + "dev": true, + "optional": true, + "requires": { + "buffer": "^5.5.0", + "multibase": "^0.7.0", + "varint": "^5.0.0" + }, + "dependencies": { + "multibase": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-0.7.0.tgz", + "integrity": "sha512-TW8q03O0f6PNFTQDvh3xxH03c8CjGaaYrjkl9UQPG6rz53TQzzxJVCIWVjzcbN/Q5Y53Zd0IBQBMVktVgNx4Fg==", + "dev": true, + "optional": true, + "requires": { + "base-x": "^3.0.8", + "buffer": "^5.5.0" + } + } + } + }, + "nano-json-stream-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz", + "integrity": "sha1-DMj20OK2IrR5xA1JnEbWS3Vcb18=", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true, + "optional": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-addon-api": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=", + "dev": true + }, + "node-gyp-build": { + "version": "4.2.3", + "bundled": true, + "dev": true + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true, + "optional": true + }, + "number-to-bn": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", + "integrity": "sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA=", + "dev": true, + "optional": true, + "requires": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", + "dev": true, + "optional": true + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object-is": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", + "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz", + "integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "oboe": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.4.tgz", + "integrity": "sha1-IMiM2wwVNxuwQRklfU/dNLCqSfY=", + "dev": true, + "optional": true, + "requires": { + "http-https": "^1.0.0" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "optional": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "optional": true + }, + "p-timeout": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", + "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", + "dev": true, + "optional": true, + "requires": { + "p-finally": "^1.0.0" + }, + "dependencies": { + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "optional": true + } + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "optional": true, + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-headers": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz", + "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA==", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "optional": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "patch-package": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.2.2.tgz", + "integrity": "sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg==", + "dev": true, + "requires": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "find-yarn-workspace-root": "^1.2.1", + "fs-extra": "^7.0.1", + "is-ci": "^2.0.0", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.0", + "rimraf": "^2.6.3", + "semver": "^5.6.0", + "slash": "^2.0.0", + "tmp": "^0.0.33" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true, + "optional": true + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "precond": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", + "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=", + "dev": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "optional": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "promise-to-callback": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/promise-to-callback/-/promise-to-callback-1.0.0.tgz", + "integrity": "sha1-XSp0kBC/tn2WNZj805YHRqaP7vc=", + "dev": true, + "requires": { + "is-fn": "^1.0.0", + "set-immediate-shim": "^1.0.1" + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dev": true, + "optional": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "pull-cat": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/pull-cat/-/pull-cat-1.1.11.tgz", + "integrity": "sha1-tkLdElXaN2pwa220+pYvX9t0wxs=", + "dev": true + }, + "pull-defer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/pull-defer/-/pull-defer-0.2.3.tgz", + "integrity": "sha512-/An3KE7mVjZCqNhZsr22k1Tx8MACnUnHZZNPSJ0S62td8JtYr/AiRG42Vz7Syu31SoTLUzVIe61jtT/pNdjVYA==", + "dev": true + }, + "pull-level": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pull-level/-/pull-level-2.0.4.tgz", + "integrity": "sha512-fW6pljDeUThpq5KXwKbRG3X7Ogk3vc75d5OQU/TvXXui65ykm+Bn+fiktg+MOx2jJ85cd+sheufPL+rw9QSVZg==", + "dev": true, + "requires": { + "level-post": "^1.0.7", + "pull-cat": "^1.1.9", + "pull-live": "^1.0.1", + "pull-pushable": "^2.0.0", + "pull-stream": "^3.4.0", + "pull-window": "^2.1.4", + "stream-to-pull-stream": "^1.7.1" + } + }, + "pull-live": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pull-live/-/pull-live-1.0.1.tgz", + "integrity": "sha1-pOzuAeMwFV6RJLu89HYfIbOPUfU=", + "dev": true, + "requires": { + "pull-cat": "^1.1.9", + "pull-stream": "^3.4.0" + } + }, + "pull-pushable": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pull-pushable/-/pull-pushable-2.2.0.tgz", + "integrity": "sha1-Xy867UethpGfAbEqLpnW8b13ZYE=", + "dev": true + }, + "pull-stream": { + "version": "3.6.14", + "resolved": "https://registry.npmjs.org/pull-stream/-/pull-stream-3.6.14.tgz", + "integrity": "sha512-KIqdvpqHHaTUA2mCYcLG1ibEbu/LCKoJZsBWyv9lSYtPkJPBq8m3Hxa103xHi6D2thj5YXa0TqK3L3GUkwgnew==", + "dev": true + }, + "pull-window": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/pull-window/-/pull-window-2.1.4.tgz", + "integrity": "sha1-/DuG/uvRkgx64pdpHiP3BfiFUvA=", + "dev": true, + "requires": { + "looper": "^2.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "optional": true, + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "optional": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "optional": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "optional": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } + } + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "optional": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "resumer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", + "dev": true, + "requires": { + "through": "~2.3.4" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rlp": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.6.tgz", + "integrity": "sha512-HAfAmL6SDYNWPUOJNrM500x4Thn4PZsEy5pijPh40U9WfNk0z15hUYzO9xVIMAdIHdFtD8CBDHd75Td1g36Mjg==", + "dev": true, + "requires": { + "bn.js": "^4.11.1" + } + }, + "rustbn.js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", + "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safe-event-emitter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-event-emitter/-/safe-event-emitter-1.0.1.tgz", + "integrity": "sha512-e1wFe99A91XYYxoQbcq2ZJUWurxEyP8vfz7A7vuUe1s95q8r5ebraVaA1BukYJcpM6V16ugWoD9vngi8Ccu5fg==", + "dev": true, + "requires": { + "events": "^3.0.0" + } + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true + }, + "scryptsy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-1.2.1.tgz", + "integrity": "sha1-oyJfpLJST4AnAHYeKFW987LZIWM=", + "dev": true, + "optional": true, + "requires": { + "pbkdf2": "^3.0.3" + } + }, + "secp256k1": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", + "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", + "dev": true, + "requires": { + "elliptic": "^6.5.2", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "seedrandom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.1.tgz", + "integrity": "sha512-1/02Y/rUeU1CJBAGLebiC5Lbo5FnB22gQbIFFYTLkwvp1xdABZJH1sn4ZT1MzXmPpzv+Rf/Lu2NcsLJiK4rcDg==", + "dev": true + }, + "semaphore": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", + "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==", + "dev": true + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true, + "optional": true + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "optional": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "servify": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz", + "integrity": "sha512-/xE6GvsKKqyo1BAY+KxOWXcLpPsUUyji7Qg3bVD7hh1eRze5bR1uYiuDA/k3Gof1s9BTzQZEJK8sNcNGFIzeWw==", + "dev": true, + "optional": true, + "requires": { + "body-parser": "^1.16.0", + "cors": "^2.8.1", + "express": "^4.14.0", + "request": "^2.79.0", + "xhr": "^2.3.3" + } + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true, + "optional": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "optional": true + }, + "simple-get": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", + "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", + "dev": true, + "optional": true, + "requires": { + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + } + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "optional": true + }, + "stream-to-pull-stream": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/stream-to-pull-stream/-/stream-to-pull-stream-1.7.3.tgz", + "integrity": "sha512-6sNyqJpr5dIOQdgNy/xcDWwDuzAsAwVzhzrWlAPAQ7Lkjx/rv0wgvxEyKwTq6FmNd5rjTrELt/CLmaSw7crMGg==", + "dev": true, + "requires": { + "looper": "^3.0.0", + "pull-stream": "^3.2.3" + }, + "dependencies": { + "looper": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/looper/-/looper-3.0.0.tgz", + "integrity": "sha1-LvpUw7HLq6m5Su4uWRSwvlf7t0k=", + "dev": true + } + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true, + "optional": true + }, + "string.prototype.trim": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.3.tgz", + "integrity": "sha512-16IL9pIBA5asNOSukPfxX2W68BaBvxyiRK16H3RA/lWW9BDosh+w7f+LhomPHpXJ82QEe7w7/rY/S1CV97raLg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", + "dev": true, + "requires": { + "is-hex-prefixed": "1.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "swarm-js": { + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.40.tgz", + "integrity": "sha512-yqiOCEoA4/IShXkY3WKwP5PvZhmoOOD8clsKA7EEcRILMkTEYHCQ21HDCAcVpmIxZq4LyZvWeRJ6quIyHk1caA==", + "dev": true, + "optional": true, + "requires": { + "bluebird": "^3.5.0", + "buffer": "^5.0.5", + "eth-lib": "^0.1.26", + "fs-extra": "^4.0.2", + "got": "^7.1.0", + "mime-types": "^2.1.16", + "mkdirp-promise": "^5.0.1", + "mock-fs": "^4.1.0", + "setimmediate": "^1.0.5", + "tar": "^4.0.2", + "xhr-request": "^1.0.1" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true, + "optional": true + }, + "got": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", + "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "dev": true, + "optional": true, + "requires": { + "decompress-response": "^3.2.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-plain-obj": "^1.1.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "p-cancelable": "^0.3.0", + "p-timeout": "^1.1.1", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "url-parse-lax": "^1.0.0", + "url-to-options": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "optional": true + }, + "p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", + "dev": true, + "optional": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true, + "optional": true + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "optional": true, + "requires": { + "prepend-http": "^1.0.1" + } + } + } + }, + "tape": { + "version": "4.13.3", + "resolved": "https://registry.npmjs.org/tape/-/tape-4.13.3.tgz", + "integrity": "sha512-0/Y20PwRIUkQcTCSi4AASs+OANZZwqPKaipGCEwp10dQMipVvSZwUUCi01Y/OklIGyHKFhIcjock+DKnBfLAFw==", + "dev": true, + "requires": { + "deep-equal": "~1.1.1", + "defined": "~1.0.0", + "dotignore": "~0.1.2", + "for-each": "~0.3.3", + "function-bind": "~1.1.1", + "glob": "~7.1.6", + "has": "~1.0.3", + "inherits": "~2.0.4", + "is-regex": "~1.0.5", + "minimist": "~1.2.5", + "object-inspect": "~1.7.0", + "resolve": "~1.17.0", + "resumer": "~0.0.0", + "string.prototype.trim": "~1.2.1", + "through": "~2.3.8" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.6.0" + } + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true, + "optional": true + }, + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, + "requires": { + "rimraf": "^2.6.3" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, + "optional": true + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true, + "optional": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, + "tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "dev": true + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "optional": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typewise": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", + "integrity": "sha1-EGeTZUCvl5N8xdz5kiSG6fooRlE=", + "dev": true, + "requires": { + "typewise-core": "^1.2.0" + } + }, + "typewise-core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", + "integrity": "sha1-l+uRgFx/VdL5QXSPpQ0xXZke8ZU=", + "dev": true + }, + "typewiselite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typewiselite/-/typewiselite-1.0.0.tgz", + "integrity": "sha1-yIgvobsQksBgBal/NO9chQjjZk4=", + "dev": true + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true, + "optional": true + }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", + "dev": true, + "optional": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + } + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unorm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "optional": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "optional": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, + "url-set-query": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", + "integrity": "sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk=", + "dev": true, + "optional": true + }, + "url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", + "dev": true, + "optional": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "utf-8-validate": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.4.tgz", + "integrity": "sha512-MEF05cPSq3AwJ2C7B7sHAA6i53vONoZbMGX8My5auEVm6W+dJ2Jd/TZPyGJ5CH42V2XtbI5FD28HeHeqlPzZ3Q==", + "dev": true, + "requires": { + "node-gyp-build": "^4.2.0" + } + }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz", + "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "for-each": "^0.3.3", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.1" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "optional": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "varint": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==", + "dev": true, + "optional": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "optional": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "web3": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3/-/web3-1.2.11.tgz", + "integrity": "sha512-mjQ8HeU41G6hgOYm1pmeH0mRAeNKJGnJEUzDMoerkpw7QUQT4exVREgF1MYPvL/z6vAshOXei25LE/t/Bxl8yQ==", + "dev": true, + "optional": true, + "requires": { + "web3-bzz": "1.2.11", + "web3-core": "1.2.11", + "web3-eth": "1.2.11", + "web3-eth-personal": "1.2.11", + "web3-net": "1.2.11", + "web3-shh": "1.2.11", + "web3-utils": "1.2.11" + } + }, + "web3-bzz": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.11.tgz", + "integrity": "sha512-XGpWUEElGypBjeFyUhTkiPXFbDVD6Nr/S5jznE3t8cWUA0FxRf1n3n/NuIZeb0H9RkN2Ctd/jNma/k8XGa3YKg==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "^12.12.6", + "got": "9.6.0", + "swarm-js": "^0.1.40", + "underscore": "1.9.1" + }, + "dependencies": { + "@types/node": { + "version": "12.19.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.12.tgz", + "integrity": "sha512-UwfL2uIU9arX/+/PRcIkT08/iBadGN2z6ExOROA2Dh5mAuWTBj6iJbQX4nekiV5H8cTrEG569LeX+HRco9Cbxw==", + "dev": true, + "optional": true + } + } + }, + "web3-core": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-core/-/web3-core-1.2.11.tgz", + "integrity": "sha512-CN7MEYOY5ryo5iVleIWRE3a3cZqVaLlIbIzDPsvQRUfzYnvzZQRZBm9Mq+ttDi2STOOzc1MKylspz/o3yq/LjQ==", + "dev": true, + "optional": true, + "requires": { + "@types/bn.js": "^4.11.5", + "@types/node": "^12.12.6", + "bignumber.js": "^9.0.0", + "web3-core-helpers": "1.2.11", + "web3-core-method": "1.2.11", + "web3-core-requestmanager": "1.2.11", + "web3-utils": "1.2.11" + }, + "dependencies": { + "@types/node": { + "version": "12.19.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.12.tgz", + "integrity": "sha512-UwfL2uIU9arX/+/PRcIkT08/iBadGN2z6ExOROA2Dh5mAuWTBj6iJbQX4nekiV5H8cTrEG569LeX+HRco9Cbxw==", + "dev": true, + "optional": true + } + } + }, + "web3-core-helpers": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.11.tgz", + "integrity": "sha512-PEPoAoZd5ME7UfbnCZBdzIerpe74GEvlwT4AjOmHeCVZoIFk7EqvOZDejJHt+feJA6kMVTdd0xzRNN295UhC1A==", + "dev": true, + "optional": true, + "requires": { + "underscore": "1.9.1", + "web3-eth-iban": "1.2.11", + "web3-utils": "1.2.11" + } + }, + "web3-core-method": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.11.tgz", + "integrity": "sha512-ff0q76Cde94HAxLDZ6DbdmKniYCQVtvuaYh+rtOUMB6kssa5FX0q3vPmixi7NPooFnbKmmZCM6NvXg4IreTPIw==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/transactions": "^5.0.0-beta.135", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.11", + "web3-core-promievent": "1.2.11", + "web3-core-subscriptions": "1.2.11", + "web3-utils": "1.2.11" + } + }, + "web3-core-promievent": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.11.tgz", + "integrity": "sha512-il4McoDa/Ox9Agh4kyfQ8Ak/9ABYpnF8poBLL33R/EnxLsJOGQG2nZhkJa3I067hocrPSjEdlPt/0bHXsln4qA==", + "dev": true, + "optional": true, + "requires": { + "eventemitter3": "4.0.4" + } + }, + "web3-core-requestmanager": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.11.tgz", + "integrity": "sha512-oFhBtLfOiIbmfl6T6gYjjj9igOvtyxJ+fjS+byRxiwFJyJ5BQOz4/9/17gWR1Cq74paTlI7vDGxYfuvfE/mKvA==", + "dev": true, + "optional": true, + "requires": { + "underscore": "1.9.1", + "web3-core-helpers": "1.2.11", + "web3-providers-http": "1.2.11", + "web3-providers-ipc": "1.2.11", + "web3-providers-ws": "1.2.11" + } + }, + "web3-core-subscriptions": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.11.tgz", + "integrity": "sha512-qEF/OVqkCvQ7MPs1JylIZCZkin0aKK9lDxpAtQ1F8niEDGFqn7DT8E/vzbIa0GsOjL2fZjDhWJsaW+BSoAW1gg==", + "dev": true, + "optional": true, + "requires": { + "eventemitter3": "4.0.4", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.11" + } + }, + "web3-eth": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.11.tgz", + "integrity": "sha512-REvxW1wJ58AgHPcXPJOL49d1K/dPmuw4LjPLBPStOVkQjzDTVmJEIsiLwn2YeuNDd4pfakBwT8L3bz1G1/wVsQ==", + "dev": true, + "optional": true, + "requires": { + "underscore": "1.9.1", + "web3-core": "1.2.11", + "web3-core-helpers": "1.2.11", + "web3-core-method": "1.2.11", + "web3-core-subscriptions": "1.2.11", + "web3-eth-abi": "1.2.11", + "web3-eth-accounts": "1.2.11", + "web3-eth-contract": "1.2.11", + "web3-eth-ens": "1.2.11", + "web3-eth-iban": "1.2.11", + "web3-eth-personal": "1.2.11", + "web3-net": "1.2.11", + "web3-utils": "1.2.11" + } + }, + "web3-eth-abi": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.2.11.tgz", + "integrity": "sha512-PkRYc0+MjuLSgg03QVWqWlQivJqRwKItKtEpRUaxUAeLE7i/uU39gmzm2keHGcQXo3POXAbOnMqkDvOep89Crg==", + "dev": true, + "optional": true, + "requires": { + "@ethersproject/abi": "5.0.0-beta.153", + "underscore": "1.9.1", + "web3-utils": "1.2.11" + } + }, + "web3-eth-accounts": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.11.tgz", + "integrity": "sha512-6FwPqEpCfKIh3nSSGeo3uBm2iFSnFJDfwL3oS9pyegRBXNsGRVpgiW63yhNzL0796StsvjHWwQnQHsZNxWAkGw==", + "dev": true, + "optional": true, + "requires": { + "crypto-browserify": "3.12.0", + "eth-lib": "0.2.8", + "ethereumjs-common": "^1.3.2", + "ethereumjs-tx": "^2.1.1", + "scrypt-js": "^3.0.1", + "underscore": "1.9.1", + "uuid": "3.3.2", + "web3-core": "1.2.11", + "web3-core-helpers": "1.2.11", + "web3-core-method": "1.2.11", + "web3-utils": "1.2.11" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true, + "optional": true + } + } + }, + "web3-eth-contract": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.11.tgz", + "integrity": "sha512-MzYuI/Rq2o6gn7vCGcnQgco63isPNK5lMAan2E51AJLknjSLnOxwNY3gM8BcKoy4Z+v5Dv00a03Xuk78JowFow==", + "dev": true, + "optional": true, + "requires": { + "@types/bn.js": "^4.11.5", + "underscore": "1.9.1", + "web3-core": "1.2.11", + "web3-core-helpers": "1.2.11", + "web3-core-method": "1.2.11", + "web3-core-promievent": "1.2.11", + "web3-core-subscriptions": "1.2.11", + "web3-eth-abi": "1.2.11", + "web3-utils": "1.2.11" + } + }, + "web3-eth-ens": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.11.tgz", + "integrity": "sha512-dbW7dXP6HqT1EAPvnniZVnmw6TmQEKF6/1KgAxbo8iBBYrVTMDGFQUUnZ+C4VETGrwwaqtX4L9d/FrQhZ6SUiA==", + "dev": true, + "optional": true, + "requires": { + "content-hash": "^2.5.2", + "eth-ens-namehash": "2.0.8", + "underscore": "1.9.1", + "web3-core": "1.2.11", + "web3-core-helpers": "1.2.11", + "web3-core-promievent": "1.2.11", + "web3-eth-abi": "1.2.11", + "web3-eth-contract": "1.2.11", + "web3-utils": "1.2.11" + } + }, + "web3-eth-iban": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.11.tgz", + "integrity": "sha512-ozuVlZ5jwFC2hJY4+fH9pIcuH1xP0HEFhtWsR69u9uDIANHLPQQtWYmdj7xQ3p2YT4bQLq/axKhZi7EZVetmxQ==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^4.11.9", + "web3-utils": "1.2.11" + } + }, + "web3-eth-personal": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.11.tgz", + "integrity": "sha512-42IzUtKq9iHZ8K9VN0vAI50iSU9tOA1V7XU2BhF/tb7We2iKBVdkley2fg26TxlOcKNEHm7o6HRtiiFsVK4Ifw==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "^12.12.6", + "web3-core": "1.2.11", + "web3-core-helpers": "1.2.11", + "web3-core-method": "1.2.11", + "web3-net": "1.2.11", + "web3-utils": "1.2.11" + }, + "dependencies": { + "@types/node": { + "version": "12.19.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.12.tgz", + "integrity": "sha512-UwfL2uIU9arX/+/PRcIkT08/iBadGN2z6ExOROA2Dh5mAuWTBj6iJbQX4nekiV5H8cTrEG569LeX+HRco9Cbxw==", + "dev": true, + "optional": true + } + } + }, + "web3-net": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-net/-/web3-net-1.2.11.tgz", + "integrity": "sha512-sjrSDj0pTfZouR5BSTItCuZ5K/oZPVdVciPQ6981PPPIwJJkCMeVjD7I4zO3qDPCnBjBSbWvVnLdwqUBPtHxyg==", + "dev": true, + "optional": true, + "requires": { + "web3-core": "1.2.11", + "web3-core-method": "1.2.11", + "web3-utils": "1.2.11" + } + }, + "web3-provider-engine": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-14.2.1.tgz", + "integrity": "sha512-iSv31h2qXkr9vrL6UZDm4leZMc32SjWJFGOp/D92JXfcEboCqraZyuExDkpxKw8ziTufXieNM7LSXNHzszYdJw==", + "dev": true, + "requires": { + "async": "^2.5.0", + "backoff": "^2.5.0", + "clone": "^2.0.0", + "cross-fetch": "^2.1.0", + "eth-block-tracker": "^3.0.0", + "eth-json-rpc-infura": "^3.1.0", + "eth-sig-util": "^1.4.2", + "ethereumjs-block": "^1.2.2", + "ethereumjs-tx": "^1.2.0", + "ethereumjs-util": "^5.1.5", + "ethereumjs-vm": "^2.3.4", + "json-rpc-error": "^2.0.0", + "json-stable-stringify": "^1.0.1", + "promise-to-callback": "^1.0.0", + "readable-stream": "^2.2.9", + "request": "^2.85.0", + "semaphore": "^1.0.3", + "ws": "^5.1.1", + "xhr": "^2.2.0", + "xtend": "^4.0.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.6.3.tgz", + "integrity": "sha512-2++wDf/DYqkPR3o5tbfdhF96EfMApo1GpPfzOsR/ZYXdkSmELlvOOEAl9iKkRsktMPHdGjO4rtkBpf2I7TiTeA==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + }, + "deferred-leveldown": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-1.2.2.tgz", + "integrity": "sha512-uukrWD2bguRtXilKt6cAWKyoXrTSMo5m7crUdLfWQmu8kIm88w3QZoUL+6nhpfKVmhHANER6Re3sKoNoZ3IKMA==", + "dev": true, + "requires": { + "abstract-leveldown": "~2.6.0" + } + }, + "eth-sig-util": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", + "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", + "dev": true, + "requires": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "ethereumjs-util": "^5.1.1" + } + }, + "ethereumjs-abi": { + "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#ee3994657fa7a427238e6ba92a84d0b529bbcde0", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "dev": true, + "requires": { + "bn.js": "^4.11.8", + "ethereumjs-util": "^6.0.0" + }, + "dependencies": { + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "ethereumjs-account": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-2.0.5.tgz", + "integrity": "sha512-bgDojnXGjhMwo6eXQC0bY6UK2liSFUSMwwylOmQvZbSl/D7NXQ3+vrGO46ZeOgjGfxXmgIeVNDIiHw7fNZM4VA==", + "dev": true, + "requires": { + "ethereumjs-util": "^5.0.0", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "ethereumjs-block": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz", + "integrity": "sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg==", + "dev": true, + "requires": { + "async": "^2.0.1", + "ethereum-common": "0.2.0", + "ethereumjs-tx": "^1.2.2", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "dependencies": { + "ethereum-common": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz", + "integrity": "sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA==", + "dev": true + } + } + }, + "ethereumjs-tx": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz", + "integrity": "sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==", + "dev": true, + "requires": { + "ethereum-common": "^0.0.18", + "ethereumjs-util": "^5.0.0" + } + }, + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + }, + "ethereumjs-vm": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz", + "integrity": "sha512-r/XIUik/ynGbxS3y+mvGnbOKnuLo40V5Mj1J25+HEO63aWYREIqvWeRO/hnROlMBE5WoniQmPmhiaN0ctiHaXw==", + "dev": true, + "requires": { + "async": "^2.1.2", + "async-eventemitter": "^0.2.2", + "ethereumjs-account": "^2.0.3", + "ethereumjs-block": "~2.2.0", + "ethereumjs-common": "^1.1.0", + "ethereumjs-util": "^6.0.0", + "fake-merkle-patricia-tree": "^1.0.1", + "functional-red-black-tree": "^1.0.1", + "merkle-patricia-tree": "^2.3.2", + "rustbn.js": "~0.2.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "ethereumjs-block": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz", + "integrity": "sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg==", + "dev": true, + "requires": { + "async": "^2.0.1", + "ethereumjs-common": "^1.5.0", + "ethereumjs-tx": "^2.1.1", + "ethereumjs-util": "^5.0.0", + "merkle-patricia-tree": "^2.1.2" + }, + "dependencies": { + "ethereumjs-util": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.1.tgz", + "integrity": "sha512-v3kT+7zdyCm1HIqWlLNrHGqHGLpGYIhjeHxQjnDXjLT2FyGJDsd3LWMYUo7pAFRrk86CR3nUJfhC81CCoJNNGQ==", + "dev": true, + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "^0.1.3", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1" + } + } + } + }, + "ethereumjs-tx": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz", + "integrity": "sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw==", + "dev": true, + "requires": { + "ethereumjs-common": "^1.5.0", + "ethereumjs-util": "^6.0.0" + } + }, + "ethereumjs-util": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", + "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", + "dev": true, + "requires": { + "@types/bn.js": "^4.11.3", + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "elliptic": "^6.5.2", + "ethereum-cryptography": "^0.1.3", + "ethjs-util": "0.1.6", + "rlp": "^2.2.3" + } + } + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "level-codec": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-7.0.1.tgz", + "integrity": "sha512-Ua/R9B9r3RasXdRmOtd+t9TCOEIIlts+TN/7XTT2unhDaL6sJn83S3rUyljbr6lVtw49N3/yA0HHjpV6Kzb2aQ==", + "dev": true + }, + "level-errors": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-1.0.5.tgz", + "integrity": "sha512-/cLUpQduF6bNrWuAC4pwtUKA5t669pCsCi2XbmojG2tFeOr9j6ShtdDCtFFQO1DRt+EVZhx9gPzP9G2bUaG4ig==", + "dev": true, + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz", + "integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "level-errors": "^1.0.3", + "readable-stream": "^1.0.33", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "level-ws": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-0.0.0.tgz", + "integrity": "sha1-Ny5RIXeSSgBCSwtDrvK7QkltIos=", + "dev": true, + "requires": { + "readable-stream": "~1.0.15", + "xtend": "~2.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "levelup": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-1.3.9.tgz", + "integrity": "sha512-VVGHfKIlmw8w1XqpGOAGwq6sZm2WwWLmlDcULkKWQXEA5EopA8OBNJ2Ck2v6bdk8HeEZSbCSEgzXadyQFm76sQ==", + "dev": true, + "requires": { + "deferred-leveldown": "~1.2.1", + "level-codec": "~7.0.0", + "level-errors": "~1.0.3", + "level-iterator-stream": "~1.3.0", + "prr": "~1.0.1", + "semver": "~5.4.1", + "xtend": "~4.0.0" + } + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=", + "dev": true + }, + "memdown": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-1.4.1.tgz", + "integrity": "sha1-tOThkhdGZP+65BNhqlAPMRnv4hU=", + "dev": true, + "requires": { + "abstract-leveldown": "~2.7.1", + "functional-red-black-tree": "^1.0.1", + "immediate": "^3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "abstract-leveldown": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-2.7.2.tgz", + "integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==", + "dev": true, + "requires": { + "xtend": "~4.0.0" + } + } + } + }, + "merkle-patricia-tree": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.2.tgz", + "integrity": "sha512-81PW5m8oz/pz3GvsAwbauj7Y00rqm81Tzad77tHBwU7pIAtN+TJnMSOJhxBKflSVYhptMMb9RskhqHqrSm1V+g==", + "dev": true, + "requires": { + "async": "^1.4.2", + "ethereumjs-util": "^5.0.0", + "level-ws": "0.0.0", + "levelup": "^1.2.1", + "memdown": "^1.0.0", + "readable-stream": "^2.0.0", + "rlp": "^2.0.0", + "semaphore": ">=1.0.1" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "web3-providers-http": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.11.tgz", + "integrity": "sha512-psh4hYGb1+ijWywfwpB2cvvOIMISlR44F/rJtYkRmQ5jMvG4FOCPlQJPiHQZo+2cc3HbktvvSJzIhkWQJdmvrA==", + "dev": true, + "optional": true, + "requires": { + "web3-core-helpers": "1.2.11", + "xhr2-cookies": "1.1.0" + } + }, + "web3-providers-ipc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.11.tgz", + "integrity": "sha512-yhc7Y/k8hBV/KlELxynWjJDzmgDEDjIjBzXK+e0rHBsYEhdCNdIH5Psa456c+l0qTEU2YzycF8VAjYpWfPnBpQ==", + "dev": true, + "optional": true, + "requires": { + "oboe": "2.1.4", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.11" + } + }, + "web3-providers-ws": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.11.tgz", + "integrity": "sha512-ZxnjIY1Er8Ty+cE4migzr43zA/+72AF1myzsLaU5eVgdsfV7Jqx7Dix1hbevNZDKFlSoEyq/3j/jYalh3So1Zg==", + "dev": true, + "optional": true, + "requires": { + "eventemitter3": "4.0.4", + "underscore": "1.9.1", + "web3-core-helpers": "1.2.11", + "websocket": "^1.0.31" + } + }, + "web3-shh": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.11.tgz", + "integrity": "sha512-B3OrO3oG1L+bv3E1sTwCx66injW1A8hhwpknDUbV+sw3fehFazA06z9SGXUefuFI1kVs4q2vRi0n4oCcI4dZDg==", + "dev": true, + "optional": true, + "requires": { + "web3-core": "1.2.11", + "web3-core-method": "1.2.11", + "web3-core-subscriptions": "1.2.11", + "web3-net": "1.2.11" + } + }, + "web3-utils": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.2.11.tgz", + "integrity": "sha512-3Tq09izhD+ThqHEaWYX4VOT7dNPdZiO+c/1QMA0s5X2lDFKK/xHJb7cyTRRVzN2LvlHbR7baS1tmQhSua51TcQ==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "underscore": "1.9.1", + "utf8": "3.0.0" + }, + "dependencies": { + "eth-lib": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/eth-lib/-/eth-lib-0.2.8.tgz", + "integrity": "sha512-ArJ7x1WcWOlSpzdoTBX8vkwlkSQ85CjjifSZtV4co64vWxSV8geWfPI9x4SVYu3DSxnX4yWFVTtGL+j9DUFLNw==", + "dev": true, + "optional": true, + "requires": { + "bn.js": "^4.11.6", + "elliptic": "^6.4.0", + "xhr-request-promise": "^0.1.2" + } + } + } + }, + "websocket": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.32.tgz", + "integrity": "sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q==", + "dev": true, + "requires": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "optional": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true + } + } + }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dev": true, + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xhr-request": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", + "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", + "dev": true, + "optional": true, + "requires": { + "buffer-to-arraybuffer": "^0.0.5", + "object-assign": "^4.1.1", + "query-string": "^5.0.1", + "simple-get": "^2.7.0", + "timed-out": "^4.0.1", + "url-set-query": "^1.0.0", + "xhr": "^2.0.4" + } + }, + "xhr-request-promise": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz", + "integrity": "sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==", + "dev": true, + "optional": true, + "requires": { + "xhr-request": "^1.1.0" + } + }, + "xhr2-cookies": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz", + "integrity": "sha1-fXdEnQmZGX8VXLc7I99yUF7YnUg=", + "dev": true, + "optional": true, + "requires": { + "cookiejar": "^2.1.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "yaeti": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", + "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dev": true, + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "hardhat": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.6.2.tgz", + "integrity": "sha512-83kCYIrkRq+vrsjOLIv349fbMpL7Vp/ZGpWtu+KLMH3wcAAs+aMsABZJ8W5hb5uo69+06KeF0pCHQ6y8tBAcwA==", + "dev": true, + "requires": { + "@ethereumjs/block": "^3.4.0", + "@ethereumjs/blockchain": "^5.4.0", + "@ethereumjs/common": "^2.4.0", + "@ethereumjs/tx": "^3.3.0", + "@ethereumjs/vm": "^5.5.2", + "@ethersproject/abi": "^5.1.2", + "@sentry/node": "^5.18.1", + "@solidity-parser/parser": "^0.11.0", + "@types/bn.js": "^5.1.0", + "@types/lru-cache": "^5.1.0", + "abort-controller": "^3.0.0", + "adm-zip": "^0.4.16", + "ansi-escapes": "^4.3.0", + "chalk": "^2.4.2", + "chokidar": "^3.4.0", + "ci-info": "^2.0.0", + "debug": "^4.1.1", + "enquirer": "^2.3.0", + "env-paths": "^2.2.0", + "eth-sig-util": "^2.5.2", + "ethereum-cryptography": "^0.1.2", + "ethereumjs-abi": "^0.6.8", + "ethereumjs-util": "^7.1.0", + "find-up": "^2.1.0", + "fp-ts": "1.19.3", + "fs-extra": "^7.0.1", + "glob": "^7.1.3", + "https-proxy-agent": "^5.0.0", + "immutable": "^4.0.0-rc.12", + "io-ts": "1.10.4", + "lodash": "^4.17.11", + "merkle-patricia-tree": "^4.2.0", + "mnemonist": "^0.38.0", + "mocha": "^7.1.2", + "node-fetch": "^2.6.0", + "qs": "^6.7.0", + "raw-body": "^2.4.1", + "resolve": "1.17.0", + "semver": "^6.3.0", + "slash": "^3.0.0", + "solc": "0.7.3", + "source-map-support": "^0.5.13", + "stacktrace-parser": "^0.1.10", + "true-case-path": "^2.2.1", + "tsort": "0.0.1", + "uuid": "^3.3.2", + "ws": "^7.4.6" + }, + "dependencies": { + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "solc": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", + "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", + "dev": true, + "requires": { + "command-exists": "^1.2.8", + "commander": "3.0.2", + "follow-redirects": "^1.12.1", + "fs-extra": "^0.30.0", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "require-from-string": "^2.0.0", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "dependencies": { + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + } + } + }, + "hardhat-contract-sizer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hardhat-contract-sizer/-/hardhat-contract-sizer-2.0.3.tgz", + "integrity": "sha512-iaixOzWxwOSIIE76cl2uk4m9VXI1hKU3bFt+gl7jDhyb2/JB2xOp5wECkfWqAoc4V5lD4JtjldZlpSTbzX+nPQ==", + "dev": true, + "requires": { + "cli-table3": "^0.6.0", + "colors": "^1.4.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "idna-uts46-hx": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz", + "integrity": "sha512-PWoF9Keq6laYdIRwwCdhTPl60xRqAloYNMQLiyUnG42VjT53oW07BXIRM+NK7eQjzXjAk2gUvX9caRxlnF9TAA==", + "dev": true, + "requires": { + "punycode": "2.1.0" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", + "dev": true + }, + "immutable": { + "version": "4.0.0-rc.14", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.14.tgz", + "integrity": "sha512-pfkvmRKJSoW7JFx0QeYlAmT+kNYvn5j0u7bnpNq4N2RCvHSTlLT208G8jgaquNe+Q8kCPHKOSpxJkyvLDpYq0w==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "io-ts": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", + "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", + "dev": true, + "requires": { + "fp-ts": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hex-prefixed": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", + "integrity": "sha1-fY035q135dEnFIkTxXPggtd39VQ=", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "js-sha3": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", + "integrity": "sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "keccak": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.2.tgz", + "integrity": "sha512-PyKKjkH53wDMLGrvmRGSNWgmSxZOUqbnXwKL9tmgbFYA1iAYqW21kfR7mZXV0MlESiefxQQE9X9fTa3X+2MPDQ==", + "dev": true, + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + } + }, + "klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "level-codec": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", + "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", + "dev": true, + "requires": { + "buffer": "^5.6.0" + } + }, + "level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", + "dev": true + }, + "level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "dev": true, + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", + "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.4.0", + "xtend": "^4.0.2" + } + }, + "level-mem": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/level-mem/-/level-mem-5.0.1.tgz", + "integrity": "sha512-qd+qUJHXsGSFoHTziptAKXoLX87QjR7v2KMbqncDXPxQuCdsQlzmyX+gwrEHhlzn08vkf8TyipYyMmiC6Gobzg==", + "dev": true, + "requires": { + "level-packager": "^5.0.3", + "memdown": "^5.0.0" + } + }, + "level-packager": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", + "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", + "dev": true, + "requires": { + "encoding-down": "^6.3.0", + "levelup": "^4.3.2" + } + }, + "level-supports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "dev": true, + "requires": { + "xtend": "^4.0.2" + } + }, + "level-ws": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-2.0.0.tgz", + "integrity": "sha512-1iv7VXx0G9ec1isqQZ7y5LmoZo/ewAsyDHNA8EFDW5hqH2Kqovm33nSFkSdnLLAK+I5FlT+lo5Cw9itGe+CpQA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^3.1.0", + "xtend": "^4.0.1" + } + }, + "levelup": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", + "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", + "dev": true, + "requires": { + "deferred-leveldown": "~5.3.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~4.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.assign": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", + "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", + "dev": true + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=", + "dev": true + }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=", + "dev": true + }, + "mcl-wasm": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", + "integrity": "sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "memdown": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/memdown/-/memdown-5.1.0.tgz", + "integrity": "sha512-B3J+UizMRAlEArDjWHTMmadet+UKwHd3UjMgGBkZcKAxAYVPS9o0Yeiha4qvz7iGiL2Sb3igUft6p7nbFWctpw==", + "dev": true, + "requires": { + "abstract-leveldown": "~6.2.1", + "functional-red-black-tree": "~1.0.1", + "immediate": "~3.2.3", + "inherits": "~2.0.1", + "ltgt": "~2.2.0", + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "abstract-leveldown": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", + "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "immediate": "^3.2.3", + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, + "immediate": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", + "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=", + "dev": true + } + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, + "merkle-patricia-tree": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-4.2.1.tgz", + "integrity": "sha512-25reMgrT8PhJy0Ba0U7fMZD2oobS1FPWB9vQa0uBpJYIQYYuFXEHoqEkTqA/UzX+s9br3pmUVVY/TOsFt/x0oQ==", + "dev": true, + "requires": { + "@types/levelup": "^4.3.0", + "ethereumjs-util": "^7.1.0", + "level-mem": "^5.0.1", + "level-ws": "^2.0.0", + "readable-stream": "^3.6.0", + "rlp": "^2.2.4", + "semaphore-async-await": "^1.5.1" + } + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==", + "dev": true + }, + "mime-types": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", + "dev": true, + "requires": { + "mime-db": "1.49.0" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "dev": true, + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mnemonist": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "dev": true, + "requires": { + "obliterator": "^1.6.1" + } + }, + "mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, + "node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", + "dev": true + }, + "nofilter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-1.0.4.tgz", + "integrity": "sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "number-to-bn": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", + "integrity": "sha1-uzYjWS9+X54AMLGXe9QaDFP+HqA=", + "dev": true, + "requires": { + "bn.js": "4.11.6", + "strip-hex-prefix": "1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.6", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", + "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=", + "dev": true + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", + "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2" + } + }, + "obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-headers": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.4.tgz", + "integrity": "sha512-psZ9iZoCNFLrgRjZ1d8mn0h9WRqJwFxM9q3x7iUjN/YT2OksthDJ5TiPCu2F38kS4zutqfW+YdVVkBZZx3/1aw==", + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "patch-package": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.4.7.tgz", + "integrity": "sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ==", + "dev": true, + "requires": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^7.0.1", + "is-ci": "^2.0.0", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.0", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^5.6.0", + "slash": "^2.0.0", + "tmp": "^0.0.33" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, + "path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "postinstall-postinstall": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz", + "integrity": "sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==", + "dev": true + }, + "prettier": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "dev": true + }, + "printj": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", + "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "dev": true + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "rlp": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.6.tgz", + "integrity": "sha512-HAfAmL6SDYNWPUOJNrM500x4Thn4PZsEy5pijPh40U9WfNk0z15hUYzO9xVIMAdIHdFtD8CBDHd75Td1g36Mjg==", + "dev": true, + "requires": { + "bn.js": "^4.11.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "rustbn.js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", + "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "dev": true + }, + "secp256k1": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz", + "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==", + "dev": true, + "requires": { + "elliptic": "^6.5.2", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + } + }, + "semaphore-async-await": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz", + "integrity": "sha1-hXvvXjZEYBykuVcLh+nfXKEpdPo=", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true + }, + "simple-get": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", + "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", + "dev": true, + "requires": { + "decompress-response": "^3.3.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "solc": { + "version": "0.6.12", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.6.12.tgz", + "integrity": "sha512-Lm0Ql2G9Qc7yPP2Ba+WNmzw2jwsrd3u4PobHYlSOxaut3TtUbj9+5ZrT6f4DUpNPEoBaFUOEg9Op9C0mk7ge9g==", + "dev": true, + "requires": { + "command-exists": "^1.2.8", + "commander": "3.0.2", + "fs-extra": "^0.30.0", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "require-from-string": "^2.0.0", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "dependencies": { + "fs-extra": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" + } + }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true + }, + "jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + } + } + }, + "stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, + "requires": { + "type-fest": "^0.7.1" + }, + "dependencies": { + "type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-hex-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", + "integrity": "sha1-DF8VX+8RUTczd96du1iNoFUA428=", + "dev": true, + "requires": { + "is-hex-prefixed": "1.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "test-value": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz", + "integrity": "sha1-Edpv9nDzRxpztiXKTz/c97t0gpE=", + "dev": true, + "requires": { + "array-back": "^1.0.3", + "typical": "^2.6.0" + }, + "dependencies": { + "array-back": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", + "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=", + "dev": true, + "requires": { + "typical": "^2.6.0" + } + } + } + }, + "testrpc": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/testrpc/-/testrpc-0.0.1.tgz", + "integrity": "sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA==", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, + "true-case-path": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", + "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==", + "dev": true + }, + "ts-essentials": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-1.0.4.tgz", + "integrity": "sha512-q3N1xS4vZpRouhYHDPwO0bDW3EZ6SK9CrrDHxi/D6BPReSjpVgWIOpLS2o0gSBZm+7q/wyKp6RVM1AeeW7uyfQ==", + "dev": true + }, + "ts-generator": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ts-generator/-/ts-generator-0.1.1.tgz", + "integrity": "sha512-N+ahhZxTLYu1HNTQetwWcx3so8hcYbkKBHTr4b4/YgObFTIKkOSSsaa+nal12w8mfrJAyzJfETXawbNjSfP2gQ==", + "dev": true, + "requires": { + "@types/mkdirp": "^0.5.2", + "@types/prettier": "^2.1.1", + "@types/resolve": "^0.0.8", + "chalk": "^2.4.1", + "glob": "^7.1.2", + "mkdirp": "^0.5.1", + "prettier": "^2.1.2", + "resolve": "^1.8.1", + "ts-essentials": "^1.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsort": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", + "integrity": "sha1-4igPXoF/i/QnVlf9D5rr1E9aJ4Y=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, + "tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typechain": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/typechain/-/typechain-5.1.2.tgz", + "integrity": "sha512-FuaCxJd7BD3ZAjVJoO+D6TnqKey3pQdsqOBsC83RKYWKli5BDhdf0TPkwfyjt20TUlZvOzJifz+lDwXsRkiSKA==", + "dev": true, + "requires": { + "@types/prettier": "^2.1.1", + "command-line-args": "^4.0.7", + "debug": "^4.1.1", + "fs-extra": "^7.0.0", + "glob": "^7.1.6", + "js-sha3": "^0.8.0", + "lodash": "^4.17.15", + "mkdirp": "^1.0.4", + "prettier": "^2.1.2", + "ts-essentials": "^7.0.1" + }, + "dependencies": { + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "ts-essentials": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", + "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", + "dev": true + } + } + }, + "typical": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", + "integrity": "sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0=", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "url-set-query": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-set-query/-/url-set-query-1.0.0.tgz", + "integrity": "sha1-AW6M/Xwg7gXK/neV6JK9BwL6ozk=", + "dev": true + }, + "utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz", + "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "for-each": "^0.3.3", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.1" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "web3-utils": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.5.2.tgz", + "integrity": "sha512-quTtTeQJHYSxAwIBOCGEcQtqdVcFWX6mCFNoqnp+mRbq+Hxbs8CGgO/6oqfBx4OvxIOfCpgJWYVHswRXnbEu9Q==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "eth-lib": "0.2.8", + "ethereum-bloom-filters": "^1.0.6", + "ethjs-unit": "0.1.6", + "number-to-bn": "1.7.0", + "randombytes": "^2.1.0", + "utf8": "3.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "window-size": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz", + "integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "dev": true + }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dev": true, + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xhr-request": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xhr-request/-/xhr-request-1.1.0.tgz", + "integrity": "sha512-Y7qzEaR3FDtL3fP30k9wO/e+FBnBByZeybKOhASsGP30NIkRAAkKD/sCnLvgEfAIEC1rcmK7YG8f4oEnIrrWzA==", + "dev": true, + "requires": { + "buffer-to-arraybuffer": "^0.0.5", + "object-assign": "^4.1.1", + "query-string": "^5.0.1", + "simple-get": "^2.7.0", + "timed-out": "^4.0.1", + "url-set-query": "^1.0.0", + "xhr": "^2.0.4" + } + }, + "xhr-request-promise": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/xhr-request-promise/-/xhr-request-promise-0.1.3.tgz", + "integrity": "sha512-YUBytBsuwgitWtdRzXDDkWAXzhdGB8bYm0sSzMPZT7Z2MBjMSTHFsyCT1yCRATY+XC69DUrQraRAEgcoCRaIPg==", + "dev": true, + "requires": { + "xhr-request": "^1.1.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..f4aea4a0 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "gambit-contracts", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "npx hardhat test", + "compile": "hardhat compile", + "typechain": "hardhat typechain" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@nomiclabs/hardhat-ethers": "^2.0.1", + "@nomiclabs/hardhat-etherscan": "^2.1.5", + "@nomiclabs/hardhat-waffle": "^2.0.1", + "@typechain/ethers-v5": "^7.0.1", + "@typechain/hardhat": "^2.3.0", + "chai": "^4.3.0", + "ethereum-waffle": "^3.3.0", + "ethers": "^5.4.6", + "hardhat": "^2.6.1", + "hardhat-contract-sizer": "^2.0.3", + "typechain": "^5.1.2" + }, + "dependencies": { + "bn.js": "^5.2.0" + } +} diff --git a/scripts/access/deployTokenManager.js b/scripts/access/deployTokenManager.js new file mode 100644 index 00000000..732a2389 --- /dev/null +++ b/scripts/access/deployTokenManager.js @@ -0,0 +1,20 @@ +const { deployContract, contractAt, writeTmpAddresses, sendTxn } = require("../shared/helpers") + +async function main() { + const tokenManager = await deployContract("TokenManager", [2], "TokenManager") + + const signers = [ + "0x3D850Acfaa18c58b383fCA69d4d867Dc5Bb697c5", // Ben Simon + "0x881690382102106b00a99E3dB86056D0fC71eee6", // Han Wen + "0x2e5d207a4c0f7e7c52f6622dcc6eb44bc0fe1a13" // Krunal Amin + ] + + await sendTxn(tokenManager.initialize(signers), "tokenManager.initialize") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/addPluginOrderBook.js b/scripts/core/addPluginOrderBook.js new file mode 100644 index 00000000..86785568 --- /dev/null +++ b/scripts/core/addPluginOrderBook.js @@ -0,0 +1,17 @@ +const { contractAt , sendTxn, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const router = await callWithRetries(contractAt, ["Router", "0x526f42EA12E167E36E3De747187f53b6989d121A"]) + + await sendTxn(callWithRetries(router.addPlugin.bind(router), [ + "0x84B1FEA4A2c1e0C07f34755ac4cf5aD26a07485d" + ]), "router.addPlugin") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/checkAdjustments.js b/scripts/core/checkAdjustments.js new file mode 100644 index 00000000..d0b90580 --- /dev/null +++ b/scripts/core/checkAdjustments.js @@ -0,0 +1,28 @@ +const { deployContract, contractAt , sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const gov = await contractAt("Timelock", "0xa870b459ba1f206bbcb0df90ef887b19fcde66ae") + const vaultPriceFeed = await contractAt("VaultPriceFeed", "0x82b1fa2741a6591d30e61830b1cfda0e7ba3abd3") + const tokenKeys = ["btc", "eth", "bnb"] + + for (let i = 0; i < tokenKeys.length; i++) { + const key = tokenKeys[i] + const token = tokens[key] + const adjustmentBasisPoints = await vaultPriceFeed.adjustmentBasisPoints(token.address) + const isAdditive = await vaultPriceFeed.isAdjustmentAdditive(token.address) + + console.log(`${key}: ${isAdditive ? "+" : "-"}${adjustmentBasisPoints}`) + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/checkAmounts.js b/scripts/core/checkAmounts.js new file mode 100644 index 00000000..ae70810a --- /dev/null +++ b/scripts/core/checkAmounts.js @@ -0,0 +1,59 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const router = await contractAt("Router", "0xD46B23D042E976F8666F554E928e0Dc7478a8E1f") + const vault = await contractAt("Vault", "0xc73A8DcAc88498FD4b4B1b2AaA37b0a2614Ff67B") + const gov = await contractAt("Timelock", "0x330EeF6b9B1ea6EDd620C825c9919DC8b611d5d5") + + const tokenDecimals = 18 + + const btc = { + symbol: "BTC", + address: "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c" + } + const eth = { + symbol: "ETH", + address: "0x2170ed0880ac9a755fd29b2688956bd959f933f8" + } + const bnb = { + symbol: "BNB", + address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" + } + const busd = { + symbol: "BUSD", + address: "0xe9e7cea3dedca5984780bafc599bd69add087d56" + } + const usdc = { + symbol: "USDC", + address: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d" + } + const usdt = { + symbol: "USDT", + address: "0x55d398326f99059fF775485246999027B3197955" + } + + const tokens = [btc, eth, bnb, busd, usdc, usdt] + + for (let i = 0; i < tokens.length; i++) { + const token = await contractAt("YieldToken", tokens[i].address) + const poolAmount = await vault.poolAmounts(token.address) + const feeReserve = await vault.feeReserves(token.address) + const balance = await token.balanceOf(vault.address) + const vaultAmount = poolAmount.add(feeReserve) + if (vaultAmount.gt(balance)) { + const diff = vaultAmount.sub(balance) + console.log(`${token.address}: vaultAmount.gt(balance): ${ethers.utils.formatUnits(diff, 18)} ${tokens[i].symbol}`) + } else { + const diff = balance.sub(vaultAmount) + console.log(`${token.address}: vaultAmount.lt(balance): ${ethers.utils.formatUnits(diff, 18)} ${tokens[i].symbol}`) + } + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/checkPriceFeeds.js b/scripts/core/checkPriceFeeds.js new file mode 100644 index 00000000..716abcd3 --- /dev/null +++ b/scripts/core/checkPriceFeeds.js @@ -0,0 +1,66 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const btcPriceFeed = await contractAt("PriceFeed", "0x264990fbd0A4796A3E3d8E37C4d5F87a3aCa5Ebf") + const ethPriceFeed = await contractAt("PriceFeed", "0x9ef1B8c0E4F7dc8bF5719Ea496883DC6401d5b2e") + const bnbPriceFeed = await contractAt("PriceFeed", "0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE") + const busdPriceFeed = await contractAt("PriceFeed", "0xcBb98864Ef56E9042e7d2efef76141f15731B82f") + const usdcPriceFeed = await contractAt("PriceFeed", "0x51597f405303C4377E36123cBc172b13269EA163") + const usdtPriceFeed = await contractAt("PriceFeed", "0xB97Ad0E74fa7d920791E90258A6E2085088b4320") + const priceDecimals = 8 + + const btc = { + symbol: "BTC", + address: "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c", + priceFeed: btcPriceFeed + } + const eth = { + symbol: "ETH", + address: "0x2170ed0880ac9a755fd29b2688956bd959f933f8", + priceFeed: ethPriceFeed + } + const bnb = { + symbol: "BNB", + address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + priceFeed: bnbPriceFeed + } + const busd = { + symbol: "BUSD", + address: "0xe9e7cea3dedca5984780bafc599bd69add087d56", + priceFeed: busdPriceFeed + } + const usdc = { + symbol: "USDC", + address: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + priceFeed: usdcPriceFeed + } + const usdt = { + symbol: "USDT", + address: "0x55d398326f99059fF775485246999027B3197955", + priceFeed: usdtPriceFeed + } + + const tokens = [btc, eth, bnb, busd, usdc, usdt] + + const now = parseInt(Date.now() / 1000) + + for (let i = 0; i < tokens.length; i++) { + const { symbol, priceFeed } = tokens[i] + const latestRound = await priceFeed.latestRound() + + for (let j = 0; j < 5; j++) { + const roundData = await priceFeed.getRoundData(latestRound.sub(j)) + const answer = roundData[1] + const updatedAt = roundData[3] + console.log(`${symbol} ${j}: ${ethers.utils.formatUnits(answer, priceDecimals)}, ${updatedAt}, ${updatedAt.sub(now).toString()}s, ${updatedAt.sub(now).div(60).toString()}m`) + } + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/checkPrices.js b/scripts/core/checkPrices.js new file mode 100644 index 00000000..9275107f --- /dev/null +++ b/scripts/core/checkPrices.js @@ -0,0 +1,58 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const vaultPriceFeed1 = await contractAt("VaultPriceFeed", "0x776d204065e465168eC469DCd0872609154bb505") + const vaultPriceFeed2 = await contractAt("VaultPriceFeed", "0x1CF4579904EB2ACDA0E4081E39eC10d0c32B5DE3") + const usdDecimals = 30 + + const { btc, eth, usdc, link, uni, usdt } = tokens + const tokenArr = [btc, eth, usdc, link, uni, usdt] + + for (let i = 0; i < tokenArr.length; i++) { + const token = tokenArr[i] + const maxPrice1 = await vaultPriceFeed1.getPrice(token.address, true, true, true) + const maxPrice2 = await vaultPriceFeed2.getPrice(token.address, true, true, true) + const minPrice1 = await vaultPriceFeed1.getPrice(token.address, false, true, true) + const minPrice2 = await vaultPriceFeed2.getPrice(token.address, false, true, true) + const diff1 = maxPrice1.sub(minPrice1) + const diff2 = maxPrice2.sub(minPrice2) + const spread1 = diff1.mul(1000000).div(minPrice1) + const spread2 = diff2.mul(1000000).div(minPrice2) + + const delta1 = maxPrice1.gt(maxPrice2) ? maxPrice1.sub(maxPrice2) : maxPrice2.sub(maxPrice1) + const delta2 = minPrice1.gt(minPrice2) ? minPrice1.sub(minPrice2) : minPrice2.sub(minPrice1) + const deltaBps1 = delta1.mul(1000000).div(maxPrice1) + const deltaBps2 = delta2.mul(1000000).div(minPrice1) + console.log(`------------ ${token.name} ------------`) + console.log("\n1.") + console.log(`max1: ${ethers.utils.formatUnits(maxPrice1, usdDecimals)}`) + console.log(`min1: ${ethers.utils.formatUnits(minPrice1, usdDecimals)}`) + console.log(`diff1: ${ethers.utils.formatUnits(diff1, usdDecimals)}`) + console.log(`spread1: ${ethers.utils.formatUnits(spread1, 4)}`) + console.log("\n2.") + console.log(`max2: ${ethers.utils.formatUnits(maxPrice2, usdDecimals)}`) + console.log(`min2: ${ethers.utils.formatUnits(minPrice2, usdDecimals)}`) + console.log(`diff2: ${ethers.utils.formatUnits(diff2, usdDecimals)}`) + console.log(`spread2: ${ethers.utils.formatUnits(spread2, 4)}`) + console.log("\n3.") + console.log(`delta1: ${ethers.utils.formatUnits(deltaBps1, 4)}`) + console.log(`delta2: ${ethers.utils.formatUnits(deltaBps2, 4)}`) + if (parseFloat(ethers.utils.formatUnits(deltaBps1, 4)) > 0.7) { + throw new Error("delta1 exceeds threshold") + } + if (parseFloat(ethers.utils.formatUnits(deltaBps2, 4)) > 0.7) { + throw new Error("delta2 exceeds threshold") + } + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/checkTokenConfigs.js b/scripts/core/checkTokenConfigs.js new file mode 100644 index 00000000..69cc8cbd --- /dev/null +++ b/scripts/core/checkTokenConfigs.js @@ -0,0 +1,42 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const { btc, eth, usdc, link, uni, usdt } = tokens + const tokenArr = [btc, eth, usdc, link, uni, usdt] + + for (let i = 0; i < tokenArr.length; i++) { + const tokenInfo = tokenArr[i] + const token = await contractAt("Token", tokenInfo.address) + const name = await token.name() + const symbol = await token.symbol() + const decimals = await token.decimals() + const priceFeed = await contractAt("PriceFeed", tokenInfo.priceFeed) + const priceDecimals = await priceFeed.decimals() + + if (tokenInfo.decimals.toString() !== decimals.toString()) { + throw new Error(`Invalid decimals, ${tokenInfo.decimals.toString()}, ${decimals.toString()}`) + } + + if (tokenInfo.priceDecimals.toString() !== priceDecimals.toString()) { + throw new Error(`Invalid price decimals, ${tokenInfo.priceDecimals.toString()}, ${priceDecimals.toString()}`) + } + + console.log(`${tokenInfo.name}, ${name}, ${symbol}, ${token.address}`) + console.log(`token decimals: ${tokenInfo.decimals}, ${decimals.toString()}`) + console.log(`price decimals: ${tokenInfo.priceDecimals}, ${priceDecimals.toString()}`) + console.log(`price feed: ${await priceFeed.description()}`) + console.log(`isShortable: ${tokenInfo.isShortable}, isStable: ${tokenInfo.isStable}, isStrictStable: ${tokenInfo.isStrictStable}`) + console.log("\n-------\n") + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/claimTokens.js b/scripts/core/claimTokens.js new file mode 100644 index 00000000..d8cd6e55 --- /dev/null +++ b/scripts/core/claimTokens.js @@ -0,0 +1,34 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const receiver = { address: "0x9f169c2189A2d975C18965DE985936361b4a9De9" } + const usdg = await contractAt("YieldToken", "0x85E76cbf4893c1fbcB34dCF1239A91CE2A4CF5a7") + const xgmt = await contractAt("YieldToken", "0xe304ff0983922787Fd84BC9170CD21bF78B16B10") + const usdgYieldTracker = await contractAt("YieldTracker", "0x0EF0Cf825B8e9F89A43FfD392664131cFB4cfA89") + const xgmtYieldTracker = await contractAt("YieldTracker", "0x82A012A9b3003b18B6bCd6052cbbef7Fa4892e80") + const gmtUsdgPair = { address: "0xa41e57459f09a126F358E118b693789d088eA8A0" } + const xgmtUsdgPair = { address: "0x0b622208fc0691C2486A3AE6B7C875b4A174b317" } + const busdgUsdgPair = { address: "0x7Fea0c6022D81EE17146324E4F55f6A02E138Dab" } + const autoUsdgPair = { address: "0x0523FD5C53ea5419B4DAF656BC1b157dDFE3ce50" } + + const wbnbClaimableForXgmtPair = await xgmtYieldTracker.claimable(xgmtUsdgPair.address) + console.log(`claimable: ${ethers.utils.formatUnits(wbnbClaimableForXgmtPair, 18)} WBNB`) + await sendTxn(xgmt.recoverClaim(xgmtUsdgPair.address, receiver.address), "recoverClaim") + + const accounts = [gmtUsdgPair, xgmtUsdgPair, busdgUsdgPair, autoUsdgPair] + + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i] + const claimable = await usdgYieldTracker.claimable(account.address) + console.log(`claimable ${i}: ${ethers.utils.formatUnits(claimable, 18)} WBNB`) + await sendTxn(usdg.recoverClaim(account.address, receiver.address), `recoverClaim ${i}`) + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/deployGlpManager.js b/scripts/core/deployGlpManager.js new file mode 100644 index 00000000..6d49b2bf --- /dev/null +++ b/scripts/core/deployGlpManager.js @@ -0,0 +1,35 @@ +const { deployContract, contractAt , sendTxn, writeTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const { + nativeToken + } = tokens + + const vault = await contractAt("Vault", "0xDE3590067c811b6F023b557ed45E4f1067859663") + const usdg = await contractAt("USDG", "0x45096e7aA921f27590f8F19e457794EB09678141") + const glp = await contractAt("GLP", "0x4277f8F2c384827B5273592FF7CeBd9f2C1ac258") + + const glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 15 * 60]) + + await sendTxn(glpManager.setInPrivateMode(true), "glpManager.setInPrivateMode") + + await sendTxn(glp.setMinter(glpManager.address, true), "glp.setMinter") + await sendTxn(usdg.addVault(glpManager.address), "usdg.addVault") + await sendTxn(vault.setManager(glpManager.address, true), "vault.setManager") + + writeTmpAddresses({ + glpManager: glpManager.address + }) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/deployOrderBook.js b/scripts/core/deployOrderBook.js new file mode 100644 index 00000000..97ef6281 --- /dev/null +++ b/scripts/core/deployOrderBook.js @@ -0,0 +1,27 @@ +const { deployContract, contractAt , sendTxn, writeTmpAddresses } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const orderBook = await deployContract("OrderBook", []); + + // Arbitrum mainnet addresses + await sendTxn(orderBook.initialize( + "0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064", // router + "0x489ee077994B6658eAfA855C308275EAd8097C4A", // vault + "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // weth + "0x0000000000000000000000000000000000000000", // usdg + expandDecimals(500000, 9), // 0,0005 ETH + expandDecimals(10, 30) // min purchase token amount usd + )); + + writeTmpAddresses({ + orderBook: orderBook.address + }) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/deployPriceFeed.js b/scripts/core/deployPriceFeed.js new file mode 100644 index 00000000..67dc9a83 --- /dev/null +++ b/scripts/core/deployPriceFeed.js @@ -0,0 +1,59 @@ +const { deployContract, contractAt , sendTxn, readTmpAddresses, writeTmpAddresses } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + // const tmpAddresses = readTmpAddresses() + + // const vault = await contractAt("Vault", tmpAddresses.vault) + // const vaultPriceFeed = await contractAt("VaultPriceFeed", "0x30333ce00AC3025276927672aAeFd80f22E89E54") + + // console.log("vault", vault.address) + // console.log("vaultPriceFeed", vaultPriceFeed.address) + + const signers = ["0xFb11f15f206bdA02c224EDC744b0E50E46137046"] + const fastPriceFeedGov = { address: "0xEBa076279F41571f7860661eE36665b54F4cA315" } + + const chainlinkFlags = { address: "0x3C14e07Edd0dC67442FA96f1Ec6999c57E810a83" } + const secondaryPriceFeed = await deployContract("FastPriceFeed", [5 * 60, 250]) + await sendTxn(secondaryPriceFeed.initialize(1, signers), "secondaryPriceFeed.initialize") + + // const secondaryPriceFeed = await contractAt("FastPriceFeed", "0xfAf8C044FF7A040fBc2c48fC6c3180c1d02f91d1") + const vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await sendTxn(vaultPriceFeed.setMaxStrictPriceDeviation(expandDecimals(5, 28)), "vaultPriceFeed.setMaxStrictPriceDeviation") // 0.05 USD + await sendTxn(vaultPriceFeed.setPriceSampleSpace(1), "vaultPriceFeed.setPriceSampleSpace") + await sendTxn(vaultPriceFeed.setSecondaryPriceFeed(secondaryPriceFeed.address), "vaultPriceFeed.setSecondaryPriceFeed") + await sendTxn(vaultPriceFeed.setIsAmmEnabled(false), "vaultPriceFeed.setIsAmmEnabled") + await sendTxn(vaultPriceFeed.setChainlinkFlags(chainlinkFlags.address), "vaultPriceFeed.setChainlinkFlags") + + const { btc, eth, usdc, link, uni, usdt } = tokens + const tokenArr = [btc, eth, usdc, link, uni, usdt] + + for (const token of tokenArr) { + await sendTxn(vaultPriceFeed.setTokenConfig( + token.address, // _token + token.priceFeed, // _priceFeed + token.priceDecimals, // _priceDecimals + token.isStrictStable // _isStrictStable + ), `vaultPriceFeed.setTokenConfig(${token.name}) ${token.address} ${token.priceFeed}`) + } + + await sendTxn(secondaryPriceFeed.setTokens([btc.address, eth.address, link.address, uni.address]), "secondaryPriceFeed.setTokens") + await sendTxn(secondaryPriceFeed.setGov(fastPriceFeedGov.address), "secondaryPriceFeed.setGov") + // await sendTxn(vault.setPriceFeed(vaultPriceFeed.address), "vault.setPriceFeed") + + writeTmpAddresses({ + vaultPriceFeed: vaultPriceFeed.address + }) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/deployVault.js b/scripts/core/deployVault.js new file mode 100644 index 00000000..d58893b0 --- /dev/null +++ b/scripts/core/deployVault.js @@ -0,0 +1,82 @@ +const { deployContract, contractAt , sendTxn, writeTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") +const { errors } = require("../../test/core/Vault/helpers") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const { + nativeToken + } = tokens + + const usdg = await contractAt("USDG", "0x45096e7aA921f27590f8F19e457794EB09678141") + const glp = await contractAt("GLP", "0x4277f8F2c384827B5273592FF7CeBd9f2C1ac258") + + // const vault = await deployContract("Vault", []) + const vault = await contractAt("Vault", "0x489ee077994B6658eAfA855C308275EAd8097C4A") + // const router = await deployContract("Router", [vault.address, usdg.address, nativeToken.address]) + const router = await contractAt("Router", "0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064") + const vaultPriceFeed = await contractAt("VaultPriceFeed", "0x30333ce00ac3025276927672aaefd80f22e89e54") + // const secondaryPriceFeed = await deployContract("FastPriceFeed", [5 * 60]) + + // await sendTxn(usdg.addVault(vault.address), "usdg.addVault(vault)") + + const glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 15 * 60]) + await sendTxn(glpManager.setInPrivateMode(true), "glpManager.setInPrivateMode") + + await sendTxn(glp.setMinter(glpManager.address, true), "glp.setMinter") + await sendTxn(usdg.addVault(glpManager.address), "usdg.addVault(glpManager)") + + await sendTxn(vault.initialize( + router.address, // router + usdg.address, // usdg + vaultPriceFeed.address, // priceFeed + toUsd(2), // liquidationFeeUsd + 100, // fundingRateFactor + 100 // stableFundingRateFactor + ), "vault.initialize") + + await sendTxn(vault.setFundingRate(60 * 60, 100, 100), "vault.setFundingRate") + + await sendTxn(vault.setInManagerMode(true), "vault.setInManagerMode") + await sendTxn(vault.setManager(glpManager.address, true), "vault.setManager") + + // await sendTxn(vaultPriceFeed.setMaxStrictPriceDeviation(expandDecimals(5, 28)), "vaultPriceFeed.setMaxStrictPriceDeviation") // 0.05 USD + // await sendTxn(vaultPriceFeed.setPriceSampleSpace(1), "vaultPriceFeed.setPriceSampleSpace") + // await sendTxn(vaultPriceFeed.setSecondaryPriceFeed(secondaryPriceFeed.address), "vaultPriceFeed.setSecondaryPriceFeed") + // await sendTxn(vaultPriceFeed.setIsAmmEnabled(false), "vaultPriceFeed.setIsAmmEnabled") + + await sendTxn(vault.setFees( + 10, // _taxBasisPoints + 5, // _stableTaxBasisPoints + 20, // _mintBurnFeeBasisPoints + 20, // _swapFeeBasisPoints + 1, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(2), // _liquidationFeeUsd + 24 * 60 * 60, // _minProfitTime + true // _hasDynamicFees + ), "vault.setFees") + + const vaultErrorController = await deployContract("VaultErrorController", []) + await sendTxn(vault.setErrorController(vaultErrorController.address), "vault.setErrorController") + await sendTxn(vaultErrorController.setErrors(vault.address, errors), "vaultErrorController.setErrors") + + writeTmpAddresses({ + vault: vault.address, + router: router.address, + vaultPriceFeed: vaultPriceFeed.address, + usdg: usdg.address, + glp: glp.address, + glpManager: glpManager.address + }) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/directPoolDeposit.js b/scripts/core/directPoolDeposit.js new file mode 100644 index 00000000..5eeead19 --- /dev/null +++ b/scripts/core/directPoolDeposit.js @@ -0,0 +1,21 @@ +const { deployContract, contractAt, sendTxn, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const token = await contractAt("Token", "0x82af49447d8a07e3bd95bd0d56f35241523fbab1") + const router = await contractAt("Router", "0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064") + // const amount = expandDecimals(3000, 6) + const amount = "300000000000000000" + await sendTxn(token.approve(router.address, amount), "router.approve") + await sendTxn(router.directPoolDeposit(token.address, amount), "router.directPoolDeposit") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/removeGlpManager.js b/scripts/core/removeGlpManager.js new file mode 100644 index 00000000..37096a75 --- /dev/null +++ b/scripts/core/removeGlpManager.js @@ -0,0 +1,29 @@ +const { deployContract, contractAt , sendTxn, writeTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const { + nativeToken + } = tokens + + const vault = await contractAt("Vault", "0xDE3590067c811b6F023b557ed45E4f1067859663") + const usdg = await contractAt("USDG", "0x45096e7aA921f27590f8F19e457794EB09678141") + const glp = await contractAt("GLP", "0x4277f8F2c384827B5273592FF7CeBd9f2C1ac258") + + const glpManager = await contractAt("GlpManager", "0x91425Ac4431d068980d497924DD540Ae274f3270") + + await sendTxn(glp.setMinter(glpManager.address, false), "glp.setMinter") + await sendTxn(usdg.removeVault(glpManager.address), "usdg.removeVault") + await sendTxn(vault.setManager(glpManager.address, false), "vault.setManager") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/removeVault.js b/scripts/core/removeVault.js new file mode 100644 index 00000000..c4888965 --- /dev/null +++ b/scripts/core/removeVault.js @@ -0,0 +1,20 @@ +const { deployContract, contractAt , sendTxn, writeTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const usdg = await contractAt("USDG", "0x45096e7aA921f27590f8F19e457794EB09678141") + const vault = await contractAt("Vault", "0xDE3590067c811b6F023b557ed45E4f1067859663") + + await sendTxn(usdg.removeVault(vault.address), "usdg.removeVault") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/setAdjustments.js b/scripts/core/setAdjustments.js new file mode 100644 index 00000000..e1fc9947 --- /dev/null +++ b/scripts/core/setAdjustments.js @@ -0,0 +1,39 @@ +const { deployContract, contractAt , sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const gov = await contractAt("Timelock", "0xa870b459ba1f206bbcb0df90ef887b19fcde66ae") + const vaultPriceFeed = await contractAt("VaultPriceFeed", "0x82b1fa2741a6591d30e61830b1cfda0e7ba3abd3") + + await sendTxn(gov.setAdjustment( + vaultPriceFeed.address, + tokens.btc.address, + false, + 0 + ), "gov.setAdjustment(btc)") + + await sendTxn(gov.setAdjustment( + vaultPriceFeed.address, + tokens.eth.address, + false, + 0 + ), "gov.setAdjustment(eth)") + + await sendTxn(gov.setAdjustment( + vaultPriceFeed.address, + tokens.bnb.address, + false, + 0 + ), "gov.setAdjustment(bnb)") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/setFees.js b/scripts/core/setFees.js new file mode 100644 index 00000000..000dc278 --- /dev/null +++ b/scripts/core/setFees.js @@ -0,0 +1,29 @@ +const { deployContract, contractAt , sendTxn, writeTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const vault = await contractAt("Vault", "0xDE3590067c811b6F023b557ed45E4f1067859663") + + await sendTxn(vault.setFees( + 10, // _taxBasisPoints + 5, // _stableTaxBasisPoints + 20, // _mintBurnFeeBasisPoints + 20, // _swapFeeBasisPoints + 1, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(1), // _liquidationFeeUsd + 24 * 60 * 60, // _minProfitTime + true // _hasDynamicFees + ), "vault.setFees") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/setPrices.js b/scripts/core/setPrices.js new file mode 100644 index 00000000..2636ffe0 --- /dev/null +++ b/scripts/core/setPrices.js @@ -0,0 +1,26 @@ +const { deployContract, contractAt , sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const secondaryPriceFeed = await contractAt("FastPriceFeed", "0x06588aad1eCc1275CBF68ab192257714ac1ed89c") + const vaultPriceFeed = await contractAt("VaultPriceFeed", "0x82B1Fa2741a6591D30E61830b1CfDA0E7ba3ABd3") + + // await sendTxn(vaultPriceFeed.setIsAmmEnabled(false), "vaultPriceFeed.setIsAmmEnabled") + // console.log("vaultPriceFeed.isSecondaryPriceEnabled", await vaultPriceFeed.isSecondaryPriceEnabled()) + + await sendTxn(secondaryPriceFeed.setPrices( + [tokens.btc.address, tokens.eth.address, tokens.bnb.address], + [expandDecimals(35000, 30), expandDecimals(4000, 30), expandDecimals(310, 30)] + ), "secondaryPriceFeed.setPrices") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/setTokenConfig.js b/scripts/core/setTokenConfig.js new file mode 100644 index 00000000..26b11da0 --- /dev/null +++ b/scripts/core/setTokenConfig.js @@ -0,0 +1,34 @@ +const { deployContract, contractAt, sendTxn, readTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toChainlinkPrice } = require("../../test/shared/chainlink") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const vault = await contractAt("Vault", "0x489ee077994B6658eAfA855C308275EAd8097C4A") + const timelock = await contractAt("Timelock", "0xbb8614a9ad437739c9910a9cb2254c608aa7fdb4") + + console.log("vault", vault.address) + console.log("timelock", timelock.address) + + const { btc, eth, usdc, link, uni, usdt } = tokens + const tokenArr = [btc, eth, usdc, link, uni, usdt] + + for (const token of tokenArr) { + await sendTxn(timelock.setTokenConfig( + vault.address, + token.address, // _token + token.tokenWeight, // _tokenWeight + token.minProfitBps, // _minProfitBps + token.maxUsdgAmount // _maxUsdgAmount + ), `vault.setTokenConfig(${token.name}) ${token.address}`) + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/setVaultConfig.js b/scripts/core/setVaultConfig.js new file mode 100644 index 00000000..d52516b8 --- /dev/null +++ b/scripts/core/setVaultConfig.js @@ -0,0 +1,25 @@ +const { contractAt , sendTxn, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const vault = await callWithRetries(contractAt, ["Vault", "0xA4704fBfaf7c89511668052931Ba0f1816D2c9d3"]) + + await sendTxn(callWithRetries(vault.setFees.bind(vault), [ + 10, // taxBasisPoints, + 10, // stableTaxBasisPoints, + 10, // mintBurnFeeBasisPoints, + 10, // swapFeeBasisPoints, + 10, // stableSwapFeeBasisPoints, + 10, // marginFeeBasisPoints, + expandDecimals(1, 30), // 1 USD, liquidationFeeUsd, + 3600, // 1 hour, minProfitTime, + true // hasDynamicFees + ]), "vault.setFees") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/setupDistributors.js b/scripts/core/setupDistributors.js new file mode 100644 index 00000000..a111b22c --- /dev/null +++ b/scripts/core/setupDistributors.js @@ -0,0 +1,54 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const usdg = await contractAt("YieldToken", "0x85E76cbf4893c1fbcB34dCF1239A91CE2A4CF5a7") + const tokenDecimals = 18 + const wbnb = await contractAt("YieldToken", "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c") + + const farms = [ + { + farm: usdg, + yieldTrackerIndex: 0, + transferAmount: "35.0", + shouldClaim: true + } + ] + + for (let i = 0; i < farms.length; i++) { + console.log(`---------- ${i} ----------`) + const { farm, yieldTrackerIndex, transferAmount, shouldClaim } = farms[i] + const convertedTransferAmount = ethers.utils.parseUnits(transferAmount, tokenDecimals) + console.log("convertedTransferAmount", convertedTransferAmount.toString()) + const rewardsPerInterval = convertedTransferAmount.div(168) + console.log("rewardsPerInterval", rewardsPerInterval.toString()) + + const yieldTrackerAddress = await farm.yieldTrackers(yieldTrackerIndex) + console.log("yieldTrackerAddress", yieldTrackerAddress) + const yieldTracker0 = await contractAt("YieldTracker", yieldTrackerAddress) + const distributorAddress = await yieldTracker0.distributor() + console.log("distributorAddress", distributorAddress) + const distributor = await contractAt("TimeDistributor", distributorAddress) + const rewardToken = await distributor.rewardTokens(yieldTracker0.address) + console.log("rewardToken", rewardToken) + const tokensPerInterval = await distributor.tokensPerInterval(yieldTracker0.address) + console.log("tokensPerInterval", tokensPerInterval.toString()) + const lastDistributionTime = await distributor.lastDistributionTime(yieldTracker0.address) + console.log("lastDistributionTime", lastDistributionTime.toString()) + + if (shouldClaim) { + await sendTxn(farm.claim("0x9f169c2189A2d975C18965DE985936361b4a9De9"), `farm.claim ${i}`) + } + if (convertedTransferAmount.gt(0)) { + await sendTxn(wbnb.transfer(distributorAddress, convertedTransferAmount), `wbnb.transfer ${i}`) + } + await sendTxn(distributor.setTokensPerInterval(yieldTrackerAddress, rewardsPerInterval), `distributor.setTokensPerInterval ${i}`) + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/tokens.js b/scripts/core/tokens.js new file mode 100644 index 00000000..35dbf63e --- /dev/null +++ b/scripts/core/tokens.js @@ -0,0 +1,189 @@ +// price feeds https://docs.chain.link/docs/binance-smart-chain-addresses/ + +module.exports = { + bsc: { + btcPriceFeed: { address: "0x264990fbd0A4796A3E3d8E37C4d5F87a3aCa5Ebf" }, + ethPriceFeed: { address: "0x9ef1B8c0E4F7dc8bF5719Ea496883DC6401d5b2e" }, + bnbPriceFeed: { address: "0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE" }, + busdPriceFeed: { address: "0xcBb98864Ef56E9042e7d2efef76141f15731B82f" }, + usdcPriceFeed: { address: "0x51597f405303C4377E36123cBc172b13269EA163" }, + usdtPriceFeed: { address: "0xB97Ad0E74fa7d920791E90258A6E2085088b4320" }, + btc: { + address: "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c", + decimals: 18 + }, + eth: { + address: "0x2170ed0880ac9a755fd29b2688956bd959f933f8", + decimals: 18 + }, + bnb: { + address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + decimals: 18 + }, + busd: { + address: "0xe9e7cea3dedca5984780bafc599bd69add087d56", + decimals: 18 + }, + usdc: { + address: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", + decimals: 18 + + }, + usdt: { + address: "0x55d398326f99059fF775485246999027B3197955", + decimals: 18 + }, + nativeToken: { + address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", + decimals: 18 + } + }, + testnet: { + btcPriceFeed: { address: "0x5741306c21795FdCBb9b265Ea0255F499DFe515C" }, + ethPriceFeed: { address: "0x143db3CEEfbdfe5631aDD3E50f7614B6ba708BA7" }, + bnbPriceFeed: { address: "0x2514895c72f50D8bd4B4F9b1110F0D6bD2c97526" }, + busdPriceFeed: { address: "0x8F460c4F4Fa9F87AeA4f29B4Ee91d1b8e97163BA" }, + usdcPriceFeed: { address: " 0x90c069C4538adAc136E051052E14c1cD799C41B7" }, + usdtPriceFeed: { address: "0xEca2605f0BCF2BA5966372C99837b1F182d3D620" }, + btc: { + address: "0xb19C12715134bee7c4b1Ca593ee9E430dABe7b56", + decimals: 18 + }, + eth: { + address: "0x1958f7C067226c7C8Ac310Dc994D0cebAbfb2B02", + decimals: 18 + }, + bnb: { + address: "0x612777Eea37a44F7a95E3B101C39e1E2695fa6C2", + decimals: 18 + }, + busd: { + address: "0x3F223C4E5ac67099CB695834b20cCd5E5D5AA9Ef", + decimals: 18 + }, + usdc: { + address: "0x9780881bf45b83ee028c4c1de7e0c168df8e9eef", + decimals: 18 + }, + usdt: { + address: "0x337610d27c682e347c9cd60bd4b3b107c9d34ddd", + decimals: 18 + }, + nativeToken: { + address: "0x612777Eea37a44F7a95E3B101C39e1E2695fa6C2", + decimals: 18 + } + }, + arbitrumTestnet: { + // https://docs.chain.link/docs/arbitrum-price-feeds/ + btcPriceFeed: { address: "0x0c9973e7a27d00e656B9f153348dA46CaD70d03d" }, + ethPriceFeed: { address: "0x5f0423B1a6935dc5596e7A24d98532b67A0AeFd8" }, + usdtPriceFeed: { address: "0xb1Ac85E779d05C2901812d812210F6dE144b2df0" }, + usdcPriceFeed: { address: "0xb1Ac85E779d05C2901812d812210F6dE144b2df0" }, // this is USDT price feed, chainlink doesn't have one for USDC + btc: { + address: "0xab952e6801daB7920B65b8aC918FF0F66a8a0F44", + decimals: 18 + }, + eth: { + address: "0xB47e6A5f8b33b3F17603C83a0535A9dcD7E32681", + decimals: 18 + }, + usdc: { + address: "0xb93cb5F5c6a56e060A5e5A9691229D2a7e2D234A", + decimals: 18 + }, + usdt: { + address: "0xaB7ee1A7D5bc677e3A7ac694f2c156b3fFCaABC1", + decimals: 18 + }, + nativeToken: { + address: "0xB47e6A5f8b33b3F17603C83a0535A9dcD7E32681", + decimals: 18 + } + }, + arbitrum: { + btc: { + name: "btc", + address: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f", + decimals: 8, + priceFeed: "0x6ce185860a4963106506C203335A2910413708e9", + priceDecimals: 8, + isStrictStable: false, + tokenWeight: 20000, + minProfitBps: 150, + maxUsdgAmount: 0, + isStable: false, + isShortable: true + }, + eth: { + name: "eth", + address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", + decimals: 18, + priceFeed: "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612", + priceDecimals: 8, + isStrictStable: false, + tokenWeight: 20000, + minProfitBps: 150, + maxUsdgAmount: 0, + isStable: false, + isShortable: true + }, + usdc: { + name: "usdc", + address: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", + decimals: 6, + priceFeed: "0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3", + priceDecimals: 8, + isStrictStable: true, + tokenWeight: 40000, + minProfitBps: 150, + maxUsdgAmount: 0, + isStable: true, + isShortable: false + }, + link: { + name: "link", + address: "0xf97f4df75117a78c1A5a0DBb814Af92458539FB4", + decimals: 18, + priceFeed: "0x86E53CF1B870786351Da77A57575e79CB55812CB", + priceDecimals: 8, + isStrictStable: false, + tokenWeight: 5000, + minProfitBps: 150, + maxUsdgAmount: 0, + isStable: false, + isShortable: true + }, + uni: { + name: "uni", + address: "0xFa7F8980b0f1E64A2062791cc3b0871572f1F7f0", + decimals: 18, + priceFeed: "0x9C917083fDb403ab5ADbEC26Ee294f6EcAda2720", + priceDecimals: 8, + isStrictStable: false, + tokenWeight: 5000, + minProfitBps: 150, + maxUsdgAmount: 0, + isStable: false, + isShortable: true + }, + usdt: { + name: "usdt", + address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", + decimals: 6, + priceFeed: "0x3f3f5dF88dC9F13eac63DF89EC16ef6e7E25DdE7", + priceDecimals: 8, + isStrictStable: true, + tokenWeight: 10000, + minProfitBps: 150, + maxUsdgAmount: 0, + isStable: true, + isShortable: false + }, + nativeToken: { + name: "weth", + address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", + decimals: 18 + } + } +} diff --git a/scripts/core/updateAccess.js b/scripts/core/updateAccess.js new file mode 100644 index 00000000..6948a565 --- /dev/null +++ b/scripts/core/updateAccess.js @@ -0,0 +1,125 @@ +const { contractAt, sendTxn } = require("../shared/helpers") + +const wallet = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } +const timelock = { address: "0x4A3930B629F899fE19C1F280C73A376382d61A78" } + +async function printRewardTracker(rewardTracker, label) { + // console.log(label, "inPrivateTransferMode", await rewardTracker.inPrivateTransferMode()) + // console.log(label, "inPrivateStakingMode", await rewardTracker.inPrivateStakingMode()) + // console.log(label, "inPrivateClaimingMode", await rewardTracker.inPrivateClaimingMode()) + console.log(label, "isHandler", await rewardTracker.isHandler(wallet.address)) + console.log(label, "gov", await rewardTracker.gov()) +} + +async function updateHandler(rewardTracker, label) { + await sendTxn(rewardTracker.setHandler(wallet.address, false), `${label}, rewardTracker.setHandler`) +} + +async function printToken(token, label) { + console.log(label, "inPrivateTransferMode", await token.inPrivateTransferMode()) + console.log(label, "isHandler", await token.isHandler(wallet.address)) + console.log(label, "isMinter", await token.isMinter(wallet.address)) + console.log(label, "gov", await token.gov()) +} + +async function printUsdg(token, label) { + console.log(label, "isVault", await token.vaults(wallet.address)) + console.log(label, "gov", await token.gov()) +} + +async function updateToken(token, label) { + // await sendTxn(token.removeAdmin(wallet.address), `${label}, token.removeAdmin`) + await sendTxn(token.setMinter(wallet.address, false), `${label}, token.setMinter`) +} + +async function updateGov(contract, label) { + await sendTxn(contract.setGov(timelock.address), `${label}.setGov`) +} + +async function signalGov(prevGov, contract, nextGov, label) { + await sendTxn(prevGov.signalSetGov(contract.address, nextGov.address), `${label}.signalSetGov`) +} + +async function updateRewardTrackerGov(rewardTracker, label) { + const distributorAddress = await rewardTracker.distributor() + const distributor = await contractAt("RewardDistributor", distributorAddress) + await sendTxn(rewardTracker.setGov(timelock.address), `${label}.setGov`) + await sendTxn(distributor.setGov(timelock.address), `${label}.distributor.setGov`) +} + +async function main() { + const stakedGmxTracker = await contractAt("RewardTracker", "0x908C4D94D34924765f1eDc22A1DD098397c59dD4") + const bonusGmxTracker = await contractAt("RewardTracker", "0x4d268a7d4C16ceB5a606c173Bd974984343fea13") + const feeGmxTracker = await contractAt("RewardTracker", "0xd2D1162512F927a7e282Ef43a362659E4F2a728F") + + const stakedGlpTracker = await contractAt("RewardTracker", "0x1aDDD80E6039594eE970E5872D247bf0414C8903") + const feeGlpTracker = await contractAt("RewardTracker", "0x4e971a87900b931fF39d1Aad67697F49835400b6") + + // await printRewardTracker(stakedGmxTracker, "stakedGmxTracker") + // await printRewardTracker(bonusGmxTracker, "bonusGmxTracker") + // await printRewardTracker(feeGmxTracker, "feeGmxTracker") + // + // await printRewardTracker(stakedGlpTracker, "stakedGlpTracker") + // await printRewardTracker(feeGlpTracker, "feeGlpTracker") + + // await updateHandler(stakedGmxTracker, "stakedGmxTracker") + // await updateHandler(bonusGmxTracker, "bonusGmxTracker") + // await updateHandler(feeGmxTracker, "feeGmxTracker") + // await updateHandler(stakedGlpTracker, "stakedGlpTracker") + // await updateHandler(feeGlpTracker, "feeGlpTracker") + + const glp = await contractAt("MintableBaseToken", "0x4277f8F2c384827B5273592FF7CeBd9f2C1ac258") + const usdg = await contractAt("USDG", "0x45096e7aA921f27590f8F19e457794EB09678141") + const gmx = await contractAt("MintableBaseToken", "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a") + const esGmx = await contractAt("MintableBaseToken", "0xf42Ae1D54fd613C9bb14810b0588FaAa09a426cA") + const bnGmx = await contractAt("MintableBaseToken", "0x35247165119B69A40edD5304969560D0ef486921") + + // await printToken(glp, "glp") + // await printUsdg(usdg, "usdg") + // await printToken(gmx, "gmx") + // await printToken(esGmx, "esGmx") + // await printToken(bnGmx, "bnGmx") + + const prevGov = await contractAt("Timelock", "0x4a3930b629f899fe19c1f280c73a376382d61a78") + const nextGov = await contractAt("Timelock", "0x09214C0A3594fbcad59A58099b0A63E2B29b15B8") + + // await signalGov(prevGov, glp, nextGov, "glp") + // await signalGov(prevGov, gmx, nextGov, "gmx") + // await signalGov(prevGov, esGmx, nextGov, "esGmx") + await signalGov(prevGov, bnGmx, nextGov, "bnGmx") + + // await updateToken(gmx, "gmx") + // await updateToken(esGmx, "esGmx") + // await updateToken(bnGmx, "bnGmx") + + // await updateRewardTrackerGov(stakedGmxTracker, "stakedGmxTracker") + // await updateRewardTrackerGov(bonusGmxTracker, "bonusGmxTracker") + // await updateRewardTrackerGov(feeGmxTracker, "feeGmxTracker") + // + // await updateRewardTrackerGov(stakedGlpTracker, "stakedGlpTracker") + // await updateRewardTrackerGov(feeGlpTracker, "feeGlpTracker") + + // await updateGov(glp, "glp") + // await updateGov(usdg, "usdg") + // await updateGov(gmx, "gmx") + // await updateGov(esGmx, "esGmx") + // await updateGov(bnGmx, "bnGmx") + + // const vault = await contractAt("Vault", "0x489ee077994B6658eAfA855C308275EAd8097C4A") + // const vaultPriceFeedAddress = await vault.priceFeed() + // const vaultPriceFeed = await contractAt("VaultPriceFeed", vaultPriceFeedAddress) + // const glpManager = await contractAt("GlpManager", "0x321F653eED006AD1C29D174e17d96351BDe22649") + // const router = await contractAt("Router", "0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064") + // + // await updateGov(vault, "vault") + // await updateGov(vaultPriceFeed, "vaultPriceFeed") + // await updateGov(glpManager, "glpManager") + // await updateGov(router, "router") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/upgradeVault.js b/scripts/core/upgradeVault.js new file mode 100644 index 00000000..494e718c --- /dev/null +++ b/scripts/core/upgradeVault.js @@ -0,0 +1,31 @@ +const { deployContract, contractAt , sendTxn, writeTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toUsd } = require("../../test/shared/units") +const { errors } = require("../../test/core/Vault/helpers") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +// this should only be used for development +// mainnet contracts should be controller by a timelock +async function main() { + const wallet = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } + + const vault = await contractAt("Vault", "0xDE3590067c811b6F023b557ed45E4f1067859663") + const { eth, btc, usdc } = tokens + const tokenArr = [eth, btc, usdc] + for (let i = 0; i < tokenArr.length; i++) { + const tokenInfo = tokenArr[i] + const token = await contractAt("Token", tokenInfo.address) + const balance = await token.balanceOf(vault.address) + console.log(tokenInfo.name, balance.toString()) + await sendTxn(vault.upgradeVault(wallet.address, token.address, balance), `vault.upgradeVault(${tokenInfo.name})`) + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/vaultSetTokenConfig.js b/scripts/core/vaultSetTokenConfig.js new file mode 100644 index 00000000..eee56fba --- /dev/null +++ b/scripts/core/vaultSetTokenConfig.js @@ -0,0 +1,43 @@ +const { deployContract, contractAt, sendTxn, readTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toChainlinkPrice } = require("../../test/shared/chainlink") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const vault = await contractAt("Vault", "0x489ee077994B6658eAfA855C308275EAd8097C4A") + const timelock = await contractAt("Timelock", "0xbb8614a9ad437739c9910a9cb2254c608aa7fdb4") + const method = "signalVaultSetTokenConfig" + // const method = "vaultSetTokenConfig" + + console.log("vault", vault.address) + console.log("timelock", timelock.address) + console.log("method", method) + + const tokenWeight = 100 + const maxUsdgAmount = "50000000000000000000" + + const { link, uni, usdt } = tokens + const tokenArr = [link, uni, usdt] + + for (const token of tokenArr) { + await sendTxn(timelock[method]( + vault.address, + token.address, // _token + token.decimals, // _tokenDecimals + tokenWeight, // _tokenWeight + token.minProfitBps, // _minProfitBps + maxUsdgAmount, // _maxUsdgAmount + token.isStable, // _isStable + token.isShortable // _isShortable + ), `vault.signalVaultSetTokenConfig(${token.name}) ${token.address}`) + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/whitelistTokens.js b/scripts/core/whitelistTokens.js new file mode 100644 index 00000000..5bc17259 --- /dev/null +++ b/scripts/core/whitelistTokens.js @@ -0,0 +1,44 @@ +const { deployContract, contractAt, sendTxn, readTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { toChainlinkPrice } = require("../../test/shared/chainlink") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function main() { + const tmpAddresses = readTmpAddresses() + + const vault = await contractAt("Vault", tmpAddresses.vault) + const vaultPriceFeed = await contractAt("VaultPriceFeed", tmpAddresses.vaultPriceFeed) + console.log("vault", vault.address) + console.log("vaultPriceFeed", vaultPriceFeed.address) + + const { btc, eth, usdc } = tokens + const tokenArr = [btc, eth, usdc] + + for (const token of tokenArr) { + // await sendTxn(vaultPriceFeed.setTokenConfig( + // token.address, // _token + // priceFeed.address, // _priceFeed + // 8, // _priceDecimals + // isStrictStable // _isStrictStable + // ), `vaultPriceFeed.setTokenConfig(${name}) ${token.address} ${priceFeed.address}`) + + await sendTxn(vault.setTokenConfig( + token.address, // _token + token.decimals, // _tokenDecimals + token.tokenWeight, // _tokenWeight + token.minProfitBps, // _minProfitBps + token.maxUsdgAmount, // _maxUsdgAmount + token.isStable, // _isStable + token.isShortable // _isShortable + ), `vault.setTokenConfig(${token.name}) ${token.address}`) + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/withdrawFees.js b/scripts/core/withdrawFees.js new file mode 100644 index 00000000..37d4f583 --- /dev/null +++ b/scripts/core/withdrawFees.js @@ -0,0 +1,78 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('./tokens')[network]; + +async function withdrawFeesBsc() { + const receiver = { address: "0x9f169c2189A2d975C18965DE985936361b4a9De9" } + const vault = await contractAt("Vault", "0xc73A8DcAc88498FD4b4B1b2AaA37b0a2614Ff67B") + const gov = await contractAt("Timelock", "0x58d6e1675232496226d074502d0c2df383fa0cbe") + const balanceUpdater = await contractAt("BalanceUpdater", "0x912F4db2076079718D3b3A3Ab21F5Af22Bd1EDd3") + const usdg = await contractAt("Token", "0x85E76cbf4893c1fbcB34dCF1239A91CE2A4CF5a7") + + const btc = { address: "0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c" } + const eth = { address: "0x2170ed0880ac9a755fd29b2688956bd959f933f8" } + const bnb = { address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" } + const busd = { address: "0xe9e7cea3dedca5984780bafc599bd69add087d56" } + const usdc = { address: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d" } + const usdt = { address: "0x55d398326f99059fF775485246999027B3197955" } + + const tokens = [btc, eth, bnb, busd, usdc, usdt] + + for (let i = 0; i < tokens.length; i++) { + const token = await contractAt("Token", tokens[i].address) + const poolAmount = await vault.poolAmounts(token.address) + const feeReserve = await vault.feeReserves(token.address) + const balance = await token.balanceOf(vault.address) + const vaultAmount = poolAmount.add(feeReserve) + const acccountBalance = await token.balanceOf(receiver.address) + + if (vaultAmount.gt(balance)) { + const diff = vaultAmount.sub(balance) + console.log(`${token.address}: ${diff.toString()}, ${acccountBalance.toString()}`) + await sendTxn(balanceUpdater.updateBalance(vault.address, token.address, usdg.address, expandDecimals(1, 18)), `updateBalance ${i}`) + } + + await sendTxn(gov.withdrawFees(vault.address, token.address, receiver.address), `gov.withdrawFees ${i}`) + } +} + +async function withdrawFeesArb() { + const receiver = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } + const vault = await contractAt("Vault", "0x489ee077994B6658eAfA855C308275EAd8097C4A") + const gov = await contractAt("Timelock", "0xbb8614A9aD437739C9910a9CB2254C608Aa7fDB4") + const { btc, eth, usdc } = tokens + + const tokenArr = [btc, eth, usdc] + + for (let i = 0; i < tokenArr.length; i++) { + const token = await contractAt("Token", tokenArr[i].address) + const poolAmount = await vault.poolAmounts(token.address) + const feeReserve = await vault.feeReserves(token.address) + const balance = await token.balanceOf(vault.address) + const vaultAmount = poolAmount.add(feeReserve) + + if (vaultAmount.gt(balance)) { + throw new Error("vaultAmount > vault.balance") + } + + await sendTxn(gov.withdrawFees(vault.address, token.address, receiver.address), `gov.withdrawFees ${i}`) + } +} + +async function main() { + if (network === "bsc") { + await withdrawFeesBsc() + return + } + + await withdrawFeesArb() +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/core/write.js b/scripts/core/write.js new file mode 100644 index 00000000..741f0c11 --- /dev/null +++ b/scripts/core/write.js @@ -0,0 +1,30 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const frame = new ethers.providers.JsonRpcProvider("http://127.0.0.1:1248") + const signer = frame.getSigner() + + // const glpManager = await contractAt("GlpManager", "0x14fB4767dc9E10F96faaF37Ad24DE3E498cC344B") + // await sendTxn(glpManager.setCooldownDuration(10 * 60), "glpManager.setCooldownDuration") + const gmx = await contractAt("GMX", "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a", signer) + // const esGmx = await contractAt("EsGMX", "0xf42Ae1D54fd613C9bb14810b0588FaAa09a426cA") + + // const stakedGmxTracker = await contractAt("RewardTracker", "0x908C4D94D34924765f1eDc22A1DD098397c59dD4") + // await sendTxn(gmx.approve(stakedGmxTracker.address, 0), "gmx.approve(stakedGmxTracker)") + + // const rewardRouter = await contractAt("RewardRouter", "0x67b789D48c926006F5132BFCe4e976F0A7A63d5D") + // await sendTxn(rewardRouter.stakeEsGmx(expandDecimals(1, 18)), "rewardRouter.stakeEsGmx") + + // const vaultPriceFeed = await contractAt("VaultPriceFeed", "0x30333ce00AC3025276927672aAeFd80f22E89E54") + // await sendTxn(vaultPriceFeed.setPriceSampleSpace(2), "vaultPriceFeed.setPriceSampleSpace") + + await sendTxn(gmx.approve("0x891f8E531F89465cF7B6b4CD3e6323fFB07ebf23", 100), "gmx.approve") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gambit-token/addLiquidity.js b/scripts/gambit-token/addLiquidity.js new file mode 100644 index 00000000..410522fc --- /dev/null +++ b/scripts/gambit-token/addLiquidity.js @@ -0,0 +1,16 @@ +const { contractAt, sendTxn } = require("../shared/helpers") + +async function main() { + const treasury = await contractAt("Treasury", "0xa44E7252a0C137748F523F112644042E5987FfC7") + const gmt = await contractAt("GMT", "0x99e92123eB77Bc8f999316f622e5222498438784") + + // await sendTxn(treasury.addLiquidity(), "treasury.addLiquidity") + await sendTxn(gmt.endMigration(), "gmt.endMigration") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gambit-token/airdropGMT.js b/scripts/gambit-token/airdropGMT.js new file mode 100644 index 00000000..162e9485 --- /dev/null +++ b/scripts/gambit-token/airdropGMT.js @@ -0,0 +1,32 @@ +const { contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { getAccounts } = require("../../data/airdrop") + +async function main() { + const gmt = await contractAt("GMT", "0x99e92123eB77Bc8f999316f622e5222498438784") + const batchSender = await contractAt("BatchSender", "0x04c5B7575De2E00079e11578bF00F09C07007Bda") + const accounts = getAccounts() + + // await sendTxn(gmt.beginMigration(), "gmt.beginMigration") + // await sendTxn(gmt.addMsgSender(batchSender.address), "gmt.addMsgSender(batchSender)") + + // await sendTxn(gmt.approve(batchSender.address, "112000000000000000000000"), "gmt.approve") + + const addresses = [] + const amounts = [] + + for (let i = 0; i < accounts.length; i++) { + console.info("accounts[i]", i, accounts[i]) + addresses.push(accounts[i][0]) + amounts.push(ethers.utils.parseEther(accounts[i][1])) + } + + await sendTxn(batchSender.send(gmt.address, addresses, amounts), "batchSender.send") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gambit-token/deployGMT.js b/scripts/gambit-token/deployGMT.js new file mode 100644 index 00000000..75210723 --- /dev/null +++ b/scripts/gambit-token/deployGMT.js @@ -0,0 +1,15 @@ +const { deployContract } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const initialSupply = expandDecimals(401 * 1000, 18) + const gmt = await deployContract("GMT", [initialSupply]) + return { gmt } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gambit-token/deployTreasury.js b/scripts/gambit-token/deployTreasury.js new file mode 100644 index 00000000..746e18c7 --- /dev/null +++ b/scripts/gambit-token/deployTreasury.js @@ -0,0 +1,44 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +const PRECISION = 1000000 + +async function main() { + const treasury = await contractAt("Treasury", "0xa44E7252a0C137748F523F112644042E5987FfC7") + const gmt = await contractAt("GMT", "0x99e92123eB77Bc8f999316f622e5222498438784") + const busd = await contractAt("Token", "0xe9e7cea3dedca5984780bafc599bd69add087d56") + const router = { address: "0x05ff2b0db69458a0750badebc4f9e13add608c7f" } + const fund = { address: "0x58CAaCa45a213e9218C5fFd605d5B953da9b9a91" } + const gmtPresalePrice = 4.5 * PRECISION + const gmtListingPrice = 5 * PRECISION + const busdSlotCap = expandDecimals(2000, 18) + const busdHardCap = expandDecimals(900 * 1000, 18) + const busdBasisPoints = 5000 // 50% + const unlockTime = 1615291200 // Tuesday, 9 March 2021 12:00:00 (GMT+0) + + await sendTxn(treasury.initialize( + [ + gmt.address, + busd.address, + router.address, + fund.address + ], + [ + gmtPresalePrice, + gmtListingPrice, + busdSlotCap, + busdHardCap, + busdBasisPoints, + unlockTime + ] + ), "treasury.initialize") + + return { treasury } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gambit-token/setupPresale.js b/scripts/gambit-token/setupPresale.js new file mode 100644 index 00000000..b2775b09 --- /dev/null +++ b/scripts/gambit-token/setupPresale.js @@ -0,0 +1,21 @@ +const { contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const { getWhitelist } = require("../../data/whitelist") + +async function main() { + const wallet = { address: "0x9f169c2189A2d975C18965DE985936361b4a9De9" } + const gmt = await contractAt("GMT", "0x99e92123eB77Bc8f999316f622e5222498438784") + const treasury = await contractAt("Treasury", "0xa44E7252a0C137748F523F112644042E5987FfC7") + + const hasActiveMigration = await gmt.hasActiveMigration() + if (!hasActiveMigration) { + throw new Error("GMT migration not started") + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gmx/deployGMX.js b/scripts/gmx/deployGMX.js new file mode 100644 index 00000000..1ba9bccf --- /dev/null +++ b/scripts/gmx/deployGMX.js @@ -0,0 +1,12 @@ +const { deployContract, contractAt, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + await deployContract("GMX", []) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gmx/deployGmxMigrator.js b/scripts/gmx/deployGmxMigrator.js new file mode 100644 index 00000000..84799d48 --- /dev/null +++ b/scripts/gmx/deployGmxMigrator.js @@ -0,0 +1,61 @@ +const { deployContract, contractAt } = require("../shared/helpers") +const { bigNumberify, expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const { MaxUint256 } = ethers.constants + const precision = 1000000 + + const gmxMigrator = await deployContract("GmxMigrator", [2]) + const gmtGmxIou = await deployContract("GmxIou", [gmxMigrator.address, "GMT GMX (IOU)", "GMT:GMX"]) + const xgmtGmxIou = await deployContract("GmxIou", [gmxMigrator.address, "xGMT GMX (IOU)", "xGMT:GMX"]) + const gmtUsdgGmxIou = await deployContract("GmxIou", [gmxMigrator.address, "GMT-USDG GMX (IOU)", "GMT-USDG:GMX"]) + const xgmtUsdgGmxIou = await deployContract("GmxIou", [gmxMigrator.address, "xGMT-USDG GMX (IOU)", "xGMT-USDG:GMX"]) + + const gmt = { address: "0x99e92123eB77Bc8f999316f622e5222498438784" } + const xgmt = { address: "0xe304ff0983922787Fd84BC9170CD21bF78B16B10" } + const gmtUsdg = { address: "0xa41e57459f09a126F358E118b693789d088eA8A0" } + const xgmtUsdg = { address: "0x0b622208fc0691C2486A3AE6B7C875b4A174b317" } + const usdg = { address: "0x85E76cbf4893c1fbcB34dCF1239A91CE2A4CF5a7" } + + const ammRouter = { address: "0x10ED43C718714eb63d5aA57B78B54704E256024E" } + const gmxPrice = bigNumberify(2 * precision) + + const signers = [ + "0x3D850Acfaa18c58b383fCA69d4d867Dc5Bb697c5", // Ben Simon + "0x881690382102106b00a99E3dB86056D0fC71eee6", // Han Wen + "0x2e5d207a4c0f7e7c52f6622dcc6eb44bc0fe1a13" // Krunal Amin + ] + + const gmtPrice = bigNumberify(10.97 * precision) + const xgmtPrice = bigNumberify(90.31 * precision) + const gmtUsdgPrice = bigNumberify(parseInt(6.68 * precision * 1.1)) + const xgmtUsdgPrice = bigNumberify(parseInt(19.27 * precision * 1.1)) + + const whitelistedTokens = [gmt.address, xgmt.address, gmtUsdg.address, xgmtUsdg.address] + const iouTokens = [gmtGmxIou.address, xgmtGmxIou.address, gmtUsdgGmxIou.address, xgmtUsdgGmxIou.address] + const prices = [gmtPrice, xgmtPrice, gmtUsdgPrice, xgmtUsdgPrice] + const caps = [MaxUint256, MaxUint256, expandDecimals(483129, 18), expandDecimals(150191, 18)] + const lpTokens = [gmtUsdg.address, xgmtUsdg.address] + const lpTokenAs = [gmt.address, xgmt.address] + const lpTokenBs = [usdg.address, usdg.address] + + await gmxMigrator.initialize( + ammRouter.address, + gmxPrice, + signers, + whitelistedTokens, + iouTokens, + prices, + caps, + lpTokens, + lpTokenAs, + lpTokenBs + ) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gmx/deployMigrationHandler.js b/scripts/gmx/deployMigrationHandler.js new file mode 100644 index 00000000..4c7d2294 --- /dev/null +++ b/scripts/gmx/deployMigrationHandler.js @@ -0,0 +1,32 @@ +const { deployContract, contractAt, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + const ammRouterV1 = { address: "0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F" } + const ammRouterV2 = { address: "0x10ED43C718714eb63d5aA57B78B54704E256024E" } + const vault = { address: "0xc73A8DcAc88498FD4b4B1b2AaA37b0a2614Ff67B" } + const gmt = { address: "0x99e92123eB77Bc8f999316f622e5222498438784" } + const xgmt = { address: "0xe304ff0983922787Fd84BC9170CD21bF78B16B10" } + const usdg = { address: "0x85E76cbf4893c1fbcB34dCF1239A91CE2A4CF5a7" } + const busd = { address: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56" } + const bnb = { address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" } + + const migrationHandler = await deployContract("MigrationHandler", []) + + await migrationHandler.initialize( + ammRouterV1.address, + ammRouterV2.address, + vault.address, + gmt.address, + xgmt.address, + usdg.address, + bnb.address, + busd.address + ) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gmx/deployTokens.js b/scripts/gmx/deployTokens.js new file mode 100644 index 00000000..17277bef --- /dev/null +++ b/scripts/gmx/deployTokens.js @@ -0,0 +1,13 @@ +const { deployContract, contractAt, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + await deployContract("EsGMX", []) + await deployContract("GLP", []) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/gmx/setMaxMigrationAmount.js b/scripts/gmx/setMaxMigrationAmount.js new file mode 100644 index 00000000..cbd0afee --- /dev/null +++ b/scripts/gmx/setMaxMigrationAmount.js @@ -0,0 +1,61 @@ +const { contractAt, sendTxn } = require("../shared/helpers") + +async function main() { + const account = "0xcD4e87bc4c646214b688DCe5d7BBc85b66d2361E" + const shouldApprove = true + const migrationTokens = ["GMT"] + // const migrationTokens = ["GMT", "XGMT", "GMT_USDG", "XGMT_USDG"] + + const gmxMigrator = await contractAt("GmxMigrator", "0x0472F402EA8E301D7595545884Ad4C420E9865d6") + const gmt = { + name: "GMT", + contract: await contractAt("Token", "0x99e92123eB77Bc8f999316f622e5222498438784") + } + const xgmt = { + name: "XGMT", + contract: await contractAt("Token", "0xe304ff0983922787Fd84BC9170CD21bF78B16B10") + } + const gmtUsdg = { + name: "GMT_USDG", + contract: await contractAt("Token", "0xa41e57459f09a126F358E118b693789d088eA8A0") + } + const xgmtUsdg = { + name: "XGMT_USDG", + contract: await contractAt("Token", "0x0b622208fc0691C2486A3AE6B7C875b4A174b317") + } + const tokens = [gmt, xgmt, gmtUsdg, xgmtUsdg] + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i] + if (!migrationTokens.includes(token.name)) { + continue + } + const balance = await token.contract.balanceOf(account) + console.log(`${account} ${token.name}: ${ethers.utils.formatUnits(balance, 18)}`) + } + + if (shouldApprove) { + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i] + if (!migrationTokens.includes(token.name)) { + continue + } + + const balance = await token.contract.balanceOf(account) + if (balance.eq(0)) { continue } + + const migratedAmount = await gmxMigrator.migratedAmounts(account, token.contract.address) + const totalAmount = balance.add(migratedAmount) + + const message = `approve ${account} ${token.name}: ${ethers.utils.formatUnits(balance, 18)}, ${ethers.utils.formatUnits(totalAmount, 18)}` + await sendTxn(gmxMigrator.setMaxMigrationAmount(account, token.contract.address, totalAmount), message) + } + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/peripherals/deployBalanceUpdater.js b/scripts/peripherals/deployBalanceUpdater.js new file mode 100644 index 00000000..4a2c403e --- /dev/null +++ b/scripts/peripherals/deployBalanceUpdater.js @@ -0,0 +1,12 @@ +const { deployContract, contractAt } = require("../shared/helpers") + +async function main() { + await deployContract("BalanceUpdater", []) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/peripherals/deployBatchSender.js b/scripts/peripherals/deployBatchSender.js new file mode 100644 index 00000000..3c15d8ae --- /dev/null +++ b/scripts/peripherals/deployBatchSender.js @@ -0,0 +1,13 @@ +const { deployContract } = require("../shared/helpers") + +async function main() { + const batchSender = await deployContract("BatchSender", []) + return { batchSender } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/peripherals/deployOrderBookReader.js b/scripts/peripherals/deployOrderBookReader.js new file mode 100644 index 00000000..dd6026a4 --- /dev/null +++ b/scripts/peripherals/deployOrderBookReader.js @@ -0,0 +1,16 @@ +const { deployContract, contractAt, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + const orderBookReader = await deployContract("OrderBookReader", []) + + writeTmpAddresses({ + orderBookReader: orderBookReader.address + }) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/peripherals/deployReader.js b/scripts/peripherals/deployReader.js new file mode 100644 index 00000000..007389fc --- /dev/null +++ b/scripts/peripherals/deployReader.js @@ -0,0 +1,16 @@ +const { deployContract, contractAt, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + const contract = await deployContract("Reader", [], "Reader") + + writeTmpAddresses({ + reader: contract.address + }) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/peripherals/deployRewardReader.js b/scripts/peripherals/deployRewardReader.js new file mode 100644 index 00000000..f6448294 --- /dev/null +++ b/scripts/peripherals/deployRewardReader.js @@ -0,0 +1,12 @@ +const { deployContract, contractAt, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + await deployContract("RewardReader", [], "RewardReader") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/peripherals/deployTimelock.js b/scripts/peripherals/deployTimelock.js new file mode 100644 index 00000000..60bd9a16 --- /dev/null +++ b/scripts/peripherals/deployTimelock.js @@ -0,0 +1,18 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const buffer = 24 * 60 * 60 + // const gmx = { address: "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a" } + const tokenManager = { address: "0x4E29d2ee6973E5Bd093df40ef9d0B28BD56C9e4E" } + const maxTokenSupply = expandDecimals("13250000", 18) + const timelock = await deployContract("Timelock", [buffer, tokenManager.address, maxTokenSupply]) + // await sendTxn(timelock.addExcludedToken(gmx.address), "timelock.addExcludedToken(gmx)") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/peripherals/deployVaultReader.js b/scripts/peripherals/deployVaultReader.js new file mode 100644 index 00000000..473f782b --- /dev/null +++ b/scripts/peripherals/deployVaultReader.js @@ -0,0 +1,16 @@ +const { deployContract, contractAt, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + const contract = await deployContract("VaultReader", ["0xDE3590067c811b6F023b557ed45E4f1067859663"], "VaultReader") + + writeTmpAddresses({ + reader: contract.address + }) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/peripherals/read.js b/scripts/peripherals/read.js new file mode 100644 index 00000000..58486257 --- /dev/null +++ b/scripts/peripherals/read.js @@ -0,0 +1,95 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function readVaultTokenInfo(vault, tokens, usdgAmount) { + console.log("vault.priceFeed", await vault.priceFeed()) + + const priceFeed = await contractAt("VaultPriceFeed", await vault.priceFeed()) + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i] + console.log("vault.poolAmounts", (await vault.poolAmounts(token)).toString()) + console.log("vault.reservedAmounts", (await vault.reservedAmounts(token)).toString()) + console.log("vault.usdgAmounts", (await vault.usdgAmounts(token)).toString()) + console.log("vault.getRedemptionAmount", (await vault.getRedemptionAmount(token, usdgAmount)).toString()) + console.log("vault.getMinPrice", (await vault.getMinPrice(token)).toString()) + console.log("vault.getMaxPrice", (await vault.getMaxPrice(token)).toString()) + console.log("vault.guaranteedUsd", (await vault.guaranteedUsd(token)).toString()) + console.log("priceFeed.getPrice", (await priceFeed.getPrice(token, false, false, false)).toString()) + console.log("priceFeed.getPrice", (await priceFeed.getPrice(token, true, false, false)).toString()) + } +} + +async function readFees(vault, weth, usdc) { + // const result = await reader.getMaxAmountIn(vault.address, weth.address, usdc.address) + // const result = await reader.getMaxAmountIn(vault.address, usdc.address, weth.address) + // const result = await reader.getAmountOut(vault.address, weth.address, usdc.address, expandDecimals(1, 18)) + // const result = await reader.getAmountOut(vault.address, usdc.address, weth.address, expandDecimals(10, 6)) + const result = await reader.getFeeBasisPoints(vault.address, usdc.address, weth.address, expandDecimals(10, 6)) + console.log("result[0]", result[0].toString()) + console.log("result[1]", result[1].toString()) + console.log("result[2]", result[2].toString()) + + const ethTargetAmount = await vault.getTargetUsdgAmount(weth.address); + const usdcTargetAmount = await vault.getTargetUsdgAmount(usdc.address); + console.log("ethTargetAmount", ethTargetAmount.toString()) + console.log("usdcTargetAmount", usdcTargetAmount.toString()) + + const initialAmount0 = await vault.usdgAmounts(usdc.address) + console.log("initialAmount0", initialAmount0.toString()) + + const feeBasisPoints0 = await vault.getFeeBasisPoints(usdc.address, expandDecimals(10, 18), 20, 20, true) + console.log("feeBasisPoints0", feeBasisPoints0.toString()) +} + +async function readMinProfitBps(vault, tokens) { + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i] + console.log("vault.minProfitBasisPoints", (await vault.minProfitBasisPoints(token)).toString()) + } +} + +async function readFeeConfig(vault) { + console.log("vault.taxBasisPoints", (await vault.taxBasisPoints()).toString()) + console.log("vault.stableTaxBasisPoints", (await vault.stableTaxBasisPoints()).toString()) + console.log("vault.mintBurnFeeBasisPoints", (await vault.mintBurnFeeBasisPoints()).toString()) + console.log("vault.swapFeeBasisPoints", (await vault.swapFeeBasisPoints()).toString()) + console.log("vault.stableSwapFeeBasisPoints", (await vault.stableSwapFeeBasisPoints()).toString()) + console.log("vault.marginFeeBasisPoints", (await vault.marginFeeBasisPoints()).toString()) + console.log("vault.liquidationFeeUsd", (await vault.liquidationFeeUsd()).toString()) + console.log("vault.minProfitTime", (await vault.minProfitTime()).toString()) + console.log("vault.hasDynamicFees", (await vault.hasDynamicFees()).toString()) +} + +async function readGlpManager(glpManager) { + console.log("glpManager.cooldownDuration", (await glpManager.cooldownDuration()).toString()) +} + +async function getPool(tokenAddress0, tokenAddress1, fees) { + const factory = await contractAt("UniFactory", "0x1F98431c8aD98523631AE4a59f267346ea31F984") + const result = await factory.getPool(tokenAddress0, tokenAddress1, fees) + console.log("result", result) +} + +async function main() { + const reader = await contractAt("Reader", "0xbD8F00AabeC361ce52486431433FB196c53C5101") + const vault = await contractAt("Vault", "0xDE3590067c811b6F023b557ed45E4f1067859663") + const weth = { address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1" } + const usdc = { address: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8" } + const gmx = { address: "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a" } + const tokens = ["0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"] + const usdgAmount = expandDecimals(1, 18) + const glpManager = await contractAt("GlpManager", "0x91425Ac4431d068980d497924DD540Ae274f3270") + + // await readGlpManager(glpManager) + + // await getPool(weth.address, usdc.address, 500) + await getPool(weth.address, gmx.address, 10000) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/peripherals/transferNFT.js b/scripts/peripherals/transferNFT.js new file mode 100644 index 00000000..8bf0ac17 --- /dev/null +++ b/scripts/peripherals/transferNFT.js @@ -0,0 +1,18 @@ +const { deployContract, contractAt, writeTmpAddresses, sendTxn } = require("../shared/helpers") + +async function main() { + // const wallet = { address: "0xaD8987f5a71D22BD14F1c842D1f431eeDa83Fc4B" } + const wallet = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } + const nft = await contractAt("ERC721", "0xC36442b4a4522E871399CD717aBDD847Ab11FE88") + const nftId = 2742 + const tokenManager = await contractAt("TokenManager", "0x50F22389C10FcC3bA9B1AB9BCDafE40448a357FB") + await sendTxn(nft.transferFrom(wallet.address, tokenManager.address, nftId), "nft.transferFrom") + // await sendTxn(nft.transferFrom(tokenManager.address, wallet.address, nftId), "nft.transferFrom") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/shared/helpers.js b/scripts/shared/helpers.js new file mode 100644 index 00000000..214c6396 --- /dev/null +++ b/scripts/shared/helpers.js @@ -0,0 +1,75 @@ +const fs = require('fs') +const path = require('path') + +async function sendTxn(txnPromise, label) { + const txn = await txnPromise + console.info(`Sending ${label}...`) + await txn.wait() + console.info("... Sent!") + return txn +} + +async function callWithRetries(func, args, retriesCount = 3) { + let i = 0 + while (true) { + i++ + try { + return await func(...args) + } catch (ex) { + if (i === retriesCount) { + console.error("call failed %s times. throwing error", retriesCount) + throw ex + } + console.error("call i=%s failed. retrying....", i) + console.error(ex.message) + } + } +} + +async function deployContract(name, args, label, options) { + let info = name + if (label) { info = name + ":" + label } + const contractFactory = await ethers.getContractFactory(name) + let contract + if (options) { + contract = await contractFactory.deploy(...args, options) + } else { + contract = await contractFactory.deploy(...args) + } + const argStr = args.map((i) => `"${i}"`).join(" ") + console.info(`Deploying ${info} ${contract.address} ${argStr}`) + await contract.deployTransaction.wait() + console.info("... Completed!") + return contract +} + +async function contractAt(name, address, provider) { + let contractFactory = await ethers.getContractFactory(name) + if (provider) { + contractFactory = contractFactory.connect(provider) + } + return await contractFactory.attach(address) +} + +const tmpAddressesFilepath = path.join(__dirname, '..', '..', `.tmp-addresses-${process.env.HARDHAT_NETWORK}.json`) + +function readTmpAddresses() { + if (fs.existsSync(tmpAddressesFilepath)) { + return JSON.parse(fs.readFileSync(tmpAddressesFilepath)) + } + return {} +} + +function writeTmpAddresses(json) { + const tmpAddresses = Object.assign(readTmpAddresses(), json) + fs.writeFileSync(tmpAddressesFilepath, JSON.stringify(tmpAddresses)) +} + +module.exports = { + sendTxn, + deployContract, + contractAt, + writeTmpAddresses, + readTmpAddresses, + callWithRetries +} diff --git a/scripts/staking/batchClaimForAccounts.js b/scripts/staking/batchClaimForAccounts.js new file mode 100644 index 00000000..de7b1ff9 --- /dev/null +++ b/scripts/staking/batchClaimForAccounts.js @@ -0,0 +1,38 @@ +const { deployContract, contractAt, sendTxn, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + const accountsList = [] + + const batchSize = 30 + let accounts = [] + + const rewardManager = await contractAt("RewardManager", "0x4D66F7eaC6FCc1516C963667E8c6FC9eC3c3cd57") + const feeGmxTracker = await contractAt("RewardTracker", "0xd2D1162512F927a7e282Ef43a362659E4F2a728F") + // await sendTxn(feeGmxTracker.setHandler(rewardManager.address, true), "feeGmxTracker.setHandler") + + for (let i = 0; i < accountsList.length; i++) { + accounts.push(accountsList[i]) + + if (accounts.length === batchSize) { + console.log("accounts", accounts) + console.log("sending batch", i, accounts.length) + await sendTxn(rewardManager.batchClaimForAccounts(feeGmxTracker.address, accounts, "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8"), "rewardManager.batchClaimForAccounts") + + accounts = [] + } + } + + if (accounts.length > 0) { + console.log("sending final batch", accounts.length) + await sendTxn(rewardManager.batchClaimForAccounts(feeGmxTracker.address, accounts, "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8"), "rewardManager.batchClaimForAccounts") + } + + await sendTxn(feeGmxTracker.setHandler(rewardManager.address, false), "feeGmxTracker.setHandler") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/batchStakeGmx.js b/scripts/staking/batchStakeGmx.js new file mode 100644 index 00000000..c3a73da9 --- /dev/null +++ b/scripts/staking/batchStakeGmx.js @@ -0,0 +1,65 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const stakeGmxList = require("../../data/gmxMigration/stakeGmxList6.json") + +async function main() { + const wallet = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } + const gmx = await contractAt("GMX", "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a"); + const rewardRouter = await contractAt("RewardRouter", "0xc73d553473dC65CE56db96c58e6a091c20980fbA") + const stakedGmxTracker = await contractAt("RewardTracker", "0x908C4D94D34924765f1eDc22A1DD098397c59dD4") + const shouldStake = false + + console.log("processing list", stakeGmxList.length) + + // await sendTxn(gmx.setMinter(wallet.address, true), "gmx.setMinter") + // await sendTxn(gmx.mint(wallet.address, expandDecimals(5500000, 18)), "gmx.mint") + // await sendTxn(gmx.approve(stakedGmxTracker.address, expandDecimals(5500000, 18)), "gmx.approve(stakedGmxTracker)") + // await sendTxn(rewardRouter.batchStakeGmxForAccount(["0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8"], [1], { gasLimit: 30000000 }), "rewardRouter.batchStakeGmxForAccount") + + if (!shouldStake) { + for (let i = 0; i < stakeGmxList.length; i++) { + const item = stakeGmxList[i] + const account = item.address + const stakedAmount = await stakedGmxTracker.stakedAmounts(account) + console.log(`${account} : ${stakedAmount.toString()}`) + } + return + } + + const batchSize = 30 + let accounts = [] + let amounts = [] + + for (let i = 0; i < stakeGmxList.length; i++) { + const item = stakeGmxList[i] + accounts.push(item.address) + amounts.push(item.balance) + + if (accounts.length === batchSize) { + console.log("accounts", accounts) + console.log("amounts", amounts) + console.log("sending batch", i, accounts.length, amounts.length) + await sendTxn(rewardRouter.batchStakeGmxForAccount(accounts, amounts), "rewardRouter.batchStakeGmxForAccount") + + const account = accounts[0] + const amount = amounts[0] + const stakedAmount = await stakedGmxTracker.stakedAmounts(account) + console.log(`${account}: ${amount.toString()}, ${stakedAmount.toString()}`) + + accounts = [] + amounts = [] + } + } + + if (accounts.length > 0) { + console.log("sending final batch", stakeGmxList.length, accounts.length, amounts.length) + await sendTxn(rewardRouter.batchStakeGmxForAccount(accounts, amounts), "rewardRouter.batchStakeGmxForAccount") + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/checkRewards.js b/scripts/staking/checkRewards.js new file mode 100644 index 00000000..de5bf7da --- /dev/null +++ b/scripts/staking/checkRewards.js @@ -0,0 +1,54 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function getDistributor(rewardTracker) { + const distributorAddress = await rewardTracker.distributor() + return await contractAt("RewardDistributor", distributorAddress) +} + +async function printDistributorBalance(token, distributor, label) { + const balance = await token.balanceOf(distributor.address) + const pendingRewards = await distributor.pendingRewards() + console.log( + label, + ethers.utils.formatUnits(balance, 18), + ethers.utils.formatUnits(pendingRewards, 18), + balance.gte(pendingRewards) ? "sufficient-balance" : "insufficient-balance", + ethers.utils.formatUnits(balance.sub(pendingRewards), 18) + ) +} + +async function main() { + const gmx = await contractAt("GMX", "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a") + const esGmx = await contractAt("EsGMX", "0xf42Ae1D54fd613C9bb14810b0588FaAa09a426cA") + const bnGmx = await contractAt("MintableBaseToken", "0x35247165119B69A40edD5304969560D0ef486921") + const weth = await contractAt("Token", "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") + + const stakedGmxTracker = await contractAt("RewardTracker", "0x908C4D94D34924765f1eDc22A1DD098397c59dD4") + const stakedGmxDistributor = await getDistributor(stakedGmxTracker) + + const bonusGmxTracker = await contractAt("RewardTracker", "0x4d268a7d4C16ceB5a606c173Bd974984343fea13") + const bonusGmxDistributor = await getDistributor(bonusGmxTracker) + + const feeGmxTracker = await contractAt("RewardTracker", "0xd2D1162512F927a7e282Ef43a362659E4F2a728F") + const feeGmxDistributor = await getDistributor(feeGmxTracker) + + const stakedGlpTracker = await contractAt("RewardTracker", "0x1aDDD80E6039594eE970E5872D247bf0414C8903") + const stakedGlpDistributor = await getDistributor(stakedGlpTracker) + + const feeGlpTracker = await contractAt("RewardTracker", "0x4e971a87900b931fF39d1Aad67697F49835400b6") + const feeGlpDistributor = await getDistributor(feeGlpTracker) + + await printDistributorBalance(esGmx, stakedGmxDistributor, "esGmx in stakedGmxDistributor:") + await printDistributorBalance(bnGmx, bonusGmxDistributor, "bnGmx in bonusGmxDistributor:") + await printDistributorBalance(weth, feeGmxDistributor, "weth in feeGmxDistributor:") + await printDistributorBalance(esGmx, stakedGlpDistributor, "esGmx in stakedGlpDistributor:") + await printDistributorBalance(weth, feeGlpDistributor, "esGmx in feeGlpDistributor:") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/checkStake.js b/scripts/staking/checkStake.js new file mode 100644 index 00000000..48ac0279 --- /dev/null +++ b/scripts/staking/checkStake.js @@ -0,0 +1,25 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const wallet = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } + + const account = "0x9f169c2189A2d975C18965DE985936361b4a9De9" + + const gmx = await contractAt("GMX", "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a"); + const bnGmx = await contractAt("MintableBaseToken", "0x35247165119B69A40edD5304969560D0ef486921"); + const stakedGmxTracker = await contractAt("RewardTracker", "0x908C4D94D34924765f1eDc22A1DD098397c59dD4") + const bonusGmxTracker = await contractAt("RewardTracker", "0x4d268a7d4C16ceB5a606c173Bd974984343fea13") + const feeGmxTracker = await contractAt("RewardTracker", "0xd2D1162512F927a7e282Ef43a362659E4F2a728F") + + console.log("stakedGmxTracker.claimable", (await stakedGmxTracker.claimable(account)).toString()) + console.log("bonusGmxTracker.claimable", (await bonusGmxTracker.claimable(account)).toString()) + console.log("feeGmxTracker.claimable", (await feeGmxTracker.claimable(account)).toString()) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/checkStakedAmounts.js b/scripts/staking/checkStakedAmounts.js new file mode 100644 index 00000000..ce330198 --- /dev/null +++ b/scripts/staking/checkStakedAmounts.js @@ -0,0 +1,45 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") +const stakeGmxList = require("../../data/stakeGmxList.json") + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function main() { + const { formatEther } = ethers.utils + const wallet = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } + const gmx = await contractAt("GMX", "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a"); + const rewardRouter = await contractAt("RewardRouter", "0x7B01aCf6e7e9CC276e644ac65D770c1131583453") + const stakedGmxTracker = await contractAt("RewardTracker", "0x908C4D94D34924765f1eDc22A1DD098397c59dD4") + const bonusGmxTracker = await contractAt("RewardTracker", "0x4d268a7d4C16ceB5a606c173Bd974984343fea13") + + const batchSize = 20 + + for (let i = 0; i < stakeGmxList.length; i++) { + const { address, balance } = stakeGmxList[i] + + const stakedAmount = await stakedGmxTracker.stakedAmounts(address) + console.log(`${i} ${address}: ${formatEther(balance)}, ${formatEther(stakedAmount)}`) + + if (!stakedAmount.eq(balance)) { + throw new Error(`Invalid stakedAmount: ${address}, ${formatEther(balance)}, ${formatEther(stakedAmount).toString()}`) + } + + const pendingRewards = await stakedGmxTracker.claimable(address) + const pendingBonus = await bonusGmxTracker.claimable(address) + + console.log(`${address}: ${formatEther(pendingRewards).toString()}, ${formatEther(pendingBonus).toString()}`) + + if (i % batchSize === 0) { + await sleep(1) + } + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/deployRewardManager.js b/scripts/staking/deployRewardManager.js new file mode 100644 index 00000000..1742bf8d --- /dev/null +++ b/scripts/staking/deployRewardManager.js @@ -0,0 +1,12 @@ +const { deployContract, contractAt, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + await deployContract("RewardManager", [], "RewardManager") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/deployRewardRouter.js b/scripts/staking/deployRewardRouter.js new file mode 100644 index 00000000..11d71f9c --- /dev/null +++ b/scripts/staking/deployRewardRouter.js @@ -0,0 +1,67 @@ +const { deployContract, contractAt, sendTxn, readTmpAddresses } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +const network = (process.env.HARDHAT_NETWORK || 'mainnet'); +const tokens = require('../core/tokens')[network]; + +async function main() { + const { + nativeToken + } = tokens + + const weth = await contractAt("Token", "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") + const gmx = await contractAt("GMX", "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a") + const esGmx = await contractAt("EsGMX", "0xf42Ae1D54fd613C9bb14810b0588FaAa09a426cA") + const bnGmx = await contractAt("MintableBaseToken", "0x35247165119B69A40edD5304969560D0ef486921") + + const stakedGmxTracker = await contractAt("RewardTracker", "0x908C4D94D34924765f1eDc22A1DD098397c59dD4") + const bonusGmxTracker = await contractAt("RewardTracker", "0x4d268a7d4C16ceB5a606c173Bd974984343fea13") + const feeGmxTracker = await contractAt("RewardTracker", "0xd2D1162512F927a7e282Ef43a362659E4F2a728F") + + const feeGlpTracker = await contractAt("RewardTracker", "0x4e971a87900b931fF39d1Aad67697F49835400b6") + const stakedGlpTracker = await contractAt("RewardTracker", "0x1aDDD80E6039594eE970E5872D247bf0414C8903") + + const glp = await contractAt("GLP", "0x4277f8F2c384827B5273592FF7CeBd9f2C1ac258") + const glpManager = await contractAt("GlpManager", "0x321F653eED006AD1C29D174e17d96351BDe22649") + + console.log("glpManager", glpManager.address) + + const rewardRouter = await deployContract("RewardRouter", []) + + await sendTxn(rewardRouter.initialize( + weth.address, + gmx.address, + esGmx.address, + bnGmx.address, + glp.address, + stakedGmxTracker.address, + bonusGmxTracker.address, + feeGmxTracker.address, + feeGlpTracker.address, + stakedGlpTracker.address, + glpManager.address + ), "rewardRouter.initialize") + + // allow rewardRouter to stake in stakedGmxTracker + await sendTxn(stakedGmxTracker.setHandler(rewardRouter.address, true), "stakedGmxTracker.setHandler(rewardRouter)") + // allow rewardRouter to stake in bonusGmxTracker + await sendTxn(bonusGmxTracker.setHandler(rewardRouter.address, true), "bonusGmxTracker.setHandler(rewardRouter)") + // allow rewardRouter to stake in feeGmxTracker + await sendTxn(feeGmxTracker.setHandler(rewardRouter.address, true), "feeGmxTracker.setHandler(rewardRouter)") + // allow rewardRouter to burn bnGmx + await sendTxn(bnGmx.setMinter(rewardRouter.address, true), "bnGmx.setMinter(rewardRouter)") + + // allow rewardRouter to mint in glpManager + await sendTxn(glpManager.setHandler(rewardRouter.address, true), "glpManager.setHandler(rewardRouter)") + // allow rewardRouter to stake in feeGlpTracker + await sendTxn(feeGlpTracker.setHandler(rewardRouter.address, true), "feeGlpTracker.setHandler(rewardRouter)") + // allow rewardRouter to stake in stakedGlpTracker + await sendTxn(stakedGlpTracker.setHandler(rewardRouter.address, true), "stakedGlpTracker.setHandler(rewardRouter)") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/deployRewards.js b/scripts/staking/deployRewards.js new file mode 100644 index 00000000..04c5e1f9 --- /dev/null +++ b/scripts/staking/deployRewards.js @@ -0,0 +1,101 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const wallet = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } + const { AddressZero } = ethers.constants + + const weth = { address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1" } + const gmx = await deployContract("GMX", []); + const esGmx = await deployContract("EsGMX", []); + const bnGmx = await deployContract("MintableBaseToken", ["Bonus GMX", "bnGMX", 0]); + const bnAlp = { address: AddressZero } + const alp = { address: AddressZero } + + const stakedGmxTracker = await deployContract("RewardTracker", ["Staked GMX", "sGMX"]) + const stakedGmxDistributor = await deployContract("RewardDistributor", [esGmx.address, stakedGmxTracker.address]) + await sendTxn(stakedGmxTracker.initialize([gmx.address, esGmx.address], stakedGmxDistributor.address), "stakedGmxTracker.initialize") + await sendTxn(stakedGmxDistributor.updateLastDistributionTime(), "stakedGmxDistributor.updateLastDistributionTime") + + const bonusGmxTracker = await deployContract("RewardTracker", ["Staked + Bonus GMX", "sbGMX"]) + const bonusGmxDistributor = await deployContract("BonusDistributor", [bnGmx.address, bonusGmxTracker.address]) + await sendTxn(bonusGmxTracker.initialize([stakedGmxTracker.address], bonusGmxDistributor.address), "bonusGmxTracker.initialize") + await sendTxn(bonusGmxDistributor.updateLastDistributionTime(), "bonusGmxDistributor.updateLastDistributionTime") + + const feeGmxTracker = await deployContract("RewardTracker", ["Staked + Bonus + Fee GMX", "sbfGMX"]) + const feeGmxDistributor = await deployContract("RewardDistributor", [weth.address, feeGmxTracker.address]) + await sendTxn(feeGmxTracker.initialize([bonusGmxTracker.address, bnGmx.address], feeGmxDistributor.address), "feeGmxTracker.initialize") + await sendTxn(feeGmxDistributor.updateLastDistributionTime(), "feeGmxDistributor.updateLastDistributionTime") + + const feeGlpTracker = { address: AddressZero } + const stakedGlpTracker = { address: AddressZero } + + const stakedAlpTracker = { address: AddressZero } + const bonusAlpTracker = { address: AddressZero } + const feeAlpTracker = { address: AddressZero } + + const glpManager = { address: AddressZero } + const glp = { address: AddressZero } + + await sendTxn(stakedGmxTracker.setInPrivateTransferMode(true), "stakedGmxTracker.setInPrivateTransferMode") + await sendTxn(stakedGmxTracker.setInPrivateStakingMode(true), "stakedGmxTracker.setInPrivateStakingMode") + await sendTxn(bonusGmxTracker.setInPrivateTransferMode(true), "bonusGmxTracker.setInPrivateTransferMode") + await sendTxn(bonusGmxTracker.setInPrivateStakingMode(true), "bonusGmxTracker.setInPrivateStakingMode") + await sendTxn(bonusGmxTracker.setInPrivateClaimingMode(true), "bonusGmxTracker.setInPrivateClaimingMode") + await sendTxn(feeGmxTracker.setInPrivateTransferMode(true), "feeGmxTracker.setInPrivateTransferMode") + await sendTxn(feeGmxTracker.setInPrivateStakingMode(true), "feeGmxTracker.setInPrivateStakingMode") + + const rewardRouter = await deployContract("RewardRouter", []) + + await sendTxn(rewardRouter.initialize( + gmx.address, + esGmx.address, + bnGmx.address, + bnAlp.address, + glp.address, + alp.address, + stakedGmxTracker.address, + bonusGmxTracker.address, + feeGmxTracker.address, + feeGlpTracker.address, + stakedGlpTracker.address, + stakedAlpTracker.address, + bonusAlpTracker.address, + feeAlpTracker.address, + glpManager.address + ), "rewardRouter.initialize") + + // allow rewardRouter to stake in stakedGmxTracker + await sendTxn(stakedGmxTracker.setHandler(rewardRouter.address, true), "stakedGmxTracker.setHandler(rewardRouter)") + // allow bonusGmxTracker to stake stakedGmxTracker + await sendTxn(stakedGmxTracker.setHandler(bonusGmxTracker.address, true), "stakedGmxTracker.setHandler(bonusGmxTracker)") + // allow rewardRouter to stake in bonusGmxTracker + await sendTxn(bonusGmxTracker.setHandler(rewardRouter.address, true), "bonusGmxTracker.setHandler(rewardRouter)") + // allow bonusGmxTracker to stake feeGmxTracker + await sendTxn(bonusGmxTracker.setHandler(feeGmxTracker.address, true), "bonusGmxTracker.setHandler(feeGmxTracker)") + await sendTxn(bonusGmxDistributor.setBonusMultiplier(10000), "bonusGmxDistributor.setBonusMultiplier") + // allow rewardRouter to stake in feeGmxTracker + await sendTxn(feeGmxTracker.setHandler(rewardRouter.address, true), "feeGmxTracker.setHandler(rewardRouter)") + // allow stakedGmxTracker to stake esGmx + await sendTxn(esGmx.setHandler(stakedGmxTracker.address, true), "esGmx.setHandler(stakedGmxTracker)") + // allow feeGmxTracker to stake bnGmx + await sendTxn(bnGmx.setHandler(feeGmxTracker.address, true), "bnGmx.setHandler(feeGmxTracker") + // allow rewardRouter to burn bnGmx + await sendTxn(bnGmx.setMinter(rewardRouter.address, true), "bnGmx.setMinter(rewardRouter") + + // mint esGmx for distributors + await sendTxn(esGmx.setMinter(wallet.address, true), "esGmx.setMinter(wallet)") + await sendTxn(esGmx.mint(stakedGmxDistributor.address, expandDecimals(50000 * 12, 18)), "esGmx.mint(stakedGmxDistributor") // ~50,000 GMX per month + await sendTxn(stakedGmxDistributor.setTokensPerInterval("20667989410000000"), "stakedGmxDistributor.setTokensPerInterval") // 0.02066798941 esGmx per second + + // mint bnGmx for distributor + await sendTxn(bnGmx.setMinter(wallet.address, true), "bnGmx.setMinter") + await sendTxn(bnGmx.mint(bonusGmxDistributor.address, expandDecimals(15 * 1000 * 1000, 18)), "bnGmx.mint(bonusGmxDistributor)") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/removeRewardRouter.js b/scripts/staking/removeRewardRouter.js new file mode 100644 index 00000000..f9be9001 --- /dev/null +++ b/scripts/staking/removeRewardRouter.js @@ -0,0 +1,38 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const rewardRouter = await contractAt("RewardRouter", "0xEa7fCb85802713Cb03291311C66d6012b23402ea") + const bnGmx = await contractAt("MintableBaseToken", "0x35247165119B69A40edD5304969560D0ef486921") + const glpManager = await contractAt("GlpManager", "0x91425Ac4431d068980d497924DD540Ae274f3270") + + const stakedGmxTracker = await contractAt("RewardTracker", "0x908C4D94D34924765f1eDc22A1DD098397c59dD4") + const bonusGmxTracker = await contractAt("RewardTracker", "0x4d268a7d4C16ceB5a606c173Bd974984343fea13") + const feeGmxTracker = await contractAt("RewardTracker", "0xd2D1162512F927a7e282Ef43a362659E4F2a728F") + + const feeGlpTracker = await contractAt("RewardTracker", "0x4e971a87900b931fF39d1Aad67697F49835400b6") + const stakedGlpTracker = await contractAt("RewardTracker", "0x1aDDD80E6039594eE970E5872D247bf0414C8903") + + // allow rewardRouter to stake in stakedGmxTracker + await sendTxn(stakedGmxTracker.setHandler(rewardRouter.address, false), "stakedGmxTracker.setHandler(rewardRouter)") + // allow rewardRouter to stake in bonusGmxTracker + await sendTxn(bonusGmxTracker.setHandler(rewardRouter.address, false), "bonusGmxTracker.setHandler(rewardRouter)") + // allow rewardRouter to stake in feeGmxTracker + await sendTxn(feeGmxTracker.setHandler(rewardRouter.address, false), "feeGmxTracker.setHandler(rewardRouter)") + // allow rewardRouter to burn bnGmx + await sendTxn(bnGmx.setMinter(rewardRouter.address, false), "bnGmx.setMinter(rewardRouter)") + + // allow rewardRouter to mint in glpManager + await sendTxn(glpManager.setHandler(rewardRouter.address, false), "glpManager.setHandler(rewardRouter)") + // allow rewardRouter to stake in feeGlpTracker + await sendTxn(feeGlpTracker.setHandler(rewardRouter.address, false), "feeGlpTracker.setHandler(rewardRouter)") + // allow rewardRouter to stake in stakedGlpTracker + await sendTxn(stakedGlpTracker.setHandler(rewardRouter.address, false), "stakedGlpTracker.setHandler(rewardRouter)") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/stakeForAccount.js b/scripts/staking/stakeForAccount.js new file mode 100644 index 00000000..b41c325a --- /dev/null +++ b/scripts/staking/stakeForAccount.js @@ -0,0 +1,20 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const wallet = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } + const gmx = await contractAt("GMX", "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a"); + const rewardRouter = await contractAt("RewardRouter", "0xEa7fCb85802713Cb03291311C66d6012b23402ea") + + const account = "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" + const amount = "1000000000000000000" + + await sendTxn(rewardRouter.stakeGmxForAccount(account, amount), `Stake for ${account}: ${amount}`) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/unstakeForAccount.js b/scripts/staking/unstakeForAccount.js new file mode 100644 index 00000000..9697fd50 --- /dev/null +++ b/scripts/staking/unstakeForAccount.js @@ -0,0 +1,65 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const wallet = { address: "0x5F799f365Fa8A2B60ac0429C48B153cA5a6f0Cf8" } + + const account = "0x6eA748d14f28778495A3fBa3550a6CdfBbE555f9" + const unstakeAmount = "79170000000000000000" + + const rewardRouter = await contractAt("RewardRouter", "0x1b8911995ee36F4F95311D1D9C1845fA18c56Ec6") + const gmx = await contractAt("GMX", "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a"); + const bnGmx = await contractAt("MintableBaseToken", "0x35247165119B69A40edD5304969560D0ef486921"); + const stakedGmxTracker = await contractAt("RewardTracker", "0x908C4D94D34924765f1eDc22A1DD098397c59dD4") + const bonusGmxTracker = await contractAt("RewardTracker", "0x4d268a7d4C16ceB5a606c173Bd974984343fea13") + const feeGmxTracker = await contractAt("RewardTracker", "0xd2D1162512F927a7e282Ef43a362659E4F2a728F") + + // const gasLimit = 30000000 + + // await sendTxn(feeGmxTracker.setHandler(wallet.address, true, { gasLimit }), "feeGmxTracker.setHandler") + // await sendTxn(bonusGmxTracker.setHandler(wallet.address, true, { gasLimit }), "bonusGmxTracker.setHandler") + // await sendTxn(stakedGmxTracker.setHandler(wallet.address, true, { gasLimit }), "stakedGmxTracker.setHandler") + + const stakedAmount = await stakedGmxTracker.stakedAmounts(account) + console.log(`${account} staked: ${stakedAmount.toString()}`) + console.log(`unstakeAmount: ${unstakeAmount.toString()}`) + + await sendTxn(feeGmxTracker.unstakeForAccount(account, bonusGmxTracker.address, unstakeAmount, account), "feeGmxTracker.unstakeForAccount") + await sendTxn(bonusGmxTracker.unstakeForAccount(account, stakedGmxTracker.address, unstakeAmount, account), "bonusGmxTracker.unstakeForAccount") + await sendTxn(stakedGmxTracker.unstakeForAccount(account, gmx.address, unstakeAmount, account), "stakedGmxTracker.unstakeForAccount") + + await sendTxn(bonusGmxTracker.claimForAccount(account, account), "bonusGmxTracker.claimForAccount") + + const bnGmxAmount = await bnGmx.balanceOf(account) + console.log(`bnGmxAmount: ${bnGmxAmount.toString()}`) + + await sendTxn(feeGmxTracker.stakeForAccount(account, account, bnGmx.address, bnGmxAmount), "feeGmxTracker.stakeForAccount") + + const stakedBnGmx = await feeGmxTracker.depositBalances(account, bnGmx.address) + console.log(`stakedBnGmx: ${stakedBnGmx.toString()}`) + + const reductionAmount = stakedBnGmx.mul(unstakeAmount).div(stakedAmount) + console.log(`reductionAmount: ${reductionAmount.toString()}`) + await sendTxn(feeGmxTracker.unstakeForAccount(account, bnGmx.address, reductionAmount, account), "feeGmxTracker.unstakeForAccount") + await sendTxn(bnGmx.burn(account, reductionAmount), "bnGmx.burn") + + const gmxAmount = await gmx.balanceOf(account) + console.log(`gmxAmount: ${gmxAmount.toString()}`) + + await sendTxn(gmx.burn(account, unstakeAmount), "gmx.burn") + const nextGmxAmount = await gmx.balanceOf(account) + console.log(`nextGmxAmount: ${nextGmxAmount.toString()}`) + + const nextStakedAmount = await stakedGmxTracker.stakedAmounts(account) + console.log(`nextStakedAmount: ${nextStakedAmount.toString()}`) + + const nextStakedBnGmx = await feeGmxTracker.depositBalances(account, bnGmx.address) + console.log(`nextStakedBnGmx: ${nextStakedBnGmx.toString()}`) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/staking/updateRewards.js b/scripts/staking/updateRewards.js new file mode 100644 index 00000000..8243fab4 --- /dev/null +++ b/scripts/staking/updateRewards.js @@ -0,0 +1,43 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const weth = await contractAt("Token", "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1") + const tokenDecimals = 18 + + const rewardTrackerArr = [ + { + name: "feeGmxTracker", + address: "0xd2D1162512F927a7e282Ef43a362659E4F2a728F", + transferAmount: "40" + }, + { + name: "feeGlpTracker", + address: "0x4e971a87900b931fF39d1Aad67697F49835400b6", + transferAmount: "67" + } + ] + + for (let i = 0; i < rewardTrackerArr.length; i++) { + const rewardTrackerItem = rewardTrackerArr[i] + const { transferAmount } = rewardTrackerItem + const rewardTracker = await contractAt("RewardTracker", rewardTrackerItem.address) + const rewardDistributorAddress = await rewardTracker.distributor() + const rewardDistributor = await contractAt("RewardDistributor", rewardDistributorAddress) + const convertedTransferAmount = ethers.utils.parseUnits(transferAmount, tokenDecimals) + const rewardsPerInterval = convertedTransferAmount.div(7 * 24 * 60 * 60) + console.log("rewardDistributorAddress", rewardDistributorAddress) + console.log("convertedTransferAmount", convertedTransferAmount.toString()) + console.log("rewardsPerInterval", rewardsPerInterval.toString()) + + await sendTxn(weth.transfer(rewardDistributorAddress, convertedTransferAmount), `weth.transfer ${i}`) + await sendTxn(rewardDistributor.setTokensPerInterval(rewardsPerInterval), "rewardDistributor.setTokensPerInterval") + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/tokens/addLiquidity.js b/scripts/tokens/addLiquidity.js new file mode 100644 index 00000000..5246e507 --- /dev/null +++ b/scripts/tokens/addLiquidity.js @@ -0,0 +1,58 @@ +const { contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals, maxUint256 } = require("../../test/shared/utilities") + +async function main() { + const wallet = { address: "0x56a2a358d687a40E3bFd2BE28E69aB229e0b444e" } + const router = await contractAt("PancakeRouter", "0xD99D1c33F9fC3444f8101754aBC46c52416550D1") + const gmt = await contractAt("GMT", "0xedba0360a44f885ed390fad01aa34d00d2532817") + const xgmt = await contractAt("YieldToken", "0x28cba798eca1a3128ffd1b734afb93870f22e613") + const usdg = await contractAt("USDG", "0xE14F46Ee1e23B68003bCED6D85465455a309dffF") + const wbnb = await contractAt("WETH", "0x6A2345E019DB2aCC6007DCD3A69731F51D7Dca52") + const busd = await contractAt("FaucetToken", "0xae7486c680720159130b71e0f9EF7AFd8f413227") + + await sendTxn(gmt.approve(router.address, maxUint256), "gmt.approve(router)") + await sendTxn(xgmt.approve(router.address, maxUint256), "xgmt.approve(router)") + await sendTxn(usdg.approve(router.address, maxUint256), "usdg.approve(router)") + await sendTxn(wbnb.approve(router.address, maxUint256), "wbnb.approve(router)") + await sendTxn(busd.approve(router.address, maxUint256), "busd.approve(router)") + + await sendTxn(router.addLiquidity( + gmt.address, // tokenA + usdg.address, // tokenB + expandDecimals(1000, 18), // amountADesired + expandDecimals(120 * 1000, 18), // amountBDesired + 0, // amountAMin + 0, // amountBMin + wallet.address, // to + maxUint256 // deadline + ), "router.addLiquidity(gmt, usdg)") + + await sendTxn(router.addLiquidity( + xgmt.address, // tokenA + usdg.address, // tokenB + expandDecimals(100, 18), // amountADesired + expandDecimals(60 * 1000, 18), // amountBDesired + 0, // amountAMin + 0, // amountBMin + wallet.address, // to + maxUint256 // deadline + ), "router.addLiquidity(xgmt, usdg)") + + await sendTxn(router.addLiquidity( + wbnb.address, // tokenA + busd.address, // tokenB + expandDecimals(10, 18), // amountADesired + expandDecimals(5250, 18), // amountBDesired + 0, // amountAMin + 0, // amountBMin + wallet.address, // to + maxUint256 // deadline + ), "router.addLiquidity(bnb, busd)") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/tokens/deployAutoFarm.js b/scripts/tokens/deployAutoFarm.js new file mode 100644 index 00000000..9a1590de --- /dev/null +++ b/scripts/tokens/deployAutoFarm.js @@ -0,0 +1,32 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const usdg = await contractAt("USDG", "0x85E76cbf4893c1fbcB34dCF1239A91CE2A4CF5a7") + const wbnb = await contractAt("WETH", "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c") + const xgmt = await contractAt("YieldToken", "0xe304ff0983922787Fd84BC9170CD21bF78B16B10") + + const autoUsdgPair = { address: "0x0523FD5C53ea5419B4DAF656BC1b157dDFE3ce50" } + const autoUsdgFarm = await deployContract("YieldFarm", ["AUTO-USDG Farm", "AUTO-USDG:FARM", autoUsdgPair.address], "autoUsdgFarm") + + const autoUsdgFarmYieldTrackerXgmt = await deployContract("YieldTracker", [autoUsdgFarm.address], "autoUsdgFarmYieldTrackerXgmt") + const autoUsdgFarmDistributorXgmt = await deployContract("TimeDistributor", [], "autoUsdgFarmDistributorXgmt") + + await sendTxn(autoUsdgFarmYieldTrackerXgmt.setDistributor(autoUsdgFarmDistributorXgmt.address), "autoUsdgFarmYieldTrackerXgmt.setDistributor") + await sendTxn(autoUsdgFarmDistributorXgmt.setDistribution([autoUsdgFarmYieldTrackerXgmt.address], ["0"], [xgmt.address]), "autoUsdgFarmDistributorXgmt.setDistribution") + + const autoUsdgFarmYieldTrackerWbnb = await deployContract("YieldTracker", [autoUsdgFarm.address], "autoUsdgFarmYieldTrackerWbnb") + const autoUsdgFarmDistributorWbnb = await deployContract("TimeDistributor", [], "autoUsdgFarmDistributorWbnb") + + await sendTxn(autoUsdgFarmYieldTrackerWbnb.setDistributor(autoUsdgFarmDistributorWbnb.address), "autoUsdgFarmYieldTrackerWbnb.setDistributor") + await sendTxn(autoUsdgFarmDistributorWbnb.setDistribution([autoUsdgFarmYieldTrackerWbnb.address], ["0"], [wbnb.address]), "autoUsdgFarmDistributorWbnb.setDistribution") + + await sendTxn(autoUsdgFarm.setYieldTrackers([autoUsdgFarmYieldTrackerXgmt.address, autoUsdgFarmYieldTrackerWbnb.address]), "autoUsdgFarm.setYieldTrackers") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/tokens/deployBridge.js b/scripts/tokens/deployBridge.js new file mode 100644 index 00000000..0e11c4ab --- /dev/null +++ b/scripts/tokens/deployBridge.js @@ -0,0 +1,14 @@ +const { deployContract, contractAt, writeTmpAddresses } = require("../shared/helpers") + +async function main() { + const gmx = { address: "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a" } + const wGmx = { address: "0x590020B1005b8b25f1a2C82c5f743c540dcfa24d" } + await deployContract("Bridge", [gmx.address, wGmx.address], "Bridge") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/tokens/deployFarms.js b/scripts/tokens/deployFarms.js new file mode 100644 index 00000000..a4bf14e6 --- /dev/null +++ b/scripts/tokens/deployFarms.js @@ -0,0 +1,63 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const usdg = await contractAt("USDG", "0x85E76cbf4893c1fbcB34dCF1239A91CE2A4CF5a7") + const wbnb = await contractAt("WETH", "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c") + const xgmt = await contractAt("YieldToken", "0xe304ff0983922787Fd84BC9170CD21bF78B16B10") + + const gmtUsdgPair = { address: "0xa41e57459f09a126F358E118b693789d088eA8A0" } + const gmtUsdgFarm = await deployContract("YieldFarm", ["GMT-USDG Farm", "GMT-USDG:FARM", gmtUsdgPair.address], "gmtUsdgFarm") + + const xgmtUsdgPair = { address: "0x0b622208fc0691C2486A3AE6B7C875b4A174b317" } + const xgmtUsdgFarm = await deployContract("YieldFarm", ["xGMT-USDG Farm", "xGMT-USDG:FARM", xgmtUsdgPair.address], "xgmtUsdgFarm") + + const usdgYieldTracker = await deployContract("YieldTracker", [usdg.address], "usdgYieldTracker") + const usdgRewardDistributor = await deployContract("TimeDistributor", [], "usdgRewardDistributor") + + await sendTxn(usdg.setYieldTrackers([usdgYieldTracker.address]), "usdg.setYieldTrackers") + await sendTxn(usdgYieldTracker.setDistributor(usdgRewardDistributor.address), "usdgYieldTracker.setDistributor") + await sendTxn(usdgRewardDistributor.setDistribution([usdgYieldTracker.address], ["0"], [wbnb.address]), "usdgRewardDistributor.setDistribution") + + const xgmtYieldTracker = await deployContract("YieldTracker", [xgmt.address], "xgmtYieldTracker") + const xgmtRewardDistributor = await deployContract("TimeDistributor", [], "xgmtRewardDistributor") + + await sendTxn(xgmt.setYieldTrackers([xgmtYieldTracker.address]), "xgmt.setYieldTrackers") + await sendTxn(xgmtYieldTracker.setDistributor(xgmtRewardDistributor.address), "xgmtYieldTracker.setDistributor") + await sendTxn(xgmtRewardDistributor.setDistribution([xgmtYieldTracker.address], ["0"], [wbnb.address]), "xgmtRewardDistributor.setDistribution") + + const gmtUsdgFarmYieldTrackerXgmt = await deployContract("YieldTracker", [gmtUsdgFarm.address], "gmtUsdgFarmYieldTrackerXgmt") + const gmtUsdgFarmDistributorXgmt = await deployContract("TimeDistributor", [], "gmtUsdgFarmDistributorXgmt") + + await sendTxn(gmtUsdgFarmYieldTrackerXgmt.setDistributor(gmtUsdgFarmDistributorXgmt.address), "gmtUsdgFarmYieldTrackerXgmt.setDistributor") + await sendTxn(gmtUsdgFarmDistributorXgmt.setDistribution([gmtUsdgFarmYieldTrackerXgmt.address], ["0"], [xgmt.address]), "gmtUsdgFarmDistributorXgmt.setDistribution") + + const gmtUsdgFarmYieldTrackerWbnb = await deployContract("YieldTracker", [gmtUsdgFarm.address], "gmtUsdgFarmYieldTrackerWbnb") + const gmtUsdgFarmDistributorWbnb = await deployContract("TimeDistributor", [], "gmtUsdgFarmDistributorWbnb") + + await sendTxn(gmtUsdgFarmYieldTrackerWbnb.setDistributor(gmtUsdgFarmDistributorWbnb.address), "gmtUsdgFarmYieldTrackerWbnb.setDistributor") + await sendTxn(gmtUsdgFarmDistributorWbnb.setDistribution([gmtUsdgFarmYieldTrackerWbnb.address], ["0"], [wbnb.address]), "gmtUsdgFarmDistributorWbnb.setDistribution") + + await sendTxn(gmtUsdgFarm.setYieldTrackers([gmtUsdgFarmYieldTrackerXgmt.address, gmtUsdgFarmYieldTrackerWbnb.address]), "gmtUsdgFarm.setYieldTrackers") + + const xgmtUsdgFarmYieldTrackerXgmt = await deployContract("YieldTracker", [xgmtUsdgFarm.address], "xgmtUsdgFarmYieldTrackerXgmt") + const xgmtUsdgFarmDistributorXgmt = await deployContract("TimeDistributor", [], "xgmtUsdgFarmDistributorXgmt") + + await sendTxn(xgmtUsdgFarmYieldTrackerXgmt.setDistributor(xgmtUsdgFarmDistributorXgmt.address), "xgmtUsdgFarmYieldTrackerXgmt.setDistributor") + await sendTxn(xgmtUsdgFarmDistributorXgmt.setDistribution([xgmtUsdgFarmYieldTrackerXgmt.address], ["0"], [xgmt.address]), "xgmtUsdgFarmDistributorXgmt.setDistribution") + + const xgmtUsdgFarmYieldTrackerWbnb = await deployContract("YieldTracker", [xgmtUsdgFarm.address], "xgmtUsdgFarmYieldTrackerWbnb") + const xgmtUsdgFarmDistributorWbnb = await deployContract("TimeDistributor", [], "xgmtUsdgFarmDistributorWbnb") + + await sendTxn(xgmtUsdgFarmYieldTrackerWbnb.setDistributor(xgmtUsdgFarmDistributorWbnb.address), "xgmtUsdgFarmYieldTrackerWbnb.setDistributor") + await sendTxn(xgmtUsdgFarmDistributorWbnb.setDistribution([xgmtUsdgFarmYieldTrackerWbnb.address], ["0"], [wbnb.address]), "gmtUsdgFarmDistributorWbnb.setDistribution") + + await sendTxn(xgmtUsdgFarm.setYieldTrackers([xgmtUsdgFarmYieldTrackerXgmt.address, xgmtUsdgFarmYieldTrackerWbnb.address]), "xgmtUsdgFarm.setYieldTrackers") +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/tokens/deployTokens.js b/scripts/tokens/deployTokens.js new file mode 100644 index 00000000..f40bbc05 --- /dev/null +++ b/scripts/tokens/deployTokens.js @@ -0,0 +1,18 @@ +const { deployContract, sendTxn, writeTmpAddresses, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const addresses = {} + addresses.BTC = (await callWithRetries(deployContract, ["FaucetToken", ["Bitcoin", "BTC", 18, expandDecimals(1000, 18)]])).address + addresses.USDC = (await callWithRetries(deployContract, ["FaucetToken", ["USDC Coin", "USDC", 18, expandDecimals(1000, 18)]])).address + addresses.USDT = (await callWithRetries(deployContract, ["FaucetToken", ["Tether", "USDT", 18, expandDecimals(1000, 18)]])).address + + writeTmpAddresses(addresses) +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/tokens/deployXGMT.js b/scripts/tokens/deployXGMT.js new file mode 100644 index 00000000..691dc754 --- /dev/null +++ b/scripts/tokens/deployXGMT.js @@ -0,0 +1,15 @@ +const { deployContract } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const initialSupply = expandDecimals(100 * 1000, 18) + const xgmt = await deployContract("YieldToken", ["xGambit", "xGMT", initialSupply]) + return { xgmt } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/scripts/tokens/mint.js b/scripts/tokens/mint.js new file mode 100644 index 00000000..57987cc3 --- /dev/null +++ b/scripts/tokens/mint.js @@ -0,0 +1,16 @@ +const { readTmpAddresses, contractAt, callWithRetries } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const account = (await ethers.getSigners())[0] + const {BTC, ETH, USDC, USDT} = readTmpAddresses() + + for (const tokenAddress of [BTC, USDC, USDT]) { + const amount = expandDecimals(100000, 18) + console.log(`Minting ${amount} of tokens ${tokenAddress}`) + const tokenContract = await contractAt("FaucetToken", tokenAddress) + await callWithRetries(tokenContract.mint.bind(tokenContract), [account.address, amount]) + } +} + +main() \ No newline at end of file diff --git a/scripts/tokens/updateGov.js b/scripts/tokens/updateGov.js new file mode 100644 index 00000000..72037b3f --- /dev/null +++ b/scripts/tokens/updateGov.js @@ -0,0 +1,35 @@ +const { deployContract, contractAt, sendTxn } = require("../shared/helpers") +const { expandDecimals } = require("../../test/shared/utilities") + +async function main() { + const addresses = [ + "0x0EF0Cf825B8e9F89A43FfD392664131cFB4cfA89", // usdg yield tracker + "0xd729d21EB85F8dBf3e0754f058024f20439a6AE9", // usdg reward distributor + "0x82A012A9b3003b18B6bCd6052cbbef7Fa4892e80", // xgmt yield tracker + "0x71d0F891a7e5A1D3526cB35589e37469380952c0", // xgmt reward distributor + "0x3E8B08876c791dC880ADC8f965A02e53Bb9C0422", // gmt/usdg farm + "0x08FAb024BEfcb6068847726b2eccEAd18b6c23Cd", // gmt/usdg xgmt yield tracker + "0xA633158288520807F91CCC98aa58E0eA43ACB400", // gmt/usdg xgmt farm distributor + "0xd8E26637B34B2487Cad1f91808878a391134C5c2", // gmt/usdg wbnb yield tracker + "0x40aaDC15af652A790f18Eaf8EcA6228093d2F72E", // gmt/usdg wbnb farm distributor + "0x68D7ee2A16AB7c0Ee1D670BECd144166d2Ae0759", // xgmt/usdg farm + "0x026A02F7F26C1AFccb9Cba7C4df3Dc810F4e92e8", // xgmt/usdg xgmt yield tracker + "0xd9b1C23411aDBB984B1C4BE515fAfc47a12898b2", // xgmt/usdg xgmt distributor + "0x22458CEbD14a9679b2880147d08CA1ce5aa40E84", // xgmt/usdg wbnb yield tracker + "0xB5EA6A50e7B9C5Aa640c7d5E6458a38E1718E8Cd" // xgmt/usdg wbnb distributor + ] + + const gov = { address: "0x7918B81E119954488C00D2243A8BF2fa407ae87d" } + for (let i = 0; i < addresses.length; i++) { + const address = addresses[i] + const contract = await contractAt("YieldToken", address) + await sendTxn(contract.setGov(gov.address), `${i}: setGov`) + } +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error) + process.exit(1) + }) diff --git a/test/core/GlpManager.js b/test/core/GlpManager.js new file mode 100644 index 00000000..4610181b --- /dev/null +++ b/test/core/GlpManager.js @@ -0,0 +1,467 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed, newWallet } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("./Vault/helpers") + +use(solidity) + +describe("GlpManager", function () { + const provider = waffle.provider + const [wallet, rewardRouter, user0, user1, user2, user3] = provider.getWallets() + let vault + let glpManager + let glp + let usdg + let router + let vaultPriceFeed + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let eth + let ethPriceFeed + let dai + let daiPriceFeed + let busd + let busdPriceFeed + let distributor0 + let yieldTracker0 + let reader + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + busd = await deployContract("Token", []) + busdPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + glp = await deployContract("GLP", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 24 * 60 * 60]) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + reader = await deployContract("Reader", []) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(eth.address, ethPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await glp.setInPrivateTransferMode(true) + await glp.setMinter(glpManager.address, true) + + await vault.setInManagerMode(true) + }) + + it("inits", async () => { + expect(await glpManager.gov()).eq(wallet.address) + expect(await glpManager.vault()).eq(vault.address) + expect(await glpManager.usdg()).eq(usdg.address) + expect(await glpManager.glp()).eq(glp.address) + expect(await glpManager.cooldownDuration()).eq(24 * 60 * 60) + }) + + it("setGov", async () => { + await expect(glpManager.connect(user0).setGov(user1.address)) + .to.be.revertedWith("Governable: forbidden") + + expect(await glpManager.gov()).eq(wallet.address) + + await glpManager.setGov(user0.address) + expect(await glpManager.gov()).eq(user0.address) + + await glpManager.connect(user0).setGov(user1.address) + expect(await glpManager.gov()).eq(user1.address) + }) + + it("setHandler", async () => { + await expect(glpManager.connect(user0).setHandler(user1.address, true)) + .to.be.revertedWith("Governable: forbidden") + + expect(await glpManager.gov()).eq(wallet.address) + await glpManager.setGov(user0.address) + expect(await glpManager.gov()).eq(user0.address) + + expect(await glpManager.isHandler(user1.address)).eq(false) + await glpManager.connect(user0).setHandler(user1.address, true) + expect(await glpManager.isHandler(user1.address)).eq(true) + }) + + it("setCooldownDuration", async () => { + await expect(glpManager.connect(user0).setCooldownDuration(1000)) + .to.be.revertedWith("Governable: forbidden") + + await glpManager.setGov(user0.address) + + await expect(glpManager.connect(user0).setCooldownDuration(48 * 60 * 60 + 1)) + .to.be.revertedWith("GlpManager: invalid _cooldownDuration") + + expect(await glpManager.cooldownDuration()).eq(24 * 60 * 60) + await glpManager.connect(user0).setCooldownDuration(48 * 60 * 60) + expect(await glpManager.cooldownDuration()).eq(48 * 60 * 60) + }) + + it("setAumAdjustment", async () => { + await expect(glpManager.connect(user0).setAumAdjustment(29, 17)) + .to.be.revertedWith("Governable: forbidden") + + await glpManager.setGov(user0.address) + + expect(await glpManager.aumAddition()).eq(0) + expect(await glpManager.aumDeduction()).eq(0) + expect(await glpManager.getAum(true)).eq(0) + await glpManager.connect(user0).setAumAdjustment(29, 17) + expect(await glpManager.aumAddition()).eq(29) + expect(await glpManager.aumDeduction()).eq(17) + expect(await glpManager.getAum(true)).eq(12) + }) + + it("addLiquidity, removeLiquidity", async () => { + await dai.mint(user0.address, expandDecimals(100, 18)) + await dai.connect(user0).approve(glpManager.address, expandDecimals(100, 18)) + + await expect(glpManager.connect(user0).addLiquidity( + dai.address, + expandDecimals(100, 18), + expandDecimals(101, 18), + expandDecimals(101, 18) + )).to.be.revertedWith("Vault: forbidden") + + await vault.setManager(glpManager.address, true) + + await expect(glpManager.connect(user0).addLiquidity( + dai.address, + expandDecimals(100, 18), + expandDecimals(101, 18), + expandDecimals(101, 18) + )).to.be.revertedWith("GlpManager: insufficient USDG output") + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(400)) + + expect(await dai.balanceOf(user0.address)).eq(expandDecimals(100, 18)) + expect(await dai.balanceOf(vault.address)).eq(0) + expect(await usdg.balanceOf(glpManager.address)).eq(0) + expect(await glp.balanceOf(user0.address)).eq(0) + expect(await glpManager.lastAddedAt(user0.address)).eq(0) + expect(await glpManager.getAumInUsdg(true)).eq(0) + + const tx0 = await glpManager.connect(user0).addLiquidity( + dai.address, + expandDecimals(100, 18), + expandDecimals(99, 18), + expandDecimals(99, 18) + ) + await reportGasUsed(provider, tx0, "addLiquidity gas used") + + let blockTime = await getBlockTime(provider) + + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await dai.balanceOf(vault.address)).eq(expandDecimals(100, 18)) + expect(await usdg.balanceOf(glpManager.address)).eq("99700000000000000000") // 99.7 + expect(await glp.balanceOf(user0.address)).eq("99700000000000000000") + expect(await glp.totalSupply()).eq("99700000000000000000") + expect(await glpManager.lastAddedAt(user0.address)).eq(blockTime) + expect(await glpManager.getAumInUsdg(true)).eq("99700000000000000000") + expect(await glpManager.getAumInUsdg(false)).eq("99700000000000000000") + + await bnb.mint(user1.address, expandDecimals(1, 18)) + await bnb.connect(user1).approve(glpManager.address, expandDecimals(1, 18)) + + await glpManager.connect(user1).addLiquidity( + bnb.address, + expandDecimals(1, 18), + expandDecimals(299, 18), + expandDecimals(299, 18) + ) + blockTime = await getBlockTime(provider) + + expect(await usdg.balanceOf(glpManager.address)).eq("398800000000000000000") // 398.8 + expect(await glp.balanceOf(user0.address)).eq("99700000000000000000") // 99.7 + expect(await glp.balanceOf(user1.address)).eq("299100000000000000000") // 299.1 + expect(await glp.totalSupply()).eq("398800000000000000000") + expect(await glpManager.lastAddedAt(user1.address)).eq(blockTime) + expect(await glpManager.getAumInUsdg(true)).eq("498500000000000000000") + expect(await glpManager.getAumInUsdg(false)).eq("398800000000000000000") + + await expect(glp.connect(user1).transfer(user2.address, expandDecimals(1, 18))) + .to.be.revertedWith("BaseToken: msg.sender not whitelisted") + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(400)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(400)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(500)) + + expect(await glpManager.getAumInUsdg(true)).eq("598200000000000000000") // 598.2 + expect(await glpManager.getAumInUsdg(false)).eq("498500000000000000000") // 498.5 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + + await btc.mint(user2.address, "1000000") // 0.01 BTC, $500 + await btc.connect(user2).approve(glpManager.address, expandDecimals(1, 18)) + + await expect(glpManager.connect(user2).addLiquidity( + btc.address, + "1000000", + expandDecimals(599, 18), + expandDecimals(399, 18) + )).to.be.revertedWith("GlpManager: insufficient USDG output") + + await expect(glpManager.connect(user2).addLiquidity( + btc.address, + "1000000", + expandDecimals(598, 18), + expandDecimals(399, 18) + )).to.be.revertedWith("GlpManager: insufficient GLP output") + + await glpManager.connect(user2).addLiquidity( + btc.address, + "1000000", + expandDecimals(598, 18), + expandDecimals(398, 18) + ) + + blockTime = await getBlockTime(provider) + + expect(await usdg.balanceOf(glpManager.address)).eq("997000000000000000000") // 997 + expect(await glp.balanceOf(user0.address)).eq("99700000000000000000") // 99.7 + expect(await glp.balanceOf(user1.address)).eq("299100000000000000000") // 299.1 + expect(await glp.balanceOf(user2.address)).eq("398800000000000000000") // 398.8 + expect(await glp.totalSupply()).eq("797600000000000000000") // 797.6 + expect(await glpManager.lastAddedAt(user2.address)).eq(blockTime) + expect(await glpManager.getAumInUsdg(true)).eq("1196400000000000000000") // 1196.4 + expect(await glpManager.getAumInUsdg(false)).eq("1096700000000000000000") // 1096.7 + + await expect(glpManager.connect(user0).removeLiquidity( + dai.address, + "99700000000000000000", + expandDecimals(123, 18), + user0.address + )).to.be.revertedWith("GlpManager: cooldown duration not yet passed") + + await increaseTime(provider, 24 * 60 * 60 + 1) + await mineBlock(provider) + + await expect(glpManager.connect(user0).removeLiquidity( + dai.address, + expandDecimals(73, 18), + expandDecimals(100, 18), + user0.address + )).to.be.revertedWith("Vault: poolAmount exceeded") + + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await glp.balanceOf(user0.address)).eq("99700000000000000000") // 99.7 + + await glpManager.connect(user0).removeLiquidity( + dai.address, + expandDecimals(72, 18), + expandDecimals(98, 18), + user0.address + ) + + expect(await dai.balanceOf(user0.address)).eq("98703000000000000000") // 98.703, 72 * 1096.7 / 797.6 => 99 + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await glp.balanceOf(user0.address)).eq("27700000000000000000") // 27.7 + + await glpManager.connect(user0).removeLiquidity( + bnb.address, + "27700000000000000000", // 27.7, 27.7 * 1096.7 / 797.6 => 38.0875 + "75900000000000000", // 0.0759 BNB => 37.95 USD + user0.address + ) + + expect(await dai.balanceOf(user0.address)).eq("98703000000000000000") + expect(await bnb.balanceOf(user0.address)).eq("75946475000000000") // 0.075946475 + expect(await glp.balanceOf(user0.address)).eq(0) + + expect(await glp.totalSupply()).eq("697900000000000000000") // 697.9 + expect(await glpManager.getAumInUsdg(true)).eq("1059312500000000000000") // 1059.3125 + expect(await glpManager.getAumInUsdg(false)).eq("967230000000000000000") // 967.23 + + expect(await bnb.balanceOf(user1.address)).eq(0) + expect(await glp.balanceOf(user1.address)).eq("299100000000000000000") + + await glpManager.connect(user1).removeLiquidity( + bnb.address, + "299100000000000000000", // 299.1, 299.1 * 967.23 / 697.9 => 414.527142857 + "826500000000000000", // 0.8265 BNB => 413.25 + user1.address + ) + + expect(await bnb.balanceOf(user1.address)).eq("826567122857142856") // 0.826567122857142856 + expect(await glp.balanceOf(user1.address)).eq(0) + + expect(await glp.totalSupply()).eq("398800000000000000000") // 398.8 + expect(await glpManager.getAumInUsdg(true)).eq("644785357142857143000") // 644.785357142857143 + expect(await glpManager.getAumInUsdg(false)).eq("635608285714285714400") // 635.6082857142857144 + + expect(await btc.balanceOf(user2.address)).eq(0) + expect(await glp.balanceOf(user2.address)).eq("398800000000000000000") // 398.8 + + expect(await vault.poolAmounts(dai.address)).eq("700000000000000000") // 0.7 + expect(await vault.poolAmounts(bnb.address)).eq("91770714285714286") // 0.091770714285714286 + expect(await vault.poolAmounts(btc.address)).eq("997000") // 0.00997 + + await expect(glpManager.connect(user2).removeLiquidity( + btc.address, + expandDecimals(375, 18), + "990000", // 0.0099 + user2.address + )).to.be.revertedWith("USDG: forbidden") + + await usdg.addVault(glpManager.address) + + const tx1 = await glpManager.connect(user2).removeLiquidity( + btc.address, + expandDecimals(375, 18), + "990000", // 0.0099 + user2.address + ) + await reportGasUsed(provider, tx1, "removeLiquidity gas used") + + expect(await btc.balanceOf(user2.address)).eq("993137") + expect(await glp.balanceOf(user2.address)).eq("23800000000000000000") // 23.8 + }) + + it("addLiquidityForAccount, removeLiquidityForAccount", async () => { + await vault.setManager(glpManager.address, true) + await glpManager.setInPrivateMode(true) + await glpManager.setHandler(rewardRouter.address, true) + + await dai.mint(user3.address, expandDecimals(100, 18)) + await dai.connect(user3).approve(glpManager.address, expandDecimals(100, 18)) + + await expect(glpManager.connect(user0).addLiquidityForAccount( + user3.address, + user0.address, + dai.address, + expandDecimals(100, 18), + expandDecimals(101, 18), + expandDecimals(101, 18) + )).to.be.revertedWith("GlpManager: forbidden") + + await expect(glpManager.connect(rewardRouter).addLiquidityForAccount( + user3.address, + user0.address, + dai.address, + expandDecimals(100, 18), + expandDecimals(101, 18), + expandDecimals(101, 18) + )).to.be.revertedWith("GlpManager: insufficient USDG output") + + expect(await dai.balanceOf(user3.address)).eq(expandDecimals(100, 18)) + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await dai.balanceOf(vault.address)).eq(0) + expect(await usdg.balanceOf(glpManager.address)).eq(0) + expect(await glp.balanceOf(user0.address)).eq(0) + expect(await glpManager.lastAddedAt(user0.address)).eq(0) + expect(await glpManager.getAumInUsdg(true)).eq(0) + + await glpManager.connect(rewardRouter).addLiquidityForAccount( + user3.address, + user0.address, + dai.address, + expandDecimals(100, 18), + expandDecimals(99, 18), + expandDecimals(99, 18) + ) + + let blockTime = await getBlockTime(provider) + + expect(await dai.balanceOf(user3.address)).eq(0) + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await dai.balanceOf(vault.address)).eq(expandDecimals(100, 18)) + expect(await usdg.balanceOf(glpManager.address)).eq("99700000000000000000") // 99.7 + expect(await glp.balanceOf(user0.address)).eq("99700000000000000000") + expect(await glp.totalSupply()).eq("99700000000000000000") + expect(await glpManager.lastAddedAt(user0.address)).eq(blockTime) + expect(await glpManager.getAumInUsdg(true)).eq("99700000000000000000") + + await bnb.mint(user1.address, expandDecimals(1, 18)) + await bnb.connect(user1).approve(glpManager.address, expandDecimals(1, 18)) + + await increaseTime(provider, 24 * 60 * 60 + 1) + await mineBlock(provider) + + await glpManager.connect(rewardRouter).addLiquidityForAccount( + user1.address, + user1.address, + bnb.address, + expandDecimals(1, 18), + expandDecimals(299, 18), + expandDecimals(299, 18) + ) + blockTime = await getBlockTime(provider) + + expect(await usdg.balanceOf(glpManager.address)).eq("398800000000000000000") // 398.8 + expect(await glp.balanceOf(user0.address)).eq("99700000000000000000") + expect(await glp.balanceOf(user1.address)).eq("299100000000000000000") + expect(await glp.totalSupply()).eq("398800000000000000000") + expect(await glpManager.lastAddedAt(user1.address)).eq(blockTime) + expect(await glpManager.getAumInUsdg(true)).eq("398800000000000000000") + + await expect(glpManager.connect(user1).removeLiquidityForAccount( + user1.address, + bnb.address, + "99700000000000000000", + expandDecimals(290, 18), + user1.address + )).to.be.revertedWith("GlpManager: forbidden") + + await expect(glpManager.connect(rewardRouter).removeLiquidityForAccount( + user1.address, + bnb.address, + "99700000000000000000", + expandDecimals(290, 18), + user1.address + )).to.be.revertedWith("GlpManager: cooldown duration not yet passed") + + await glpManager.connect(rewardRouter).removeLiquidityForAccount( + user0.address, + dai.address, + "79760000000000000000", // 79.76 + "79000000000000000000", // 79 + user0.address + ) + + expect(await dai.balanceOf(user0.address)).eq("79520720000000000000") + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await glp.balanceOf(user0.address)).eq("19940000000000000000") // 19.94 + }) +}) diff --git a/test/core/OrderBook/decreaseOrders.js b/test/core/OrderBook/decreaseOrders.js new file mode 100644 index 00000000..b0679014 --- /dev/null +++ b/test/core/OrderBook/decreaseOrders.js @@ -0,0 +1,462 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, reportGasUsed, gasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("../Vault/helpers") +const { getDefault, validateOrderFields, getTxFees, positionWrapper } = require('./helpers'); + +use(solidity); + +const BTC_PRICE = 60000; +const BNB_PRICE = 300; +const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; + +describe("OrderBook, decrease position orders", () => { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + + let orderBook; + let defaults; + let tokenDecimals; + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + busd = await deployContract("Token", []) + busdPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + reader = await deployContract("Reader", []) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(eth.address, ethPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + await vaultPriceFeed.setPriceSampleSpace(1); + + tokenDecimals = { + [bnb.address]: 18, + [dai.address]: 18, + [btc.address]: 8 + }; + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(BNB_PRICE)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + orderBook = await deployContract("OrderBook", []) + const minExecutionFee = 500000; + await orderBook.initialize( + router.address, + vault.address, + bnb.address, + usdg.address, + minExecutionFee, + expandDecimals(5, 30) // minPurchseTokenAmountUsd + ); + + await router.addPlugin(orderBook.address); + await router.connect(user0).approvePlugin(orderBook.address); + + await btc.mint(user0.address, expandDecimals(1000, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(100, 8)) + + await dai.mint(user0.address, expandDecimals(10000000, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(1000000, 18)) + + await dai.mint(user0.address, expandDecimals(20000000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(2000000, 18)) + await vault.directPoolDeposit(dai.address); + + await btc.mint(user0.address, expandDecimals(1000, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(100, 8)) + await vault.directPoolDeposit(btc.address); + + await bnb.mint(user0.address, expandDecimals(50000, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(10000, 18)) + await vault.directPoolDeposit(bnb.address); + + defaults = { + path: [btc.address], + sizeDelta: toUsd(100000), + amountIn: expandDecimals(1, 8), + minOut: 0, + triggerPrice: toUsd(53000), + triggerAboveThreshold: true, + executionFee: expandDecimals(1, 9).mul(1500000), + collateralToken: btc.address, + collateralDelta: toUsd(BTC_PRICE), + user: user0, + isLong: true + }; + }); + + function defaultCreateDecreaseOrder(props = {}) { + return orderBook.connect(getDefault(props, 'user', defaults.user)).createDecreaseOrder( + getDefault(props, 'indexToken', defaults.path[defaults.path.length - 1]), + getDefault(props, 'sizeDelta', defaults.sizeDelta), + getDefault(props, 'collateralToken', defaults.collateralToken), + getDefault(props, 'collateralDelta', defaults.collateralDelta), + getDefault(props, 'isLong', defaults.isLong), + getDefault(props, 'triggerPrice', defaults.triggerPrice), + getDefault(props, 'triggerAboveThreshold', defaults.triggerAboveThreshold), + {value: getDefault(props, 'value', props.executionFee || defaults.executionFee)} + ); + } + + async function getCreatedDecreaseOrder(address, orderIndex = 0) { + const order = await orderBook.decreaseOrders(address, orderIndex); + return order; + } + + /* + checklist: + [x] create order, low execution fee => revert + [x] create order, transferred ETH != execution fee => revert + [x] create order, order is retrievable + [x] executionFee transferred to OrderBook + [x] cancel order, delete order + [x] and user got back execution fee + [x] if cancelling order doesnt not exist => revert + [x] update order, all fields are new + [x] if user doesn't have such order => revert + [x] two orders retreivable + [x] execute order, if doesnt exist => revert + [x] if price is not valid => revert + [x] delete order + [x] position was decreased + [x] if collateral is weth => transfer BNB funds + [x] otherwise transfer token + [x] and transfer executionFee + [x] partial decrease + */ + + it("Create decrase order, bad fee", async() => { + await expect(defaultCreateDecreaseOrder({ + executionFee: 100 + })).to.be.revertedWith("OrderBook: insufficient execution fee"); + }) + + it("Create decrease order, long", async () => { + const tx = await defaultCreateDecreaseOrder(); + reportGasUsed(provider, tx, 'createDecraseOrder gas used'); + let order = await getCreatedDecreaseOrder(defaults.user.address); + const btcBalanceAfter = await btc.balanceOf(orderBook.address); + + expect(await bnb.balanceOf(orderBook.address), 'BNB balance').to.be.equal(defaults.executionFee); + + validateOrderFields(order, { + account: defaults.user.address, + indexToken: btc.address, + sizeDelta: defaults.sizeDelta, + collateralToken: defaults.collateralToken, + collateralDelta: defaults.collateralDelta, + isLong: true, + triggerPrice: defaults.triggerPrice, + triggerAboveThreshold: true, + executionFee: defaults.executionFee + }); + }); + + it("updateDecreaseOrder", async () => { + await defaultCreateDecreaseOrder(); + + const newSizeDelta = defaults.sizeDelta.add(100); + const newTriggerPrice = defaults.triggerPrice.add(100); + const newTriggerAboveThreshold = !defaults.triggerAboveThreshold; + const newCollateralDelta = defaults.collateralDelta.add(100); + + await expect(orderBook.connect(user1).updateDecreaseOrder( + 0, newCollateralDelta, newSizeDelta, newTriggerPrice, newTriggerAboveThreshold + )).to.be.revertedWith("OrderBook: non-existent order"); + + const tx2 = await orderBook.connect(defaults.user).updateDecreaseOrder( + 0, newCollateralDelta, newSizeDelta, newTriggerPrice, newTriggerAboveThreshold + ); + reportGasUsed(provider, tx2, 'updateDecreaseOrder gas used'); + + order = await getCreatedDecreaseOrder(user0.address); + + validateOrderFields(order, { + sizeDelta: newSizeDelta, + collateralDelta: newCollateralDelta, + triggerPrice: newTriggerPrice, + triggerAboveThreshold: newTriggerAboveThreshold + }); + }); + + it("Create decrease order, short", async () => { + const tx = await defaultCreateDecreaseOrder({ + isLong: false + }); + reportGasUsed(provider, tx, 'createDecreaseOrder gas used'); + const order = await getCreatedDecreaseOrder(defaults.user.address); + const btcBalanceAfter = await btc.balanceOf(orderBook.address); + + expect(await bnb.balanceOf(orderBook.address), 'BNB balance').to.be.equal(defaults.executionFee); + + validateOrderFields(order, { + account: defaults.user.address, + indexToken: btc.address, + sizeDelta: defaults.sizeDelta, + collateralToken: defaults.collateralToken, + collateralDelta: defaults.collateralDelta, + isLong: false, + triggerPrice: defaults.triggerPrice, + triggerAboveThreshold: true, + executionFee: defaults.executionFee + }); + }); + + it("Create two orders", async () => { + await defaultCreateDecreaseOrder({ + sizeDelta: toUsd(1) + }); + await defaultCreateDecreaseOrder({ + sizeDelta: toUsd(2) + }); + + const order1 = await getCreatedDecreaseOrder(defaults.user.address, 0); + const order2 = await getCreatedDecreaseOrder(defaults.user.address, 1); + + expect(order1.sizeDelta).to.be.equal(toUsd(1)); + expect(order2.sizeDelta).to.be.equal(toUsd(2)); + }); + + it("Execute decrease order, invalid price", async () => { + await vaultPriceFeed.setPriceSampleSpace(2); + let triggerPrice, isLong, triggerAboveThreshold, newBtcPrice; + let orderIndex = 0; + + // decrease long should use min price + // decrease short should use max price + for ([triggerPrice, isLong, triggerAboveThreshold, newBtcPrice, setPriceTwice] of [ + [expandDecimals(BTC_PRICE - 1000, 30), true, false, BTC_PRICE - 1050, false], + [expandDecimals(BTC_PRICE + 1000, 30), true, true, BTC_PRICE + 1050, true], + [expandDecimals(BTC_PRICE - 1000, 30), false, false, BTC_PRICE - 1050, true], + [expandDecimals(BTC_PRICE + 1000, 30), false, true, BTC_PRICE + 1050, false] + ]) { + // "reset" BTC price + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE)); + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE)); + + await defaultCreateDecreaseOrder({ + triggerPrice, + triggerAboveThreshold, + isLong + }); + + const order = await orderBook.decreaseOrders(defaults.user.address, orderIndex); + await expect(orderBook.executeDecreaseOrder(order.account, orderIndex, user1.address), 1) + .to.be.revertedWith("OrderBook: invalid price for execution"); + + if (setPriceTwice) { + // on first price update all limit orders are still invalid + btcPriceFeed.setLatestAnswer(toChainlinkPrice(newBtcPrice)); + await expect(orderBook.executeDecreaseOrder(order.account, orderIndex, user1.address), 2) + .to.be.revertedWith("OrderBook: invalid price for execution"); + } + + // now both min and max prices satisfies requirement + btcPriceFeed.setLatestAnswer(toChainlinkPrice(newBtcPrice)); + await expect(orderBook.executeDecreaseOrder(order.account, orderIndex, user1.address), 3) + .to.not.be.revertedWith("OrderBook: invalid price for execution"); + // so we are sure we passed price validations inside OrderBook + + orderIndex++; + } + }) + + it("Execute decrease order, non-existent", async () => { + await defaultCreateDecreaseOrder({ + triggerPrice: toUsd(BTC_PRICE - 1000), + triggerAboveThreshold: false + }); + + await expect(orderBook.executeDecreaseOrder(defaults.user.address, 1, user1.address)) + .to.be.revertedWith("OrderBook: non-existent order"); + }); + + it("Execute decrease order, long", async () => { + await btc.connect(defaults.user).transfer(vault.address, expandDecimals(10000, 8).div(BTC_PRICE)); + await vault.connect(defaults.user).increasePosition(defaults.user.address, btc.address, btc.address, toUsd(20000), true); + + const btcBalanceBefore = await btc.balanceOf(defaults.user.address); + let position = positionWrapper(await vault.getPosition(defaults.user.address, btc.address, btc.address, true)); + + await defaultCreateDecreaseOrder({ + collateralDelta: position.collateral, + sizeDelta: position.size, + triggerAboveThreshold: true, + triggerPrice: toUsd(BTC_PRICE + 5000), + isLong: true + }); + + const order = await orderBook.decreaseOrders(defaults.user.address, 0); + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE + 5050)); + + const executorBalanceBefore = await user1.getBalance(); + const tx = await orderBook.executeDecreaseOrder(defaults.user.address, 0, user1.address); + reportGasUsed(provider, tx, 'executeDecreaseOrder gas used'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter).to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const btcBalanceAfter = await btc.balanceOf(defaults.user.address); + expect(btcBalanceAfter.sub(btcBalanceBefore)).to.be.equal('17899051'); + + position = positionWrapper(await vault.getPosition(defaults.user.address, btc.address, btc.address, defaults.isLong)); + + expect(position.size).to.be.equal(0); + expect(position.collateral).to.be.equal(0); + + const orderAfter = await orderBook.increaseOrders(defaults.user.address, 0); + expect(orderAfter.account).to.be.equal(ZERO_ADDRESS); + }); + + it("Execute decrease order, short, BTC", async () => { + await dai.connect(defaults.user).transfer(vault.address, expandDecimals(10000, 18)); + await vault.connect(defaults.user).increasePosition(defaults.user.address, dai.address, btc.address, toUsd(20000), false); + + let position = positionWrapper(await vault.getPosition(defaults.user.address, dai.address, btc.address, false)); + const daiBalanceBefore = await dai.balanceOf(defaults.user.address); + + await defaultCreateDecreaseOrder({ + collateralDelta: position.collateral, + collateralToken: dai.address, + sizeDelta: position.size, + triggerAboveThreshold: false, + triggerPrice: toUsd(BTC_PRICE - 1000), + isLong: false + }); + const executor = user1; + + const order = await orderBook.decreaseOrders(defaults.user.address, 0); + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE - 1500)); + + const executorBalanceBefore = await executor.getBalance(); + + const tx = await orderBook.executeDecreaseOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeDecreaseOrder gas used'); + + const executorBalanceAfter = await executor.getBalance(); + expect(executorBalanceAfter).to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const daiBalanceAfter = await dai.balanceOf(defaults.user.address); + expect(daiBalanceAfter.sub(daiBalanceBefore)).to.be.equal("10460000000000000000000"); + + position = positionWrapper(await vault.getPosition(defaults.user.address, btc.address, btc.address, defaults.isLong)); + + expect(position.size).to.be.equal(0); + expect(position.collateral).to.be.equal(0); + + const orderAfter = await orderBook.increaseOrders(defaults.user.address, 0); + expect(orderAfter.account).to.be.equal(ZERO_ADDRESS); + }); + + it("Execute decrease order, long, BNB", async () => { + await router.connect(defaults.user).increasePositionETH( + [bnb.address], + bnb.address, + 0, + toUsd(3000), + true, + toUsd(301), + {value: expandDecimals(5, 18)} + ); + + let position = positionWrapper(await vault.getPosition(defaults.user.address, bnb.address, bnb.address, true)); + + const userTx = await defaultCreateDecreaseOrder({ + collateralDelta: position.collateral.div(2), + collateralToken: bnb.address, + indexToken: bnb.address, + sizeDelta: position.size.div(2), + triggerAboveThreshold: false, + triggerPrice: toUsd(BTC_PRICE - 1000), + isLong: true + }); + + reportGasUsed(provider, userTx, 'createSwapOrder'); + const userTxFee = await getTxFees(provider, userTx); + const order = await orderBook.decreaseOrders(defaults.user.address, 0); + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE - 1500)); + + const executor = user1; + + const balanceBefore = await defaults.user.getBalance(); + const executorBalanceBefore = await executor.getBalance(); + const tx = await orderBook.executeDecreaseOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeDecreaseOrder gas used'); + + const executorBalanceAfter = await executor.getBalance(); + expect(executorBalanceAfter).to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const balanceAfter = await defaults.user.getBalance(); + const amountOut = '2490000000000000000'; + expect(balanceAfter, 'balanceAfter').to.be.equal(balanceBefore.add(amountOut)); + + position = positionWrapper(await vault.getPosition(defaults.user.address, bnb.address, bnb.address, true)); + + expect(position.size, 'position.size').to.be.equal('1500000000000000000000000000000000'); + expect(position.collateral, 'position.collateral').to.be.equal('748500000000000000000000000000000'); + + const orderAfter = await orderBook.increaseOrders(defaults.user.address, 0); + expect(orderAfter.account).to.be.equal(ZERO_ADDRESS); + }); + + it("Cancel decrease order", async () => { + await defaultCreateDecreaseOrder(); + let order = await getCreatedDecreaseOrder(defaults.user.address); + expect(order.account).to.not.be.equal(ZERO_ADDRESS); + + await expect(orderBook.connect(defaults.user).cancelDecreaseOrder(1)) + .to.be.revertedWith("OrderBook: non-existent order"); + + const balanceBefore = await defaults.user.getBalance(); + const tx = await orderBook.connect(defaults.user).cancelDecreaseOrder(0); + reportGasUsed(provider, tx, 'cancelDecreaseOrder gas used'); + + order = await getCreatedDecreaseOrder(defaults.user.address); + expect(order.account).to.be.equal(ZERO_ADDRESS); + + const txFees = await getTxFees(provider, tx); + const balanceAfter = await defaults.user.getBalance(); + expect(balanceAfter).to.be.equal(balanceBefore.add(defaults.executionFee).sub(txFees)); + }); +}); diff --git a/test/core/OrderBook/generic.js b/test/core/OrderBook/generic.js new file mode 100644 index 00000000..992e356d --- /dev/null +++ b/test/core/OrderBook/generic.js @@ -0,0 +1,151 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("../Vault/helpers") + +use(solidity) + +const BTC_PRICE = 60000; +const BNB_PRICE = 300; + +describe("OrderBook", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + + let orderBook; + let defaults; + let tokenDecimals; + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + busd = await deployContract("Token", []) + busdPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + reader = await deployContract("Reader", []) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(eth.address, ethPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + await vaultPriceFeed.setPriceSampleSpace(1); + + tokenDecimals = { + [bnb.address]: 8, + [dai.address]: 18, + [btc.address]: 8 + }; + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(BNB_PRICE)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + orderBook = await deployContract("OrderBook", []) + const minExecutionFee = 500000; + await orderBook.initialize( + router.address, + vault.address, + bnb.address, + usdg.address, + minExecutionFee, + expandDecimals(5, 30) // minPurchseTokenAmountUsd + ); + + await router.addPlugin(orderBook.address); + await router.connect(user0).approvePlugin(orderBook.address); + + await btc.mint(user0.address, expandDecimals(1000, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(100, 8)) + + await dai.mint(user0.address, expandDecimals(10000000, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(1000000, 18)) + + await dai.mint(user0.address, expandDecimals(20000000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(2000000, 18)) + await vault.directPoolDeposit(dai.address); + + await btc.mint(user0.address, expandDecimals(1000, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(100, 8)) + await vault.directPoolDeposit(btc.address); + + await bnb.mint(user0.address, expandDecimals(10000, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(10000, 18)) + await vault.directPoolDeposit(bnb.address); + }); + + it("setGov", async () => { + await expect(orderBook.connect(user0).setGov(user1.address)).to.be.revertedWith("OrderBook: forbidden") + + expect(await orderBook.gov()).eq(wallet.address) + + await orderBook.setGov(user0.address) + expect(await orderBook.gov()).eq(user0.address) + + await orderBook.connect(user0).setGov(user1.address) + expect(await orderBook.gov()).eq(user1.address) + }); + + it("set*", async() => { + const cases = [ + ['setMinExecutionFee', 600000], + ['setMinPurchaseTokenAmountUsd', 1] + ]; + for (const [name, arg] of cases) { + await expect(orderBook.connect(user1)[name](arg)).to.be.revertedWith("OrderBook: forbidden"); + await expect(orderBook[name](arg)); + } + }) + + it("initialize, already initialized", async () => { + await expect(orderBook.connect(user1).initialize( + router.address, + vault.address, + bnb.address, + usdg.address, + 1, + expandDecimals(5, 30) // minPurchseTokenAmountUsd + )).to.be.revertedWith("OrderBook: forbidden"); + + await expect(orderBook.initialize( + router.address, + vault.address, + bnb.address, + usdg.address, + 1, + expandDecimals(5, 30) // minPurchseTokenAmountUsd + )).to.be.revertedWith("OrderBook: already initialized"); + }); +}); diff --git a/test/core/OrderBook/helpers.js b/test/core/OrderBook/helpers.js new file mode 100644 index 00000000..de57714a --- /dev/null +++ b/test/core/OrderBook/helpers.js @@ -0,0 +1,42 @@ +const { expect } = require("chai") + +function getDefault(obj, name, defaultValue) { + return (name in obj) ? obj[name] : defaultValue; +} + +function validateOrderFields(order, fields) { + for (const [key, value] of Object.entries(fields)) { + if (key === 'path') { + order.path.forEach((item, index) => { + expect(item, key).to.be.equal(value[index]); + }); + return; + } + if (value === true) return expect(order[key], key).to.be.true; + if (value === false) return expect(order[key], key).to.be.false; + expect(order[key], key).to.be.equal(value); + } +} + +async function getTxFees(provider, tx) { + const receipt = await provider.getTransactionReceipt(tx.hash); + // use receipt.effectiveGasPrice for newer versions of hardhat + return receipt.effectiveGasPrice.mul(receipt.gasUsed); +} + +function positionWrapper(position) { + return { + size: position[0], + collateral: position[1], + averagePrice: position[2], + entryFundingRate: position[3], + reserveAmount: position[4] + }; +} + +module.exports = { + getDefault, + validateOrderFields, + getTxFees, + positionWrapper +}; diff --git a/test/core/OrderBook/increaseOrders.js b/test/core/OrderBook/increaseOrders.js new file mode 100644 index 00000000..2b24758f --- /dev/null +++ b/test/core/OrderBook/increaseOrders.js @@ -0,0 +1,689 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, reportGasUsed, gasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("../Vault/helpers") +const { getDefault, validateOrderFields, getTxFees, positionWrapper } = require('./helpers'); + +use(solidity); + +const BTC_PRICE = 60000; +const BNB_PRICE = 300; +const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; +const PRICE_PRECISION = ethers.BigNumber.from(10).pow(30); +const BASIS_POINTS_DIVISOR = 10000; + +describe("OrderBook, increase position orders", function () { + /* + checklist: + create order + - [x] revert if fee too low + - [x] revert if fee != transferred BNB (if not WETH) + - [x] revert if fee + amountIn != transferred BNB (if WETH) + - [x] transfer execution fee + - [x] transfer token to OrderBook (if transfer token != WETH) + - [x] transfer execution fee + amount with BNB (if WETH) + - [x] swap tokens if path.length > 1 + - [x] revert if path.length > 3 + - [x] revert if transferred collateral usd is too low + - [x] create order with provided fields + cancel order + - [x] revert if order doesn't exist + - [x] delete order + - [x] transfer BNB if WETH + - [x] transfer BNB and token if not WETH + update + - [x] revert if does not exist + - [x] update all fields provided + execute + - [x] revert if does not exist + - [x] revert if price invalid + - [x] currentPrice < triggerPrice && triggerAboveThreshold is true + - [x] currentPrice > triggerPrice && triggerAboveThreshold is false + - [x] delete order + - [x] open position + - [x] position.collateral == order.collateral + - [x] position.size == order.sizeDelta (if new) + - [x] position.size == order.sizeDelta + positionBefore.size (if not new) + - [x] pay fees to executor + */ + + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + + let orderBook; + let defaults; + let tokenDecimals; + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + busd = await deployContract("Token", []) + busdPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + reader = await deployContract("Reader", []) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(eth.address, ethPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + await vaultPriceFeed.setPriceSampleSpace(1); + + tokenDecimals = { + [bnb.address]: 18, + [dai.address]: 18, + [btc.address]: 8 + }; + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(BNB_PRICE)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + orderBook = await deployContract("OrderBook", []) + const minExecutionFee = 500000; + await orderBook.initialize( + router.address, + vault.address, + bnb.address, + usdg.address, + minExecutionFee, + expandDecimals(5, 30) // minPurchseTokenAmountUsd + ); + + await router.addPlugin(orderBook.address); + await router.connect(user0).approvePlugin(orderBook.address); + + await btc.mint(user0.address, expandDecimals(1000, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(100, 8)) + + await dai.mint(user0.address, expandDecimals(10000000, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(1000000, 18)) + + await bnb.mint(user0.address, expandDecimals(10000000, 18)) + await bnb.connect(user0).approve(router.address, expandDecimals(1000000, 18)) + + await dai.mint(user0.address, expandDecimals(20000000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(2000000, 18)) + await vault.directPoolDeposit(dai.address); + + await btc.mint(user0.address, expandDecimals(1000, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(100, 8)) + await vault.directPoolDeposit(btc.address); + + await bnb.mint(user0.address, expandDecimals(50000, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(10000, 18)) + await vault.directPoolDeposit(bnb.address); + + defaults = { + path: [btc.address], + sizeDelta: toUsd(100000), + amountIn: expandDecimals(1, 8), + minOut: 0, + triggerPrice: toUsd(53000), + triggerAboveThreshold: true, + executionFee: expandDecimals(1, 9).mul(1500000), + collateralToken: btc.address, + collateralDelta: toUsd(BTC_PRICE), + user: user0, + isLong: true, + shouldWrap: false + }; + }); + + async function getCreatedIncreaseOrder(address, orderIndex = 0) { + const order = await orderBook.increaseOrders(address, orderIndex); + return order; + } + + function defaultCreateIncreaseOrder(props = {}) { + return orderBook.connect(getDefault(props, 'user', defaults.user)).createIncreaseOrder( + getDefault(props, 'path', defaults.path), + getDefault(props, 'amountIn', defaults.amountIn), + getDefault(props, 'indexToken', defaults.path[defaults.path.length - 1]), + getDefault(props, 'minOut', defaults.minOut), + getDefault(props, 'sizeDelta', defaults.sizeDelta), + getDefault(props, 'collateralToken', defaults.collateralToken), // _collateralToken + getDefault(props, 'isLong', defaults.isLong), + getDefault(props, 'triggerPrice', defaults.triggerPrice), + getDefault(props, 'triggerAboveThreshold', defaults.triggerAboveThreshold), + getDefault(props, 'executionFee', defaults.executionFee), + getDefault(props, 'shouldWrap', defaults.shouldWrap), + {value: getDefault(props, 'value', props.executionFee || defaults.executionFee)} + ); + } + + it("createIncreaseOrder, bad input", async () => { + const lowExecutionFee = 100; + let counter = 0; + await expect(defaultCreateIncreaseOrder({ + executionFee: lowExecutionFee + }), counter++).to.be.revertedWith("OrderBook: insufficient execution fee"); + + const goodExecutionFee = expandDecimals(1, 8); + await expect(defaultCreateIncreaseOrder({ + executionFee: goodExecutionFee, + value: goodExecutionFee - 1 + }), counter++).to.be.revertedWith("OrderBook: incorrect execution fee transferred"); + await expect(defaultCreateIncreaseOrder({ + executionFee: goodExecutionFee, + value: goodExecutionFee + 1 + }), counter++).to.be.revertedWith("OrderBook: incorrect execution fee transferred"); + + await expect(defaultCreateIncreaseOrder({ + path: [bnb.address], + executionFee: goodExecutionFee, + value: expandDecimals(10, 8).add(goodExecutionFee).sub(1), + shouldWrap: true + }), counter++).to.be.revertedWith("OrderBook: incorrect value transferred"); + + await expect(defaultCreateIncreaseOrder({ + path: [btc.address], + executionFee: goodExecutionFee, + value: expandDecimals(10, 8).add(goodExecutionFee), + shouldWrap: true + }), counter++).to.be.revertedWith("OrderBook: only weth could be wrapped"); + + await expect(defaultCreateIncreaseOrder({ + path: [bnb.address] , + executionFee: goodExecutionFee, + amountIn: expandDecimals(10, 8), + value: expandDecimals(10, 8).add(goodExecutionFee), + shouldWrap: false + }), counter++).to.be.revertedWith("OrderBook: incorrect execution fee transferred"); + + await expect(defaultCreateIncreaseOrder({ + path: [dai.address], + amountIn: expandDecimals(4, 18) + }), counter++).to.be.revertedWith("OrderBook: insufficient collateral"); + + await expect(defaultCreateIncreaseOrder({ + path: [dai.address, btc.address, bnb.address, btc.address], + amountIn: expandDecimals(4, 18) + }), counter++).to.be.revertedWith("OrderBook: invalid _path.length"); + }); + + it("createIncreaseOrder, two orders", async () => { + const sizeDelta1 = toUsd(40000); + await defaultCreateIncreaseOrder({ + path: [btc.address], + amountIn: expandDecimals(1, 8).div(10), + sizeDelta: sizeDelta1 + }); + const sizeDelta2 = toUsd(50000); + await defaultCreateIncreaseOrder({ + path: [btc.address], + amountIn: expandDecimals(1, 8).div(10), + sizeDelta: sizeDelta2 + }); + + const order1 = await getCreatedIncreaseOrder(defaults.user.address, 0); + const order2 = await getCreatedIncreaseOrder(defaults.user.address, 1); + + expect(order1.sizeDelta).to.be.equal(sizeDelta1); + expect(order2.sizeDelta).to.be.equal(sizeDelta2); + }); + + it("createIncreaseOrder, pay WETH", async () => { + const bnbBalanceBefore = await bnb.balanceOf(orderBook.address); + const amountIn = expandDecimals(30, 18); + const value = defaults.executionFee; + const tx = await defaultCreateIncreaseOrder({ + path: [bnb.address], + amountIn, + value, + shouldWrap: false + }); + + reportGasUsed(provider, tx, 'createIncreaseOrder gas used'); + + const order = await getCreatedIncreaseOrder(user0.address); + const bnbBalanceAfter = await bnb.balanceOf(orderBook.address); + + const bnbBalanceDiff = bnbBalanceAfter.sub(bnbBalanceBefore); + expect(bnbBalanceDiff, 'BNB balance').to.be.equal(amountIn.add(defaults.executionFee)); + + validateOrderFields(order, { + account: defaults.user.address, + purchaseToken: bnb.address, + purchaseTokenAmount: amountIn, + indexToken: btc.address, + sizeDelta: defaults.sizeDelta, + isLong: true, + triggerPrice: defaults.triggerPrice, + triggerAboveThreshold: true, + executionFee: defaults.executionFee + }); + }); + + it("createIncreaseOrder, pay BNB", async () => { + const bnbBalanceBefore = await bnb.balanceOf(orderBook.address); + const amountIn = expandDecimals(30, 18); + const value = defaults.executionFee.add(amountIn); + const tx = await defaultCreateIncreaseOrder({ + path: [bnb.address], + amountIn, + value, + shouldWrap: true + }); + + reportGasUsed(provider, tx, 'createIncreaseOrder gas used'); + + const order = await getCreatedIncreaseOrder(user0.address); + const bnbBalanceAfter = await bnb.balanceOf(orderBook.address); + + const bnbBalanceDiff = bnbBalanceAfter.sub(bnbBalanceBefore); + expect(bnbBalanceDiff, 'BNB balance').to.be.equal(amountIn.add(defaults.executionFee)); + + validateOrderFields(order, { + account: defaults.user.address, + purchaseToken: bnb.address, + purchaseTokenAmount: amountIn, + indexToken: btc.address, + sizeDelta: defaults.sizeDelta, + isLong: true, + triggerPrice: defaults.triggerPrice, + triggerAboveThreshold: true, + executionFee: defaults.executionFee + }); + }); + + it("createIncreaseOrder, long A, transfer and purchase A", async () => { + const btcBalanceBefore = await btc.balanceOf(orderBook.address); + const tx = await defaultCreateIncreaseOrder(); + reportGasUsed(provider, tx, 'createIncreaseOrder gas used'); + + const order = await getCreatedIncreaseOrder(user0.address); + const btcBalanceAfter = await btc.balanceOf(orderBook.address); + + expect(await bnb.balanceOf(orderBook.address), 'BNB balance').to.be.equal(defaults.executionFee); + expect(btcBalanceAfter.sub(btcBalanceBefore), 'BTC balance').to.be.equal(defaults.amountIn); + + validateOrderFields(order, { + account: defaults.user.address, + purchaseToken: btc.address, + purchaseTokenAmount: defaults.amountIn, + indexToken: btc.address, + sizeDelta: defaults.sizeDelta, + isLong: true, + triggerPrice: defaults.triggerPrice, + triggerAboveThreshold: true, + executionFee: defaults.executionFee + }); + }); + + it("createIncreaseOrder, long A, transfer A, purchase B", async () => { + const daiBalanceBefore = await dai.balanceOf(orderBook.address); + const tx = await defaultCreateIncreaseOrder({ + path: [btc.address, dai.address] + }); + reportGasUsed(provider, tx, 'createIncreaseOrder gas used'); + const daiBalanceAfter = await dai.balanceOf(orderBook.address); + const order = await getCreatedIncreaseOrder(defaults.user.address); + + expect(await bnb.balanceOf(orderBook.address), 'BNB balance').to.be.equal(defaults.executionFee); + expect(daiBalanceAfter, 'daiBalanceAfter').to.be.equal(daiBalanceBefore.add('59820000000000000000000')); + + validateOrderFields(order, { + account: defaults.user.address, + purchaseToken: dai.address, + indexToken: btc.address, + sizeDelta: defaults.sizeDelta, + isLong: true, + triggerPrice: defaults.triggerPrice, + triggerAboveThreshold: true, + executionFee: defaults.executionFee, + purchaseTokenAmount: '59820000000000000000000' + }); + }); + + it("createIncreaseOrder, short A, transfer B, purchase B", async () => { + const daiBalanceBefore = await dai.balanceOf(orderBook.address); + const amountIn = expandDecimals(30000, 18); + const tx = await defaultCreateIncreaseOrder({ + path: [dai.address], + amountIn, + isLong: false, + triggerAboveThreshold: true + }); + reportGasUsed(provider, tx, 'createIncreaseOrder gas used'); + const daiBalanceAfter = await dai.balanceOf(orderBook.address); + + const order = await getCreatedIncreaseOrder(defaults.user.address); + expect(await bnb.balanceOf(orderBook.address)).to.be.equal(defaults.executionFee); + expect(daiBalanceAfter.sub(daiBalanceBefore), 'daiBalanceAfter').to.be.equal(amountIn); + + validateOrderFields(order, { + account: defaults.user.address, + purchaseToken: dai.address, + indexToken: btc.address, + sizeDelta: defaults.sizeDelta, + isLong: false, + triggerPrice: defaults.triggerPrice, + triggerAboveThreshold: true, + executionFee: defaults.executionFee + }); + }); + + it("createIncreaseOrder, short A, transfer A, purchase B", async () => { + const daiBalanceBefore = await dai.balanceOf(orderBook.address); + const tx = await defaultCreateIncreaseOrder({ + path: [btc.address, dai.address], + isLong: false, + triggerAboveThreshold: true + }); + reportGasUsed(provider, tx, 'createIncreaseOrder gas used'); + const daiBalanceAfter = await dai.balanceOf(orderBook.address); + + const order = await getCreatedIncreaseOrder(defaults.user.address); + + expect(await bnb.balanceOf(orderBook.address)).to.be.equal(defaults.executionFee); + expect(daiBalanceAfter).to.be.equal(daiBalanceBefore.add('59820000000000000000000')); + + validateOrderFields(order, { + account: defaults.user.address, + purchaseToken: dai.address, + indexToken: btc.address, + sizeDelta: defaults.sizeDelta, + isLong: false, + triggerPrice: defaults.triggerPrice, + triggerAboveThreshold: true, + executionFee: defaults.executionFee + }); + expect(order.purchaseTokenAmount).to.be.equal('59820000000000000000000'); + }); + + it("updateIncreaseOrder", async () => { + await defaultCreateIncreaseOrder(); + + const newSizeDelta = defaults.sizeDelta.add(100); + const newTriggerPrice = defaults.triggerPrice.add(100); + const newTriggerAboveThreshold = !defaults.triggerAboveThreshold; + + await expect(orderBook.connect(user1).updateIncreaseOrder(0, newSizeDelta, newTriggerPrice, newTriggerAboveThreshold)) + .to.be.revertedWith("OrderBook: non-existent order"); + + const tx = await orderBook.connect(user0).updateIncreaseOrder(0, newSizeDelta, newTriggerPrice, newTriggerAboveThreshold); + reportGasUsed(provider, tx, 'updateIncreaseOrder gas used'); + + order = await getCreatedIncreaseOrder(user0.address); + + validateOrderFields(order, { + sizeDelta: newSizeDelta, + triggerPrice: newTriggerPrice, + triggerAboveThreshold: newTriggerAboveThreshold + }); + }); + + it("cancelOrder", async () => { + const bnbBalanceBefore = await defaults.user.getBalance(); + const tokenBalanceBefore = await btc.balanceOf(defaults.user.address); + const tx1 = await defaultCreateIncreaseOrder(); + let txFees = await getTxFees(provider, tx1); + + await expect(orderBook.connect(user1).cancelIncreaseOrder(0)).to.be.revertedWith("OrderBook: non-existent order"); + + const tx2 = await orderBook.connect(user0).cancelIncreaseOrder(0); + reportGasUsed(provider, tx2, 'cancelIncreaseOrder gas used'); + + txFees = txFees.add(await getTxFees(provider, tx2)); + const bnbBalanceAfter = await defaults.user.getBalance(); + expect(bnbBalanceAfter, 'bnbBalanceAfter') + .to.be.equal(bnbBalanceBefore.sub(txFees)); + + const tokenBalanceAfter = await btc.balanceOf(defaults.user.address); + expect(tokenBalanceAfter, 'tokenBalanceAfter').to.be.equal(tokenBalanceBefore); + + const order = await getCreatedIncreaseOrder(defaults.user.address); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("cancelOrder, pay BNB", async () => { + const balanceBefore = await defaults.user.getBalance(); + const bnbBalanceBefore = await bnb.balanceOf(orderBook.address); + const amountIn = expandDecimals(30, 18); + const value = defaults.executionFee.add(amountIn); + const tx1 = await defaultCreateIncreaseOrder({ + path: [bnb.address], + amountIn, + value, + shouldWrap: true + }); + let txFees = await getTxFees(provider, tx1); + + await expect(orderBook.connect(user1).cancelIncreaseOrder(0)).to.be.revertedWith("OrderBook: non-existent order"); + + const tx2 = await orderBook.connect(user0).cancelIncreaseOrder(0); + reportGasUsed(provider, tx2, 'cancelIncreaseOrder gas used'); + txFees = txFees.add(await getTxFees(provider, tx2)); + + const balanceAfter = await defaults.user.getBalance(); + expect(balanceAfter, "balanceAfter").to.be.equal(balanceBefore.sub(txFees)); + + const order = await getCreatedIncreaseOrder(defaults.user.address); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeOrder, non-existent order", async () => { + await expect(orderBook.executeIncreaseOrder(user3.address, 0, user1.address)).to.be.revertedWith("OrderBook: non-existent order"); + }); + + it("executeOrder, current price is invalid", async () => { + let triggerPrice, isLong, triggerAboveThreshold, newBtcPrice; + let orderIndex = 0; + + // increase long should use max price + // increase short should use min price + for ([triggerPrice, isLong, collateralToken, triggerAboveThreshold, newBtcPrice, setPriceTwice] of [ + [expandDecimals(BTC_PRICE - 1000, 30), true, btc.address, false, BTC_PRICE - 1050, true], + [expandDecimals(BTC_PRICE + 1000, 30), true, btc.address, true, BTC_PRICE + 1050, false], + [expandDecimals(BTC_PRICE - 1000, 30), false, dai.address, false, BTC_PRICE - 1050, false], + [expandDecimals(BTC_PRICE + 1000, 30), false, dai.address, true, BTC_PRICE + 1050, true] + ]) { + await vaultPriceFeed.setPriceSampleSpace(2); + + // "reset" BTC price + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE)); + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE)); + + await defaultCreateIncreaseOrder({ + triggerPrice, + isLong, + triggerAboveThreshold, + collateralToken + }); + const order = await orderBook.increaseOrders(defaults.user.address, orderIndex); + await expect(orderBook.executeIncreaseOrder(order.account, orderIndex, user1.address)) + .to.be.revertedWith("OrderBook: invalid price for execution"); + + if (setPriceTwice) { + // in this case on first price order is still non-executable because of current price + btcPriceFeed.setLatestAnswer(toChainlinkPrice(newBtcPrice)); + await expect(orderBook.executeIncreaseOrder(order.account, orderIndex, user1.address)) + .to.be.revertedWith("OrderBook: invalid price for execution"); + } + + // now both min and max prices satisfies requirement + btcPriceFeed.setLatestAnswer(toChainlinkPrice(newBtcPrice)); + await orderBook.executeIncreaseOrder(order.account, orderIndex, user1.address); + + orderIndex++; + } + }); + + it("executeOrder, long, purchase token same as collateral", async () => { + await defaultCreateIncreaseOrder(); + + const order = await orderBook.increaseOrders(defaults.user.address, 0); + + const executorBalanceBefore = await user1.getBalance(); + const tx = await orderBook.executeIncreaseOrder(defaults.user.address, 0, user1.address); + reportGasUsed(provider, tx, 'executeIncreaseOrder gas used'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter).to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const position = positionWrapper(await vault.getPosition(defaults.user.address, btc.address, btc.address, defaults.isLong)); + expect(position.collateral).to.be.equal('59900000000000000000000000000000000'); + expect(position.size).to.be.equal(order.sizeDelta); + + const orderAfter = await orderBook.increaseOrders(defaults.user.address, 0); + expect(orderAfter.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executOrder, 2 orders with the same position", async () => { + await defaultCreateIncreaseOrder(); + + await orderBook.executeIncreaseOrder(defaults.user.address, 0, user1.address); + let position = positionWrapper(await vault.getPosition(defaults.user.address, btc.address, btc.address, defaults.isLong)); + expect(position.collateral).to.be.equal('59900000000000000000000000000000000'); + expect(position.size).to.be.equal(defaults.sizeDelta); + + await defaultCreateIncreaseOrder(); + + await orderBook.executeIncreaseOrder(defaults.user.address, 1, user1.address); + position = positionWrapper(await vault.getPosition(defaults.user.address, btc.address, btc.address, defaults.isLong)); + expect(position.collateral).to.be.equal('119800000000000000000000000000000000'); + expect(position.size).to.be.equal(defaults.sizeDelta.mul(2)); + }); + + it("executeOrder, long, swap purchase token to collateral", async () => { + await defaultCreateIncreaseOrder({ + path: [dai.address], + amountIn: expandDecimals(50000, 18) + }); + + const executorBalanceBefore = await user1.getBalance(); + const order = await orderBook.increaseOrders(defaults.user.address, 0); + const tx = await orderBook.executeIncreaseOrder(defaults.user.address, 0, user1.address); + reportGasUsed(provider, tx, 'executeIncreaseOrder gas used'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter).to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const position = positionWrapper(await vault.getPosition(user0.address, btc.address, btc.address, defaults.isLong)); + expect(position.size, 'size').to.be.equal(order.sizeDelta); + expect(position.collateral, 'collateral').to.be.equal('49749999800000000000000000000000000'); + + const orderAfter = await orderBook.increaseOrders(defaults.user.address, 0); + expect(orderAfter.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeOrder, short, purchase token same as collateral", async () => { + dai.mint(user0.address, expandDecimals(50000, 18)); + await defaultCreateIncreaseOrder({ + path: [dai.address], + collateralToken: dai.address, + isLong: false, + amountIn: expandDecimals(50000, 18), + triggerAboveThreshold: true, + triggerPrice: expandDecimals(BTC_PRICE - 100, 30) + }); + + const executorBalanceBefore = await user1.getBalance(); + + const order = await orderBook.increaseOrders(defaults.user.address, 0); + const tx = await orderBook.executeIncreaseOrder(defaults.user.address, 0, user1.address); + reportGasUsed(provider, tx, 'executeIncreaseOrder gas used'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter).to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const position = positionWrapper(await vault.getPosition(defaults.user.address, dai.address, btc.address, false)); + expect(position.collateral).to.be.equal('49900000000000000000000000000000000'); + expect(position.size, 'position.size').to.be.equal(order.sizeDelta); + + const orderAfter = await orderBook.increaseOrders(defaults.user.address, 0); + expect(orderAfter.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeOrder, short, swap purchase token to collateral", async () => { + await defaultCreateIncreaseOrder({ + isLong: false, + collateralToken: dai.address, + triggerAboveThreshold: true, + triggerPrice: expandDecimals(BTC_PRICE - 100, 30) + }); + + const executorBalanceBefore = await user1.getBalance(); + + const order = await orderBook.increaseOrders(defaults.user.address, 0); + const tx = await orderBook.executeIncreaseOrder(defaults.user.address, 0, user1.address); + reportGasUsed(provider, tx, 'executeIncreaseOrder gas used'); + + const position = positionWrapper(await vault.getPosition(user0.address, dai.address, btc.address, false)); + expect(position.collateral).to.be.equal('59720000000000000000000000000000000'); + expect(position.size, 'position.size').to.be.equal(order.sizeDelta); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter).to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const orderAfter = await orderBook.increaseOrders(defaults.user.address, 0); + expect(orderAfter.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeOrder, short, pay BNB, no swap", async () => { + const amountIn = expandDecimals(50, 18); + const value = defaults.executionFee.add(amountIn) + await defaultCreateIncreaseOrder({ + path: [bnb.address], + amountIn, + value, + indexToken: bnb.address, + collateralToken: dai.address, + isLong: false, + triggerboveThreshold: true, + triggerPrice: expandDecimals(BNB_PRICE - 10, 30), + shouldWrap: true + }); + + const order = await orderBook.increaseOrders(defaults.user.address, 0); + const tx = await orderBook.executeIncreaseOrder(defaults.user.address, 0, user1.address); + reportGasUsed(provider, tx, 'executeIncreaseOrder gas used'); + + const position = positionWrapper(await vault.getPosition(user0.address, dai.address, bnb.address, false)); + expect(position.collateral).to.be.equal('14855000000000000000000000000000000'); + expect(position.size, 'position.size').to.be.equal(order.sizeDelta); + + const orderAfter = await orderBook.increaseOrders(defaults.user.address, 0); + expect(orderAfter.account).to.be.equal(ZERO_ADDRESS); + }); + + it("createIncreaseOrder, bad path", async () => { + await expect(defaultCreateIncreaseOrder({ + path: [btc.address, btc.address] + })).to.be.revertedWith("OrderBook: invalid _path"); + }); +}); diff --git a/test/core/OrderBook/swapOrders.js b/test/core/OrderBook/swapOrders.js new file mode 100644 index 00000000..21182458 --- /dev/null +++ b/test/core/OrderBook/swapOrders.js @@ -0,0 +1,1007 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, reportGasUsed, gasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("../Vault/helpers") +const { getDefault, validateOrderFields, getTxFees } = require('./helpers'); + +use(solidity); + +const BTC_PRICE = 60000; +const BNB_PRICE = 300; +const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; +const PRICE_PRECISION = ethers.BigNumber.from(10).pow(30); +const BASIS_POINTS_DIVISOR = 10000; + +describe("OrderBook, swap orders", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + + let orderBook; + let defaults; + let tokenDecimals; + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + busd = await deployContract("Token", []) + busdPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + reader = await deployContract("Reader", []) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(eth.address, ethPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + await vaultPriceFeed.setPriceSampleSpace(1); + + tokenDecimals = { + [bnb.address]: 18, + [dai.address]: 18, + [usdg.address]: 18, + [btc.address]: 8 + }; + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(BNB_PRICE)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + orderBook = await deployContract("OrderBook", []) + const minExecutionFee = 500000; + await orderBook.initialize( + router.address, + vault.address, + bnb.address, + usdg.address, + minExecutionFee, + expandDecimals(5, 30) // minPurchseTokenAmountUsd + ); + + await router.addPlugin(orderBook.address); + await router.connect(user0).approvePlugin(orderBook.address); + + await btc.mint(user0.address, expandDecimals(1000, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(100, 8)) + + await dai.mint(user0.address, expandDecimals(10000000, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(1000000, 18)) + + await dai.mint(user0.address, expandDecimals(20000000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(2000000, 18)) + await vault.directPoolDeposit(dai.address); + + // it's impossible to just mint usdg (?) + await router.connect(user0).swap( + [dai.address, usdg.address], + expandDecimals(10000, 18), + expandDecimals(9900, 18), + user0.address + ); + await usdg.connect(user0).approve(router.address, expandDecimals(9900, 18)); + + await btc.mint(user0.address, expandDecimals(1000, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(100, 8)) + await vault.directPoolDeposit(btc.address); + + await bnb.mint(user0.address, expandDecimals(100000, 18)) + await bnb.connect(user0).approve(router.address, expandDecimals(50000, 18)) + + await bnb.connect(user0).transfer(vault.address, expandDecimals(10000, 18)) + await vault.directPoolDeposit(bnb.address); + // probably I'm doing something wrong? contract doesn't have enough funds + // when I need to withdraw weth (which I have in balances) + await bnb.deposit({value: expandDecimals(500, 18)}); + + defaults = { + path: [dai.address, btc.address], + sizeDelta: toUsd(100000), + minOut: 0, + amountIn: expandDecimals(1000, 18), + triggerPrice: toUsd(53000), + triggerAboveThreshold: true, + executionFee: expandDecimals(1, 9).mul(1500000), + collateralToken: btc.address, + collateralDelta: toUsd(BTC_PRICE), + user: user0, + isLong: true, + shouldWrap: false, + shouldUnwrap: true + }; + }) + + function getSwapFees(token, amount) { + // ideally to get all this from Vault in runtime + // + let feesPoints; + if ([dai.address, busd.address, usdg.address].includes(token)) { + feesPoints = 4; + } else { + feesPoints = 30; + } + return amount.mul(feesPoints).div(BASIS_POINTS_DIVISOR); + } + + function applySwapFees(token, amount) { + const fees = getSwapFees(token, amount); + return amount.sub(fees) + } + + async function getMinOut(triggerRatio, path, amountIn) { + const tokenAPrecision = expandDecimals(1, tokenDecimals[path[0]]); + const tokenBPrecision = expandDecimals(1, tokenDecimals[path[path.length - 1]]); + + let minOut = (amountIn.mul(PRICE_PRECISION).div(triggerRatio)) + .mul(tokenBPrecision).div(tokenAPrecision); + const swapFees = getSwapFees(path[path.length - 1], minOut); + return minOut.sub(swapFees); + } + + function getTriggerRatio(tokenAUsd, tokenBUsd) { + return tokenBUsd.mul(PRICE_PRECISION).div(tokenAUsd); + } + + async function defaultCreateSwapOrder(props = {}) { + if (!('triggerRatio' in props) && !('minOut' in props)) { + throw new Error('Either triggerRatio or minOut should be provided'); + }; + + props.triggerRatio = props.triggerRatio || 0; + props.amountIn = getDefault(props, 'amountIn', defaults.amountIn); + props.path = getDefault(props, 'path', defaults.path); + props.minOut = props.minOut || await getMinOut(props.triggerRatio, props.path, props.amountIn); + props.triggerAboveThreshold = getDefault(props, 'triggerAboveThreshold', defaults.triggerAboveThreshold); + props.executionFee = getDefault(props, 'executionFee', defaults.executionFee); + props.value = getDefault(props, 'value', props.executionFee || defaults.executionFee); + props.shouldWrap = getDefault(props, 'shouldWrap', defaults.shouldWrap); + props.shouldUnwrap = getDefault(props, 'shouldUnwrap', defaults.shouldUnwrap); + + const tx = await orderBook.connect(getDefault(props, 'user', defaults.user)).createSwapOrder( + props.path, + props.amountIn, + props.minOut, + props.triggerRatio, + props.triggerAboveThreshold, + props.executionFee, + props.shouldWrap, + props.shouldUnwrap, + {value: props.value} + ); + + return [tx, props]; + } + + async function getCreatedSwapOrder(address, orderIndex = 0) { + const order = await orderBook.swapOrders(address, orderIndex); + return order; + } + + /* + checklist: + [x] create order, path.length not in (2, 3) => revert + [x] create order, path[0] == path[-1] => revert + [x] executionFee less than minimum =< revert + [x] if path[0] == weth -> transfer fee + amountIn + [x] transferred token == amountIn + [x] and check total transfer, otherwise revert + [x] if path[0] != weth -> transfer fee and transfer token separately + [x] and check total transfer, otherwise => revert + [x] order retreivable + [x] two orders retreivable + [x] cancel order deletes order + [x] and returns amountIn as token + fees as BNB if path[0] != weth + [x] otherwise returns fees + amountIn as BNB + [x] execute order – revert if doest not exist + [x] if trigger below and minOut insufficient -> revert + [x] if trigger above and priceRatio is incorrect -> revert + [x] if priceRatio correct but minOut insufficient -> revert + [x] if coniditions are met executor receives fee + [x] user receives BNB if path[-1] == weth + [x] or token otherwise + [x] order is deleted after execution + [x] user can update minOut, triggerRatio and triggerAboveThreshold + [x] if order doesn't exist => revert + */ + + it("createSwapOrder, bad input", async () => { + await expect(defaultCreateSwapOrder({ + path: [btc.address], + triggerRatio: 1 + }), 1).to.be.revertedWith("OrderBook: invalid _path.length"); + + await expect(defaultCreateSwapOrder({ + path: [btc.address, btc.address, dai.address, dai.address], + triggerRatio: 1 + }), 2).to.be.revertedWith("OrderBook: invalid _path.length"); + + await expect(defaultCreateSwapOrder({ + path: [btc.address, bnb.address], + triggerRatio: 1, + shouldWrap: true + })).to.be.revertedWith("OrderBook: only weth could be wrapped"); + + await expect(defaultCreateSwapOrder({ + path: [btc.address, btc.address], + triggerRatio: 1 + }), 3).to.be.revertedWith("OrderBook: invalid _path"); + + await expect(defaultCreateSwapOrder({ + path: [dai.address, btc.address], + triggerRatio: 1, + executionFee: 100 + }), 4).to.be.revertedWith("OrderBook: insufficient execution fee"); + + await expect(defaultCreateSwapOrder({ + path: [dai.address, btc.address], + triggerRatio: 1, + value: 100 + }), 5).to.be.revertedWith("OrderBook: incorrect execution fee transferred"); + }); + + it("createSwapOrder, DAI -> BTC", async () => { + const triggerRatio = toUsd(1).mul(PRICE_PRECISION).div(toUsd(58000)); + let tx, props; + const userDaiBalanceBefore = await dai.balanceOf(defaults.user.address); + [tx, props] = await defaultCreateSwapOrder({ + triggerRatio, + triggerAboveThreshold: false + }); + reportGasUsed(provider, tx, "createSwapOrder"); + const userDaiBalanceAfter = await dai.balanceOf(defaults.user.address); + expect(userDaiBalanceAfter).to.be.equal(userDaiBalanceBefore.sub(defaults.amountIn)); + + const daiBalance = await dai.balanceOf(orderBook.address); + expect(daiBalance).to.be.equal(defaults.amountIn); + const bnbBalance = await bnb.balanceOf(orderBook.address); + expect(bnbBalance).to.be.equal(defaults.executionFee); + + const order = await getCreatedSwapOrder(defaults.user.address); + + validateOrderFields(order, { + account: defaults.user.address, + triggerRatio, + triggerAboveThreshold: false, + path: [dai.address, btc.address], + minOut: props.minOut, + amountIn: defaults.amountIn, + executionFee: defaults.executionFee + }); + }); + + it("createSwapOrder, WBNB -> DAI", async () => { + const triggerRatio = getTriggerRatio(toUsd(550), toUsd(1)); + const amountIn = expandDecimals(10, 18); + + await expect(defaultCreateSwapOrder({ + path: [bnb.address, dai.address], + triggerRatio, + triggerAboveThreshold: false, + amountIn, + value: defaults.executionFee.sub(1) + })).to.be.revertedWith("OrderBook: incorrect execution fee transferred"); + + await expect(defaultCreateSwapOrder({ + path: [bnb.address, dai.address], + triggerRatio, + triggerAboveThreshold: false, + amountIn, + value: defaults.executionFee.add(1) + })).to.be.revertedWith("OrderBook: incorrect execution fee transferred"); + + let tx, props; + [tx, props] = await defaultCreateSwapOrder({ + path: [bnb.address, dai.address], + triggerRatio, + triggerAboveThreshold: false, + amountIn, + value: defaults.executionFee + }); + reportGasUsed(provider, tx, "createSwapOrder"); + const bnbBalance = await bnb.balanceOf(orderBook.address); + expect(bnbBalance).to.be.equal(defaults.executionFee.add(amountIn)); + + const order = await getCreatedSwapOrder(defaults.user.address); + + validateOrderFields(order, { + account: defaults.user.address, + triggerRatio, + triggerAboveThreshold: false, + path: [dai.address, btc.address], + minOut: props.minOut, + executionFee: defaults.executionFee, + amountIn + }); + }); + + it("createSwapOrder, BNB -> DAI", async () => { + const triggerRatio = getTriggerRatio(toUsd(550), toUsd(1)); + const amountIn = expandDecimals(10, 18); + const value = defaults.executionFee.add(amountIn); + + await expect(defaultCreateSwapOrder({ + path: [bnb.address, dai.address], + triggerRatio, + triggerAboveThreshold: false, + amountIn, + shouldWrap: true, + value: value.sub(1) + })).to.be.revertedWith("OrderBook: incorrect value transferred"); + + await expect(defaultCreateSwapOrder({ + path: [bnb.address, dai.address], + triggerRatio, + triggerAboveThreshold: false, + amountIn, + shouldWrap: true, + value: value.add(1) + })).to.be.revertedWith("OrderBook: incorrect value transferred"); + + let tx, props; + [tx, props] = await defaultCreateSwapOrder({ + path: [bnb.address, dai.address], + triggerRatio, + triggerAboveThreshold: false, + shouldWrap: true, + amountIn, + value + }); + reportGasUsed(provider, tx, "createSwapOrder"); + const bnbBalance = await bnb.balanceOf(orderBook.address); + expect(bnbBalance).to.be.equal(value); + + const order = await getCreatedSwapOrder(defaults.user.address); + + validateOrderFields(order, { + account: defaults.user.address, + triggerRatio, + triggerAboveThreshold: false, + path: [dai.address, btc.address], + minOut: props.minOut, + executionFee: defaults.executionFee, + amountIn + }); + }); + + it("createSwapOrder, DAI -> WBNB, shouldUnwrap = false", async () => { + const triggerRatio = getTriggerRatio(toUsd(1), toUsd(310)); + const amountIn = expandDecimals(100, 18); + + let tx, props; + [tx, props] = await defaultCreateSwapOrder({ + path: [dai.address, bnb.address], + triggerRatio, + triggerAboveThreshold: false, + amountIn, + shouldUnwrap: false, + value: defaults.executionFee + }); + reportGasUsed(provider, tx, "createSwapOrder"); + + const order = await getCreatedSwapOrder(defaults.user.address); + + validateOrderFields(order, { + account: defaults.user.address, + triggerRatio, + triggerAboveThreshold: false, + path: [dai.address, btc.address], + minOut: props.minOut, + executionFee: defaults.executionFee, + shouldUnwrap: false, + amountIn + }); + }); + + it("createSwapOrder, two orders", async () => { + const triggerRatio1 = getTriggerRatio(toUsd(58000), toUsd(1)); + let tx1; + [tx1, ] = await defaultCreateSwapOrder({triggerRatio: triggerRatio1}); + reportGasUsed(provider, tx1, 'createSwapOrder'); + + const triggerRatio2 = getTriggerRatio(toUsd(59000), toUsd(1)); + let tx2; + [tx2, ] = await defaultCreateSwapOrder({triggerRatio: triggerRatio2}); + reportGasUsed(provider, tx2, 'createSwapOrder'); + + const order1 = await getCreatedSwapOrder(defaults.user.address, 0); + const order2 = await getCreatedSwapOrder(defaults.user.address, 1); + + expect(order1.account).to.be.equal(defaults.user.address); + expect(order1.triggerRatio).to.be.equal(triggerRatio1); + + expect(order2.account).to.be.equal(defaults.user.address); + expect(order2.triggerRatio).to.be.equal(triggerRatio2); + }); + + it("cancelSwapOrder, tokenA != BNB", async () => { + const triggerRatio = toUsd(58000).mul(PRICE_PRECISION).div(toUsd(1)); + await defaultCreateSwapOrder({ + triggerRatio, + triggerAboveThreshold: false + }); + + const balanceBefore = await defaults.user.getBalance(); + const daiBalanceBefore = await dai.balanceOf(defaults.user.address) + + const tx = await orderBook.connect(defaults.user).cancelSwapOrder(0); + reportGasUsed(provider, tx, "canceSwapOrder"); + const txFees = await getTxFees(provider, tx); + + const balanceAfter = await user0.getBalance(); + const daiBalanceAfter = await dai.balanceOf(defaults.user.address); + const order = await getCreatedSwapOrder(defaults.user.address); + + expect(balanceAfter, 'balanceAfter').to.be.equal(balanceBefore.add(defaults.executionFee).sub(txFees)); + expect(daiBalanceAfter, 'daiBalanceAfter').to.be.eq(daiBalanceBefore.add(defaults.amountIn)); + + expect(order.account, 'account').to.be.equal(ZERO_ADDRESS); + }); + + it("cancelSwapOrder, tokenA == BNB", async () => { + const triggerRatio = toUsd(1).mul(PRICE_PRECISION).div(toUsd(550)); + const amountIn = expandDecimals(10, 18); + const value = defaults.executionFee.add(amountIn); + await defaultCreateSwapOrder({ + path: [bnb.address, dai.address], + triggerRatio, + triggerAboveThreshold: false, + amountIn, + shouldWrap: true, + value + }); + + const balanceBefore = await defaults.user.getBalance(); + + const tx = await orderBook.connect(defaults.user).cancelSwapOrder(0); + reportGasUsed(provider, tx, "canceSwapOrder"); + const txFees = await getTxFees(provider, tx); + + const balanceAfter = await user0.getBalance(); + const order = await getCreatedSwapOrder(defaults.user.address); + + expect(balanceAfter, 'balanceAfter').to.be.equal( + balanceBefore.add(value).sub(txFees) + ); + + expect(order.account, 'account').to.be.equal(ZERO_ADDRESS); + }); + + it("updateSwapOrder", async () => { + const triggerRatio = toUsd(58000).mul(PRICE_PRECISION).div(toUsd(1)); + await defaultCreateSwapOrder({ + triggerRatio + }); + + const orderBefore = await getCreatedSwapOrder(defaults.user.address); + + validateOrderFields(orderBefore, { + triggerRatio, + triggerAboveThreshold: defaults.triggerAboveThreshold, + minOut: defaults.minOut + }); + + const newTriggerRatio = toUsd(58000).mul(PRICE_PRECISION).div(toUsd(1)); + const newTriggerAboveThreshold = !defaults.triggerAboveThreshold; + const newMinOut = expandDecimals(1, 8).div(1000); + + await expect(orderBook.connect(user1).updateSwapOrder(0, newMinOut, newTriggerRatio, newTriggerAboveThreshold)) + .to.be.revertedWith("OrderBook: non-existent order"); + + await expect(orderBook.connect(defaults.user).updateSwapOrder(1, newMinOut, newTriggerRatio, newTriggerAboveThreshold)) + .to.be.revertedWith("OrderBook: non-existent order"); + + const tx = await orderBook.connect(defaults.user).updateSwapOrder(0, newMinOut, newTriggerRatio, newTriggerAboveThreshold); + reportGasUsed(provider, tx, 'updateSwapOrder'); + + const orderAfter = await getCreatedSwapOrder(defaults.user.address); + validateOrderFields(orderAfter, { + triggerRatio: newTriggerRatio, + triggerAboveThreshold: newTriggerAboveThreshold, + minOut: newMinOut + }); + }); + + it("executeSwapOrder, triggerAboveThreshold == false", async () => { + // in this case contract OrderBook will ignore triggerPrice prop + // and will try to swap using passed minOut + // minOut will ensure swap will occur with suitable price + + const amountIn = expandDecimals(1, 8); + const value = defaults.executionFee; + const path = [btc.address, bnb.address]; + const minOut = await getMinOut( + getTriggerRatio(toUsd(BTC_PRICE), toUsd(BNB_PRICE - 50)), + path, + amountIn + ); + + await defaultCreateSwapOrder({ + path, + triggerAboveThreshold: false, + amountIn, + minOut, + value + }); + + await expect(orderBook.executeSwapOrder(defaults.user.address, 2, user1.address), "non-existent order") + .to.be.revertedWith("OrderBook: non-existent order"); + + bnbPriceFeed.setLatestAnswer(toChainlinkPrice(BNB_PRICE - 30)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, user1.address), "insufficient amountOut") + .to.be.revertedWith("OrderBook: insufficient amountOut"); + + bnbPriceFeed.setLatestAnswer(toChainlinkPrice(BNB_PRICE - 70)); + + const executor = user1; + const executorBalanceBefore = await executor.getBalance(); + const userBalanceBefore = await defaults.user.getBalance(); + + const tx = await orderBook.executeSwapOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeSwapOrder'); + + const executorBalanceAfter = await executor.getBalance(); + expect(executorBalanceAfter, 'executorBalanceAfter').to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const userBalanceAfter = await defaults.user.getBalance(); + expect(userBalanceAfter.gt(userBalanceBefore.add(minOut)), 'userBalanceAfter').to.be.true; + + const order = await getCreatedSwapOrder(defaults.user.address, 0); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeSwapOrder, triggerAboveThreshold == false, DAI -> WBNB, shouldUnwrap = false", async () => { + const amountIn = expandDecimals(100, 18); + const value = defaults.executionFee; + const path = [dai.address, bnb.address]; + const minOut = await getMinOut( + getTriggerRatio(toUsd(1), toUsd(BNB_PRICE + 50)), + path, + amountIn + ); + + await defaultCreateSwapOrder({ + path, + triggerAboveThreshold: false, + amountIn, + minOut, + shouldUnwrap: false, + value + }); + + const executor = user1; + const executorBalanceBefore = await executor.getBalance(); + const userWbnbBalanceBefore = await bnb.balanceOf(defaults.user.address); + + const tx = await orderBook.executeSwapOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeSwapOrder'); + + const executorBalanceAfter = await executor.getBalance(); + expect(executorBalanceAfter, 'executorBalanceAfter').to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const userWbnbBalanceAfter = await bnb.balanceOf(defaults.user.address); + expect(userWbnbBalanceAfter.gt(userWbnbBalanceBefore.add(minOut)), 'userWbnbBalanceAfter').to.be.true; + + const order = await getCreatedSwapOrder(defaults.user.address, 0); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeSwapOrder, triggerAboveThreshold == true", async () => { + const triggerRatio = getTriggerRatio(toUsd(BNB_PRICE), toUsd(62000)); + const amountIn = expandDecimals(10, 18); + const path = [bnb.address, btc.address]; + const value = defaults.executionFee.add(amountIn); + + // minOut is not mandatory for such orders but with minOut it's possible to limit max price + // e.g. user would not be happy if he sets order "buy if BTC > $65000" and order executes with $75000 + const minOut = await getMinOut( + getTriggerRatio(toUsd(BNB_PRICE), toUsd(63000)), + path, + amountIn + ); + + await defaultCreateSwapOrder({ + path: path, + minOut, + triggerRatio, + shouldWrap: true, + triggerAboveThreshold: true, + amountIn, + value + }); + + const executor = user1; + + await expect(orderBook.executeSwapOrder(defaults.user.address, 2, executor.address)) + .to.be.revertedWith("OrderBook: non-existent order"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(60500)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: invalid price for execution"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(62500)); + + const executorBalanceBefore = await executor.getBalance(); + const userBtcBalanceBefore = await btc.balanceOf(defaults.user.address); + + const tx = await orderBook.executeSwapOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeSwapOrder'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter, 'executorBalanceAfter').to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const userBtcBalanceAfter = await btc.balanceOf(defaults.user.address); + expect(userBtcBalanceAfter.gt(userBtcBalanceBefore.add(minOut)), 'userBtcBalanceAfter').to.be.true; + + const order = await getCreatedSwapOrder(defaults.user.address, 0); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeSwapOrder, triggerAboveThreshold == true, BNB -> DAI -> BTC", async () => { + const triggerRatio = getTriggerRatio(toUsd(BNB_PRICE), toUsd(62000)); + const amountIn = expandDecimals(10, 18); + const path = [bnb.address, dai.address, btc.address]; + const value = defaults.executionFee.add(amountIn); + + // minOut is not mandatory for such orders but with minOut it's possible to limit max price + // e.g. user would not be happy if he sets order "buy if BTC > $65000" and order executes with $75000 + const minOut = await getMinOut( + getTriggerRatio(toUsd(BNB_PRICE), toUsd(63000)), + path, + amountIn + ); + + await defaultCreateSwapOrder({ + path: path, + minOut, + triggerRatio, + shouldWrap: true, + triggerAboveThreshold: true, + amountIn, + value + }); + + const executor = user1; + + await expect(orderBook.executeSwapOrder(defaults.user.address, 2, executor.address)) + .to.be.revertedWith("OrderBook: non-existent order"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(60500)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: invalid price for execution"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(62500)); + + const executorBalanceBefore = await executor.getBalance(); + const userBtcBalanceBefore = await btc.balanceOf(defaults.user.address); + + const tx = await orderBook.executeSwapOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeSwapOrder'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter, 'executorBalanceAfter').to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const userBtcBalanceAfter = await btc.balanceOf(defaults.user.address); + expect(userBtcBalanceAfter.gt(userBtcBalanceBefore.add(minOut)), 'userBtcBalanceAfter').to.be.true; + + const order = await getCreatedSwapOrder(defaults.user.address, 0); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeSwapOrder, triggerAboveThreshold == true, USDG -> BTC", async () => { + const triggerRatio = getTriggerRatio(toUsd(1), toUsd(62000)); + const amountIn = expandDecimals(1000, 18); + const path = [usdg.address, btc.address]; + const value = defaults.executionFee; + + // minOut is not mandatory for such orders but with minOut it's possible to limit max price + // e.g. user would not be happy if he sets order "buy if BTC > $65000" and order executes with $75000 + const minOut = await getMinOut( + getTriggerRatio(toUsd(1), toUsd(63000)), + path, + amountIn + ); + + await defaultCreateSwapOrder({ + path, + minOut, + triggerRatio, + triggerAboveThreshold: true, + amountIn, + value + }); + + const executor = user1; + + await expect(orderBook.executeSwapOrder(defaults.user.address, 2, executor.address)) + .to.be.revertedWith("OrderBook: non-existent order"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(60500)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: invalid price for execution"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(70000)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: insufficient amountOut"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(62500)); + + const executorBalanceBefore = await executor.getBalance(); + const userBtcBalanceBefore = await btc.balanceOf(defaults.user.address); + + const tx = await orderBook.executeSwapOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeSwapOrder'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter, 'executorBalanceAfter').to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const userBtcBalanceAfter = await btc.balanceOf(defaults.user.address); + expect(userBtcBalanceAfter.gt(userBtcBalanceBefore.add(minOut)), 'userBtcBalanceAfter').to.be.true; + + const order = await getCreatedSwapOrder(defaults.user.address, 0); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeSwapOrder, triggerAboveThreshold == true, USDG -> DAI -> BTC", async () => { + const triggerRatio = getTriggerRatio(toUsd(1), toUsd(62000)); + const amountIn = expandDecimals(1000, 18); + const path = [usdg.address, dai.address, btc.address]; + const value = defaults.executionFee; + + // minOut is not mandatory for such orders but with minOut it's possible to limit max price + // e.g. user would not be happy if he sets order "buy if BTC > $65000" and order executes with $75000 + const minOut = await getMinOut( + getTriggerRatio(toUsd(1), toUsd(63000)), + path, + amountIn + ); + + await defaultCreateSwapOrder({ + path, + minOut, + triggerRatio, + triggerAboveThreshold: true, + amountIn, + value + }); + + const executor = user1; + + await expect(orderBook.executeSwapOrder(defaults.user.address, 2, executor.address)) + .to.be.revertedWith("OrderBook: non-existent order"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(60500)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: invalid price for execution"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(70000)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: insufficient amountOut"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(62500)); + + const executorBalanceBefore = await executor.getBalance(); + const userBtcBalanceBefore = await btc.balanceOf(defaults.user.address); + + const tx = await orderBook.executeSwapOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeSwapOrder'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter, 'executorBalanceAfter').to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const userBtcBalanceAfter = await btc.balanceOf(defaults.user.address); + expect(userBtcBalanceAfter.gt(userBtcBalanceBefore.add(minOut)), 'userBtcBalanceAfter').to.be.true; + + const order = await getCreatedSwapOrder(defaults.user.address, 0); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeSwapOrder, triggerAboveThreshold == true, USDG -> BNB -> BTC", async () => { + const triggerRatio = getTriggerRatio(toUsd(1), toUsd(62000)); + const amountIn = expandDecimals(1000, 18); + const path = [usdg.address, bnb.address, btc.address]; + const value = defaults.executionFee; + + // minOut is not mandatory for such orders but with minOut it's possible to limit max price + // e.g. user would not be happy if he sets order "buy if BTC > $65000" and order executes with $75000 + const minOut = await getMinOut( + getTriggerRatio(toUsd(1), toUsd(63000)), + path, + amountIn + ); + + await defaultCreateSwapOrder({ + path, + minOut, + triggerRatio, + triggerAboveThreshold: true, + amountIn, + value + }); + + const executor = user1; + + await expect(orderBook.executeSwapOrder(defaults.user.address, 2, executor.address)) + .to.be.revertedWith("OrderBook: non-existent order"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(60500)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: invalid price for execution"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(70000)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: insufficient amountOut"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(62500)); + + const executorBalanceBefore = await executor.getBalance(); + const userBtcBalanceBefore = await btc.balanceOf(defaults.user.address); + + const tx = await orderBook.executeSwapOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeSwapOrder'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter, 'executorBalanceAfter').to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const userBtcBalanceAfter = await btc.balanceOf(defaults.user.address); + expect(userBtcBalanceAfter.gt(userBtcBalanceBefore.add(minOut)), 'userBtcBalanceAfter').to.be.true; + + const order = await getCreatedSwapOrder(defaults.user.address, 0); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("executeSwapOrder, triggerAboveThreshold == true, BTC -> USDG", async () => { + const triggerRatio = getTriggerRatio(toUsd(62000), toUsd(1)); + const amountIn = expandDecimals(1, 6); // 0.01 BTC + const path = [btc.address, usdg.address]; + const value = defaults.executionFee; + + // minOut is not mandatory for such orders but with minOut it's possible to limit max price + // e.g. user would not be happy if he sets order "buy if BTC > $65000" and order executes with $75000 + const minOut = await getMinOut( + getTriggerRatio(toUsd(60000), toUsd(1)), + path, + amountIn + ); + + await defaultCreateSwapOrder({ + path, + minOut, + triggerRatio, + triggerAboveThreshold: true, + amountIn, + value + }); + + const executor = user1; + + await expect(orderBook.executeSwapOrder(defaults.user.address, 2, executor.address)) + .to.be.revertedWith("OrderBook: non-existent order"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(63000)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: invalid price for execution"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)); + await expect(orderBook.executeSwapOrder(defaults.user.address, 0, executor.address)) + .to.be.revertedWith("OrderBook: insufficient amountOut"); + + btcPriceFeed.setLatestAnswer(toChainlinkPrice(61000)); + + const executorBalanceBefore = await executor.getBalance(); + const userUsdgBalanceBefore = await usdg.balanceOf(defaults.user.address); + + const tx = await orderBook.executeSwapOrder(defaults.user.address, 0, executor.address); + reportGasUsed(provider, tx, 'executeSwapOrder'); + + const executorBalanceAfter = await user1.getBalance(); + expect(executorBalanceAfter, 'executorBalanceAfter').to.be.equal(executorBalanceBefore.add(defaults.executionFee)); + + const userUsdgBalanceAfter = await usdg.balanceOf(defaults.user.address); + expect(userUsdgBalanceAfter.gt(userUsdgBalanceBefore.add(minOut)), 'userUsdgBalanceAfter').to.be.true; + + const order = await getCreatedSwapOrder(defaults.user.address, 0); + expect(order.account).to.be.equal(ZERO_ADDRESS); + }); + + it("complex scenario", async () => { + const triggerRatio1 = toUsd(BTC_PRICE + 2000).mul(PRICE_PRECISION).div(toUsd(1)); + const order1Index = 0; + // buy BTC with DAI when BTC price goes up + let props1; + [, props1] = await defaultCreateSwapOrder({ + path: [dai.address, btc.address], + triggerRatio: triggerRatio1, + triggerAboveThreshold: true + }); + + // buy BTC with BNB when BTC price goes up + let triggerRatio2 = toUsd(BTC_PRICE - 5000).mul(PRICE_PRECISION).div(toUsd(BNB_PRICE)); + const order2Index = 1; + const amountIn = expandDecimals(5, 18); + const value = defaults.executionFee.add(amountIn); + await defaultCreateSwapOrder({ + path: [bnb.address, btc.address], + triggerRatio: triggerRatio2, + triggerAboveThreshold: false, + amountIn, + shouldWrap: true, + value + }); + + // buy BTC with BNB when BTC price goes up + let triggerRatio3 = toUsd(BTC_PRICE - 5000).mul(PRICE_PRECISION).div(toUsd(BNB_PRICE)); + const order3Index = 2; + let props3; + [, props3] = await defaultCreateSwapOrder({ + path: [dai.address, btc.address], + triggerRatio: triggerRatio3, + triggerAboveThreshold: false + }); + + // try to execute order 1 + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(BTC_PRICE + 1500)); + await expect(orderBook.executeSwapOrder(defaults.user.address, order1Index, user1.address), 'order1 revert') + .to.be.revertedWith("OrderBook: invalid price for execution"); + + // update order 1 + const newTriggerRatio1 = toUsd(BTC_PRICE + 1000).mul(PRICE_PRECISION).div(toUsd(1)); + await orderBook.connect(defaults.user).updateSwapOrder(order1Index, props1.minOut, newTriggerRatio1, true); + let order1 = await getCreatedSwapOrder(defaults.user.address, order1Index); + expect(order1.triggerRatio, 'order1 triggerRatio').to.be.equal(newTriggerRatio1); + + // execute order 1 + await orderBook.executeSwapOrder(defaults.user.address, order1Index, user1.address); + order1 = await getCreatedSwapOrder(defaults.user.address, order1Index); + expect(order1.account, 'order1 account').to.be.equal(ZERO_ADDRESS); + + // cancel order 3 + let btcBalanceBefore = await btc.balanceOf(defaults.user.address); + await orderBook.connect(defaults.user).cancelSwapOrder(order3Index); + let order3 = await getCreatedSwapOrder(defaults.user.address, order3Index); + expect(order3.account, 'order3 account').to.be.equal(ZERO_ADDRESS); + + let btcBalanceAfter = await btc.balanceOf(defaults.user.address); + expect(btcBalanceAfter.gt(btcBalanceBefore.add(props3.minOut)), 'btcBalanceBefore'); + + // try to execute order 2 + await expect(orderBook.executeSwapOrder(defaults.user.address, order2Index, user1.address), 'order2 revert') + .to.be.revertedWith("OrderBook: insufficient amountOut"); + + // execute order 2 + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(BNB_PRICE + 100)); // BTC price decreased relative to BNB + await orderBook.executeSwapOrder(defaults.user.address, order2Index, user1.address); + let order2 = await getCreatedSwapOrder(defaults.user.address, order2Index); + expect(order2.account, 'order2 account').to.be.equal(ZERO_ADDRESS); + }); +}); diff --git a/test/core/Router.js b/test/core/Router.js new file mode 100644 index 00000000..548ea725 --- /dev/null +++ b/test/core/Router.js @@ -0,0 +1,369 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed, newWallet } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("./Vault/helpers") + +use(solidity) + +describe("Router", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let usdg + let router + let vaultPriceFeed + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let eth + let ethPriceFeed + let dai + let daiPriceFeed + let busd + let busdPriceFeed + let distributor0 + let yieldTracker0 + let reader + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + busd = await deployContract("Token", []) + busdPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + reader = await deployContract("Reader", []) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(eth.address, ethPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await bnb.connect(user3).deposit({ value: expandDecimals(100, 18) }) + }) + + it("setGov", async () => { + await expect(router.connect(user0).setGov(user1.address)) + .to.be.revertedWith("Router: forbidden") + + expect(await router.gov()).eq(wallet.address) + + await router.setGov(user0.address) + expect(await router.gov()).eq(user0.address) + + await router.connect(user0).setGov(user1.address) + expect(await router.gov()).eq(user1.address) + }) + + it("addPlugin", async () => { + await expect(router.connect(user0).addPlugin(user1.address)) + .to.be.revertedWith("Router: forbidden") + + await router.setGov(user0.address) + + expect(await router.plugins(user1.address)).eq(false) + await router.connect(user0).addPlugin(user1.address) + expect(await router.plugins(user1.address)).eq(true) + }) + + it("removePlugin", async () => { + await expect(router.connect(user0).removePlugin(user1.address)) + .to.be.revertedWith("Router: forbidden") + + await router.setGov(user0.address) + + expect(await router.plugins(user1.address)).eq(false) + await router.connect(user0).addPlugin(user1.address) + expect(await router.plugins(user1.address)).eq(true) + await router.connect(user0).removePlugin(user1.address) + expect(await router.plugins(user1.address)).eq(false) + }) + + it("approvePlugin", async () => { + expect(await router.approvedPlugins(user0.address, user1.address)).eq(false) + await router.connect(user0).approvePlugin(user1.address) + expect(await router.approvedPlugins(user0.address, user1.address)).eq(true) + }) + + it("denyPlugin", async () => { + expect(await router.approvedPlugins(user0.address, user1.address)).eq(false) + await router.connect(user0).approvePlugin(user1.address) + expect(await router.approvedPlugins(user0.address, user1.address)).eq(true) + await router.connect(user0).denyPlugin(user1.address) + expect(await router.approvedPlugins(user0.address, user1.address)).eq(false) + }) + + it("pluginTransfer", async () => { + await router.addPlugin(user1.address) + await router.connect(user0).approvePlugin(user1.address) + + await dai.mint(user0.address, 2000) + await dai.connect(user0).approve(router.address, 1000) + expect(await dai.allowance(user0.address, router.address)).eq(1000) + expect(await dai.balanceOf(user2.address)).eq(0) + await router.connect(user1).pluginTransfer(dai.address, user0.address, user2.address, 800) + expect(await dai.allowance(user0.address, router.address)).eq(200) + expect(await dai.balanceOf(user2.address)).eq(800) + + await expect(router.connect(user2).pluginTransfer(dai.address, user0.address, user2.address, 1)) + .to.be.revertedWith("Router: invalid plugin") + await router.addPlugin(user2.address) + await expect(router.connect(user2).pluginTransfer(dai.address, user0.address, user2.address, 1)) + .to.be.revertedWith("Router: plugin not approved") + }) + + it("pluginIncreasePosition", async () => { + await router.addPlugin(user1.address) + await router.connect(user0).approvePlugin(user1.address) + + await expect(router.connect(user1).pluginIncreasePosition(user0.address, bnb.address, bnb.address, 1000, true)) + .to.be.revertedWith("Vault: insufficient collateral for fees") + + await expect(router.connect(user2).pluginIncreasePosition(user0.address, bnb.address, bnb.address, 1000, true)) + .to.be.revertedWith("Router: invalid plugin") + await router.addPlugin(user2.address) + await expect(router.connect(user2).pluginIncreasePosition(user0.address, bnb.address, bnb.address, 1000, true)) + .to.be.revertedWith("Router: plugin not approved") + }) + + it("pluginDecreasePosition", async () => { + await router.addPlugin(user1.address) + await router.connect(user0).approvePlugin(user1.address) + + await expect(router.connect(user1).pluginDecreasePosition(user0.address, bnb.address, bnb.address, 100, 1000, true, user0.address)) + .to.be.revertedWith("Vault: empty position") + + await expect(router.connect(user2).pluginDecreasePosition(user0.address, bnb.address, bnb.address, 100, 1000, true, user0.address)) + .to.be.revertedWith("Router: invalid plugin") + await router.addPlugin(user2.address) + await expect(router.connect(user2).pluginDecreasePosition(user0.address, bnb.address, bnb.address, 100, 1000, true, user0.address)) + .to.be.revertedWith("Router: plugin not approved") + }) + + it("swap, buy USDG", async () => { + await vaultPriceFeed.getPrice(dai.address, true, true, true) + await dai.mint(user0.address, expandDecimals(200, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(200, 18)) + + await expect(router.connect(user0).swap([dai.address, usdg.address], expandDecimals(200, 18), expandDecimals(201, 18), user0.address)) + .to.be.revertedWith("Router: insufficient amountOut") + + expect(await dai.balanceOf(user0.address)).eq(expandDecimals(200, 18)) + expect(await usdg.balanceOf(user0.address)).eq(0) + const tx = await router.connect(user0).swap([dai.address, usdg.address], expandDecimals(200, 18), expandDecimals(199, 18), user0.address) + await reportGasUsed(provider, tx, "buyUSDG gas used") + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user0.address)).eq("199400000000000000000") // 199.4 + }) + + it("swap, sell USDG", async () => { + await dai.mint(user0.address, expandDecimals(200, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(200, 18)) + + await expect(router.connect(user0).swap([dai.address, usdg.address], expandDecimals(200, 18), expandDecimals(201, 18), user0.address)) + .to.be.revertedWith("Router: insufficient amountOut") + + expect(await dai.balanceOf(user0.address)).eq(expandDecimals(200, 18)) + expect(await usdg.balanceOf(user0.address)).eq(0) + const tx = await router.connect(user0).swap([dai.address, usdg.address], expandDecimals(200, 18), expandDecimals(199, 18), user0.address) + await reportGasUsed(provider, tx, "sellUSDG gas used") + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user0.address)).eq("199400000000000000000") // 199.4 + + await usdg.connect(user0).approve(router.address, expandDecimals(100, 18)) + await expect(router.connect(user0).swap([usdg.address, dai.address], expandDecimals(100, 18), expandDecimals(100, 18), user0.address)) + .to.be.revertedWith("Router: insufficient amountOut") + + await router.connect(user0).swap([usdg.address, dai.address], expandDecimals(100, 18), expandDecimals(99, 18), user0.address) + expect(await dai.balanceOf(user0.address)).eq("99700000000000000000") // 99.7 + expect(await usdg.balanceOf(user0.address)).eq("99400000000000000000") // 99.4 + }) + + it("swap, path.length == 2", async () => { + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(1, 8)) + await expect(router.connect(user0).swap([btc.address, usdg.address], expandDecimals(1, 8), expandDecimals(60000, 18), user0.address)) + .to.be.revertedWith("Router: insufficient amountOut") + await router.connect(user0).swap([btc.address, usdg.address], expandDecimals(1, 8), expandDecimals(59000, 18), user0.address) + + await dai.mint(user0.address, expandDecimals(30000, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(30000, 18)) + await expect(router.connect(user0).swap([dai.address, btc.address], expandDecimals(30000, 18), "50000000", user0.address)) // 0.5 BTC + .to.be.revertedWith("Router: insufficient amountOut") + + expect(await dai.balanceOf(user0.address)).eq(expandDecimals(30000, 18)) + expect(await btc.balanceOf(user0.address)).eq(0) + const tx = await router.connect(user0).swap([dai.address, btc.address], expandDecimals(30000, 18), "49000000", user0.address) + await reportGasUsed(provider, tx, "swap gas used") + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await btc.balanceOf(user0.address)).eq("49850000") // 0.4985 + }) + + it("swap, path.length == 3", async () => { + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(1, 8)) + await router.connect(user0).swap([btc.address, usdg.address], expandDecimals(1, 8), expandDecimals(59000, 18), user0.address) + + await dai.mint(user0.address, expandDecimals(30000, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(30000, 18)) + await router.connect(user0).swap([dai.address, usdg.address], expandDecimals(30000, 18), expandDecimals(29000, 18), user0.address) + + await usdg.connect(user0).approve(router.address, expandDecimals(20000, 18)) + + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user0.address)).eq(expandDecimals(89730, 18)) + await expect(router.connect(user0).swap([usdg.address, dai.address, usdg.address], expandDecimals(20000, 18), expandDecimals(20000, 18), user0.address)) + .to.be.revertedWith("Router: insufficient amountOut") + + await router.connect(user0).swap([usdg.address, dai.address, usdg.address], expandDecimals(20000, 18), expandDecimals(19000, 18), user0.address) + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user0.address)).eq("89610180000000000000000") // 89610.18 + + await usdg.connect(user0).approve(router.address, expandDecimals(40000, 18)) + await expect(router.connect(user0).swap([usdg.address, dai.address, btc.address], expandDecimals(30000, 18), expandDecimals(39000, 18), user0.address)) + .to.be.revertedWith("Vault: poolAmount exceeded") // this reverts as some DAI has been transferred from the pool to the fee reserve + + expect(await vault.poolAmounts(dai.address)).eq("29790180000000000000000") // 29790.18 + expect(await vault.feeReserves(dai.address)).eq("209820000000000000000") // 209.82 + + await expect(router.connect(user0).swap([usdg.address, dai.address, btc.address], expandDecimals(20000, 18), "34000000", user0.address)) + .to.be.revertedWith("Router: insufficient amountOut") + + const tx = await router.connect(user0).swap([usdg.address, dai.address, btc.address], expandDecimals(20000, 18), "33000000", user0.address) + await reportGasUsed(provider, tx, "swap gas used") + expect(await usdg.balanceOf(user0.address)).eq("69610180000000000000000") // 69610.18 + expect(await btc.balanceOf(user0.address)).eq("33133633") // 0.33133633 BTC + }) + + it("swap, increasePosition", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(600)) + await busdPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + const bnbBusd = await deployContract("PancakePair", []) + await bnbBusd.setReserves(expandDecimals(1000, 18), expandDecimals(300 * 1000, 18)) + + const ethBnb = await deployContract("PancakePair", []) + await ethBnb.setReserves(expandDecimals(800, 18), expandDecimals(100, 18)) + + const btcBnb = await deployContract("PancakePair", []) + await btcBnb.setReserves(expandDecimals(10, 18), expandDecimals(2000, 18)) + + await vaultPriceFeed.setTokens(btc.address, eth.address, bnb.address) + await vaultPriceFeed.setPairs(bnbBusd.address, ethBnb.address, btcBnb.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(1, 8)) + await router.connect(user0).swap([btc.address, usdg.address], expandDecimals(1, 8), expandDecimals(59000, 18), user0.address) + + await dai.mint(user0.address, expandDecimals(200, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(200, 18)) + + await expect(router.connect(user0).increasePosition([dai.address, btc.address], btc.address, expandDecimals(200, 18), "333333", toUsd(1200), true, toNormalizedPrice(60000))) + .to.be.revertedWith("Router: insufficient amountOut") + + await expect(router.connect(user0).increasePosition([dai.address, btc.address], btc.address, expandDecimals(200, 18), "332333", toUsd(1200), true, toNormalizedPrice(60000 - 1))) + .to.be.revertedWith("Router: mark price higher than limit") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + + await vaultPriceFeed.setPriceSampleSpace(2) + + const tx = await router.connect(user0).increasePosition([dai.address, btc.address], btc.address, expandDecimals(200, 18), "332333", toUsd(1200), true, toNormalizedPrice(60000)) + await reportGasUsed(provider, tx, "increasePosition gas used") + }) + + it("decreasePositionAndSwap", async () => { + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(1, 8)) + await router.connect(user0).swap([btc.address, usdg.address], expandDecimals(1, 8), expandDecimals(59000, 18), user0.address) + + await dai.mint(user0.address, expandDecimals(30000, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(30000, 18)) + await router.connect(user0).swap([dai.address, usdg.address], expandDecimals(30000, 18), expandDecimals(29000, 18), user0.address) + + await dai.mint(user0.address, expandDecimals(200, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(200, 18)) + await router.connect(user0).increasePosition([dai.address, btc.address], btc.address, expandDecimals(200, 18), "332333", toUsd(1200), true, toNormalizedPrice(60000)) + + await expect(router.connect(user0).decreasePositionAndSwap([btc.address, dai.address], btc.address, 0, toUsd(1200), true, user1.address, toNormalizedPrice(60000), expandDecimals(197, 18))) + .to.be.revertedWith("Router: insufficient amountOut") + + expect(await dai.balanceOf(user1.address)).eq(0) + expect(await dai.balanceOf(router.address)).eq(0) + + await router.connect(user0).decreasePositionAndSwap([btc.address, dai.address], btc.address, 0, toUsd(1200), true, user1.address, toNormalizedPrice(60000), expandDecimals(196, 18)) + + expect(await dai.balanceOf(user1.address)).eq("196408800600000000000") // 196.4088006 + expect(await dai.balanceOf(router.address)).eq(0) + }) + + it("decreasePositionAndSwapETH", async () => { + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(1, 8)) + await router.connect(user0).swap([btc.address, usdg.address], expandDecimals(1, 8), expandDecimals(59000, 18), user0.address) + + await dai.mint(user0.address, expandDecimals(30000, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(30000, 18)) + await router.connect(user0).swap([dai.address, usdg.address], expandDecimals(30000, 18), expandDecimals(29000, 18), user0.address) + + await bnb.mint(user0.address, expandDecimals(10, 18)) + await bnb.connect(user0).approve(router.address, expandDecimals(10, 18)) + await router.connect(user0).swap([bnb.address, usdg.address], expandDecimals(10, 18), expandDecimals(2900, 18), user0.address) + + await dai.mint(user0.address, expandDecimals(200, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(200, 18)) + await router.connect(user0).increasePosition([dai.address, btc.address], btc.address, expandDecimals(200, 18), "332333", toUsd(1200), true, toNormalizedPrice(60000)) + + const wallet0 = newWallet() + + expect(await provider.getBalance(wallet0.address)).eq(0) + expect(await provider.getBalance(router.address)).eq(0) + + await router.connect(user0).decreasePositionAndSwapETH([btc.address, bnb.address], btc.address, 0, toUsd(1200), true, wallet0.address, toNormalizedPrice(60000), "0") + + expect(await provider.getBalance(wallet0.address)).eq("654696002000000000") // 0.654696002 + expect(await provider.getBalance(router.address)).eq(0) + }) +}) diff --git a/test/core/Vault/averagePrice.js b/test/core/Vault/averagePrice.js new file mode 100644 index 00000000..0ef70267 --- /dev/null +++ b/test/core/Vault/averagePrice.js @@ -0,0 +1,760 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getEthConfig, getBtcConfig, getDaiConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.averagePrice", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(eth.address, ethPriceFeed.address, 8, false) + + await vault.setFees( + 50, // _taxBasisPoints + 20, // _stableTaxBasisPoints + 30, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 60 * 60, // _minProfitTime + false // _hasDynamicFees + ) + }) + + it("position.averagePrice, buyPrice != markPrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + let blockTime = await getBlockTime(provider) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + expect(position[7]).eq(blockTime) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(46100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(47100)) + + let leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(90817) // ~9X leverage + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(9)) + + await increaseTime(provider, 10 * 60) + await mineBlock(provider) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(10), true) + blockTime = await getBlockTime(provider) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(100)) // size + expect(position[1]).eq(toUsd(9.90)) // collateral, 10 - 90 * 0.1% - 10 * 0.1% + expect(position[2]).eq("43211009174311926605504587155963302") // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000 + 22172) // reserveAmount, 0.00225 * 40,000 => 90, 0.00022172 * 45100 => ~10 + expect(position[7]).eq(blockTime) + + leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(101010) // ~10X leverage + + expect(await vault.feeReserves(btc.address)).eq(969 + 21) // 0.00000021 * 45100 => 0.01 USD + expect(await vault.reservedAmounts(btc.address)).eq(225000 + 22172) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(90.1)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219 - 21) + expect(await btc.balanceOf(user2.address)).eq(0) + + // profits will decrease slightly as there is a difference between the buy price and the mark price + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("4371549893842887473460721868365") // ~4.37 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(47100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(47100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(47100)) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(9)) + + await validateVaultBalance(expect, vault, btc) + }) + + it("position.averagePrice, buyPrice == markPrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45100)) + + let leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(90817) // ~9X leverage + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(9)) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(10), true) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(100)) // size + expect(position[1]).eq(toUsd(9.90)) // collateral, 10 - 90 * 0.1% - 10 * 0.1% + expect(position[2]).eq("41376146788990825688073394495412844") // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000 + 22172) // reserveAmount, 0.00225 * 40,000 => 90, 0.00022172 * 45100 => ~10 + + leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(101010) // ~10X leverage + + expect(await vault.feeReserves(btc.address)).eq(969 + 22) // 0.00000021 * 45100 => 0.01 USD + expect(await vault.reservedAmounts(btc.address)).eq(225000 + 22172) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(90.1)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219 - 22) + expect(await btc.balanceOf(user2.address)).eq(0) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(9)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("909090909090909090909090909090") // ~0.909 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("20842572062084257206208425720620") // ~20.84 + + await validateVaultBalance(expect, vault, btc) + }) + + it("position.averagePrice, buyPrice < averagePrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(36900)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(36900)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(36900)) + + let leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(90817) // ~9X leverage + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(9)) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true)) + .to.be.revertedWith("Vault: liquidation fees exceed collateral") + + await btc.connect(user1).transfer(vault.address, 25000) + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(10), true) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(100)) // size + expect(position[1]).eq(toUsd(9.91 + 9.215)) // collateral, 0.00025 * 36900 => 9.225, 0.01 fees + expect(position[2]).eq("40549450549450549450549450549450549") // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000 + 27100) // reserveAmount, 0.000271 * 36900 => ~10 + + leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(52287) // ~5.2X leverage + + expect(await vault.feeReserves(btc.address)).eq(969 + 27) // 0.00000027 * 36900 => 0.01 USD + expect(await vault.reservedAmounts(btc.address)).eq(225000 + 27100) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.875)) + expect(await vault.poolAmounts(btc.address)).eq(274250 + 25000 - 219 - 27) + expect(await btc.balanceOf(user2.address)).eq(0) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("8999999999999999999999999999999") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("1111111111111111111111111111111") // ~1.111 + + await validateVaultBalance(expect, vault, btc) + }) + + it("long position.averagePrice, buyPrice == averagePrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq(0) + + await btc.connect(user1).transfer(vault.address, 25000) + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(10), true) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(100)) // size + expect(position[1]).eq(toUsd(9.91 + 9.99)) // collateral + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000 + 25000) // reserveAmount + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq(0) + + await validateVaultBalance(expect, vault, btc) + }) + + it("long position.averagePrice, buyPrice > averagePrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(22.5)) + + await btc.connect(user1).transfer(vault.address, 25000) + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(10), true) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(100)) // size + expect(position[2]).eq("40816326530612244897959183673469387") // averagePrice + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(22.5)) + + await validateVaultBalance(expect, vault, btc) + }) + + it("long position.averagePrice, buyPrice < averagePrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 125000) // 0.000125 BTC => 50 USD + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq("49910000000000000000000000000000") // collateral, 50 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(30000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(30000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(30000)) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(22.5)) + + await btc.connect(user1).transfer(vault.address, 25000) + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(10), true) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(100)) // size + expect(position[2]).eq("38709677419354838709677419354838709") // averagePrice + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("22499999999999999999999999999999") + }) + + it("long position.averagePrice, buyPrice < averagePrice + minProfitBasisPoints", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 125000) // 0.000125 BTC => 50 USD + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq("49910000000000000000000000000000") // collateral, 50 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40300)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40300)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40300)) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("0") + + await btc.connect(user1).transfer(vault.address, 25000) + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(10), true) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(100)) // size + expect(position[2]).eq(toUsd(40300)) // averagePrice + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("0") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("1736972704714640198511166253101") // (700 / 40300) * 100 => 1.73697 + }) + + it("short position.averagePrice, buyPrice == averagePrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.mint(user1.address, expandDecimals(101, 18)) + await dai.connect(user1).transfer(vault.address, expandDecimals(101, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await dai.mint(user0.address, expandDecimals(50, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(50, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq("49910000000000000000000000000000") // collateral, 50 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(0) + + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(10), false) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(100)) // size + expect(position[1]).eq("49900000000000000000000000000000") // collateral + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(100, 18)) // reserveAmount + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(0) + }) + + it("short position.averagePrice, buyPrice > averagePrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.mint(user1.address, expandDecimals(101, 18)) + await dai.connect(user1).transfer(vault.address, expandDecimals(101, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await dai.mint(user0.address, expandDecimals(50, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(50, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq("49910000000000000000000000000000") // collateral, 50 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq("22500000000000000000000000000000") // 22.5 + + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(10), false) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(100)) // size + expect(position[1]).eq("49900000000000000000000000000000") // collateral + expect(position[2]).eq("40816326530612244897959183673469387") // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(100, 18)) // reserveAmount + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq("22500000000000000000000000000000") // 22.5 + }) + + it("short position.averagePrice, buyPrice < averagePrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.mint(user1.address, expandDecimals(101, 18)) + await dai.connect(user1).transfer(vault.address, expandDecimals(101, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await dai.mint(user0.address, expandDecimals(50, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(50, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq("49910000000000000000000000000000") // collateral, 50 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(30000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(30000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(30000)) + + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq("22500000000000000000000000000000") // 22.5 + + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(10), false) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(100)) // size + expect(position[1]).eq("49900000000000000000000000000000") // collateral + expect(position[2]).eq("38709677419354838709677419354838709") // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(100, 18)) // reserveAmount + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq("22499999999999999999999999999999") // ~22.5 + }) + + it("short position.averagePrice, buyPrice < averagePrice - minProfitBasisPoints", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.mint(user1.address, expandDecimals(101, 18)) + await dai.connect(user1).transfer(vault.address, expandDecimals(101, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await dai.mint(user0.address, expandDecimals(50, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(50, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq("49910000000000000000000000000000") // collateral, 50 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39700)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39700)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39700)) + + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq("0") // 22.5 + + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(10), false) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(100)) // size + expect(position[1]).eq("49900000000000000000000000000000") // collateral + expect(position[2]).eq(toUsd(39700)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(100, 18)) // reserveAmount + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq("0") // ~22.5 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq("1763224181360201511335012594458") // (39700 - 39000) / 39700 * 100 => 1.7632 + }) + + it("long position.averagePrice, buyPrice < averagePrice", async () => { + await ethPriceFeed.setLatestAnswer("251382560787") + await vault.setTokenConfig(...getEthConfig(eth, ethPriceFeed)) + + await ethPriceFeed.setLatestAnswer("252145037536") + await ethPriceFeed.setLatestAnswer("252145037536") + + await eth.mint(user1.address, expandDecimals(10, 18)) + await eth.connect(user1).transfer(vault.address, expandDecimals(10, 18)) + await vault.buyUSDG(eth.address, user1.address) + + await eth.mint(user0.address, expandDecimals(1, 18)) + await eth.connect(user0).transfer(vault.address, expandDecimals(1, 18)) + await vault.connect(user0).increasePosition(user0.address, eth.address, eth.address, "5050322181222357947081599665915068", true) + + let position = await vault.getPosition(user0.address, eth.address, eth.address, true) + expect(position[0]).eq("5050322181222357947081599665915068") // size + expect(position[1]).eq("2508775285688777642052918400334084") // averagePrice + expect(position[2]).eq("2521450375360000000000000000000000") // averagePrice + expect(position[3]).eq(0) // entryFundingRate + + await ethPriceFeed.setLatestAnswer("237323502539") + await ethPriceFeed.setLatestAnswer("237323502539") + await ethPriceFeed.setLatestAnswer("237323502539") + + let delta = await vault.getPositionDelta(user0.address, eth.address, eth.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("296866944860754376482796517102673") + + await eth.mint(user0.address, expandDecimals(1, 18)) + await eth.connect(user0).transfer(vault.address, expandDecimals(1, 18)) + await vault.connect(user0).increasePosition(user0.address, eth.address, eth.address, "4746470050780000000000000000000000", true) + + position = await vault.getPosition(user0.address, eth.address, eth.address, true) + expect(position[0]).eq("9796792232002357947081599665915068") // size + expect(position[2]).eq("2447397190894361457116367555285124") // averagePrice + }) +}) diff --git a/test/core/Vault/buyUSDG.js b/test/core/Vault/buyUSDG.js new file mode 100644 index 00000000..eb186450 --- /dev/null +++ b/test/core/Vault/buyUSDG.js @@ -0,0 +1,234 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.buyUSDG", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + let glpManager + let glp + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + glp = await deployContract("GLP", []) + glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 24 * 60 * 60]) + }) + + it("buyUSDG", async () => { + await expect(vault.buyUSDG(bnb.address, wallet.address)) + .to.be.revertedWith("Vault: _token not whitelisted") + + await expect(vault.connect(user0).buyUSDG(bnb.address, user1.address)) + .to.be.revertedWith("Vault: _token not whitelisted") + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await expect(vault.connect(user0).buyUSDG(bnb.address, user1.address)) + .to.be.revertedWith("Vault: invalid tokenAmount") + + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + + await bnb.mint(user0.address, 100) + await bnb.connect(user0).transfer(vault.address, 100) + const tx = await vault.connect(user0).buyUSDG(bnb.address, user1.address, { gasPrice: "10000000000" }) + await reportGasUsed(provider, tx, "buyUSDG gas used") + + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(29700) + expect(await vault.feeReserves(bnb.address)).eq(1) + expect(await vault.usdgAmounts(bnb.address)).eq(29700) + expect(await vault.poolAmounts(bnb.address)).eq(100 - 1) + + await validateVaultBalance(expect, vault, bnb) + + expect(await glpManager.getAumInUsdg(true)).eq(29700) + }) + + it("buyUSDG allows gov to mint", async () => { + await vault.setInManagerMode(true) + await expect(vault.buyUSDG(bnb.address, wallet.address)) + .to.be.revertedWith("Vault: forbidden") + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await bnb.mint(wallet.address, 100) + await bnb.transfer(vault.address, 100) + + expect(await usdg.balanceOf(wallet.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + + await expect(vault.connect(user0).buyUSDG(bnb.address, wallet.address)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setManager(user0.address, true) + await vault.connect(user0).buyUSDG(bnb.address, wallet.address) + + expect(await usdg.balanceOf(wallet.address)).eq(29700) + expect(await vault.feeReserves(bnb.address)).eq(1) + expect(await vault.usdgAmounts(bnb.address)).eq(29700) + expect(await vault.poolAmounts(bnb.address)).eq(100 - 1) + + await validateVaultBalance(expect, vault, bnb) + }) + + it("buyUSDG uses min price", async () => { + await expect(vault.connect(user0).buyUSDG(bnb.address, user1.address)) + .to.be.revertedWith("Vault: _token not whitelisted") + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(200)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(250)) + + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + await bnb.mint(user0.address, 100) + await bnb.connect(user0).transfer(vault.address, 100) + await vault.connect(user0).buyUSDG(bnb.address, user1.address) + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(19800) + expect(await vault.feeReserves(bnb.address)).eq(1) + expect(await vault.usdgAmounts(bnb.address)).eq(19800) + expect(await vault.poolAmounts(bnb.address)).eq(100 - 1) + + await validateVaultBalance(expect, vault, bnb) + }) + + it("buyUSDG updates fees", async () => { + await expect(vault.connect(user0).buyUSDG(bnb.address, user1.address)) + .to.be.revertedWith("Vault: _token not whitelisted") + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + await bnb.mint(user0.address, 10000) + await bnb.connect(user0).transfer(vault.address, 10000) + await vault.connect(user0).buyUSDG(bnb.address, user1.address) + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(9970 * 300) + expect(await vault.feeReserves(bnb.address)).eq(30) + expect(await vault.usdgAmounts(bnb.address)).eq(9970 * 300) + expect(await vault.poolAmounts(bnb.address)).eq(10000 - 30) + + await validateVaultBalance(expect, vault, bnb) + }) + + it("buyUSDG uses mintBurnFeeBasisPoints", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + await dai.mint(user0.address, expandDecimals(10000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(10000, 18)) + await vault.connect(user0).buyUSDG(dai.address, user1.address) + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(expandDecimals(10000 - 4, 18)) + expect(await vault.feeReserves(dai.address)).eq(expandDecimals(4, 18)) + expect(await vault.usdgAmounts(dai.address)).eq(expandDecimals(10000 - 4, 18)) + expect(await vault.poolAmounts(dai.address)).eq(expandDecimals(10000 - 4, 18)) + }) + + it("buyUSDG adjusts for decimals", async () => { + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await expect(vault.connect(user0).buyUSDG(btc.address, user1.address)) + .to.be.revertedWith("Vault: invalid tokenAmount") + + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(btc.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(1, 8)) + await vault.connect(user0).buyUSDG(btc.address, user1.address) + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await vault.feeReserves(btc.address)).eq(300000) + expect(await usdg.balanceOf(user1.address)).eq(expandDecimals(60000, 18).sub(expandDecimals(180, 18))) // 0.3% of 60,000 => 180 + expect(await vault.usdgAmounts(btc.address)).eq(expandDecimals(60000, 18).sub(expandDecimals(180, 18))) + expect(await vault.poolAmounts(btc.address)).eq(expandDecimals(1, 8).sub(300000)) + + await validateVaultBalance(expect, vault, btc) + }) +}) diff --git a/test/core/Vault/closeLongPosition.js b/test/core/Vault/closeLongPosition.js new file mode 100644 index 00000000..b01a41bb --- /dev/null +++ b/test/core/Vault/closeLongPosition.js @@ -0,0 +1,183 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.closeLongPosition", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("close long position", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(46100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(47100)) + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(9)) + + const tx = await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(4), toUsd(90), true, user2.address) + await reportGasUsed(provider, tx, "decreasePosition gas used") + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount, 0.00225 * 40,000 => 90 + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) + + expect(await vault.feeReserves(btc.address)).eq(969 + 191) // 0.00000191 * 47100 => ~0.09 USD + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219 - 39957 - 191) // 0.00040148 * 47100 => ~18.9 USD + expect(await btc.balanceOf(user2.address)).eq(39957) // 0.00039957 * 47100 => 18.82 USD + + await validateVaultBalance(expect, vault, btc) + }) + + it("close long position with loss", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("4390243902439024390243902439024") // 4.39 + + const tx = await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(4), toUsd(90), true, user2.address) + await reportGasUsed(provider, tx, "decreasePosition gas used") + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount, 0.00225 * 40,000 => 90 + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) + + expect(await vault.feeReserves(btc.address)).eq(969 + 230) // 0.00000230 * 39000 => ~0.09 USD + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219 - 13923 - 230) // 0.00013923 * 39000 => ~5.42 USD + expect(await btc.balanceOf(user2.address)).eq(13922) // 0.00013922 * 39000 => 5.42958 USD + + await validateVaultBalance(expect, vault, btc, 1) + }) +}) diff --git a/test/core/Vault/closeShortPosition.js b/test/core/Vault/closeShortPosition.js new file mode 100644 index 00000000..927487dc --- /dev/null +++ b/test/core/Vault/closeShortPosition.js @@ -0,0 +1,211 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("./helpers") + +use(solidity) + +describe("Vault.closeShortPosition", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("close short position", async () => { + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await dai.mint(user0.address, expandDecimals(1000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.buyUSDG(dai.address, user1.address) + expect(await vault.feeReserves(dai.address)).eq("40000000000000000") // 0.04 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.connect(user0).transfer(vault.address, expandDecimals(10, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) // reserveAmount + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) // hasRealisedProfit + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(36000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(36000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(36000)) + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(9)) + + let leverage = await vault.getPositionLeverage(user0.address, dai.address, btc.address, false) + expect(leverage).eq(90817) // ~9X leverage + + expect(await vault.feeReserves(dai.address)).eq("130000000000000000") // 0.13, 0.04 + 0.09 + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(90, 18)) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("99960000000000000000") // 99.96 + expect(await dai.balanceOf(user2.address)).eq(0) + + const tx = await vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, toUsd(3), toUsd(90), false, user2.address) + await reportGasUsed(provider, tx, "decreasePosition gas used") + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) // hasRealisedProfit + + expect(await vault.feeReserves(dai.address)).eq("220000000000000000") // 0.22, 0.04 + 0.09 + 0.09 + expect(await vault.reservedAmounts(dai.address)).eq(0) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("90960000000000000000") // 90.96 + expect(await dai.balanceOf(user2.address)).eq("18820000000000000000") // 18.82 + }) + + it("close short position with loss", async () => { + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await dai.mint(user0.address, expandDecimals(1000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.buyUSDG(dai.address, user1.address) + expect(await vault.feeReserves(dai.address)).eq("40000000000000000") // 0.04 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.connect(user0).transfer(vault.address, expandDecimals(10, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) // reserveAmount + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) // hasRealisedProfit + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq("2250000000000000000000000000000") // 2.25 + + let leverage = await vault.getPositionLeverage(user0.address, dai.address, btc.address, false) + expect(leverage).eq(90817) // ~9X leverage + + expect(await vault.feeReserves(dai.address)).eq("130000000000000000") // 0.13, 0.04 + 0.09 + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(90, 18)) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("99960000000000000000") // 99.96 + expect(await dai.balanceOf(user2.address)).eq(0) + + const tx = await vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, toUsd(3), toUsd(90), false, user2.address) + await reportGasUsed(provider, tx, "decreasePosition gas used") + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) // hasRealisedProfit + + expect(await vault.feeReserves(dai.address)).eq("220000000000000000") // 0.22, 0.04 + 0.09 + 0.09 + expect(await vault.reservedAmounts(dai.address)).eq(0) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("102210000000000000000") // 102.21 + expect(await dai.balanceOf(user2.address)).eq("7570000000000000000") // 7.57 + }) +}) diff --git a/test/core/Vault/decreaseLongPosition.js b/test/core/Vault/decreaseLongPosition.js new file mode 100644 index 00000000..48c2050f --- /dev/null +++ b/test/core/Vault/decreaseLongPosition.js @@ -0,0 +1,332 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.decreaseLongPosition", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + await vault.setFees( + 50, // _taxBasisPoints + 20, // _stableTaxBasisPoints + 30, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 60 * 60, // _minProfitTime + false // _hasDynamicFees + ) + }) + + it("decreasePosition long", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await expect(vault.connect(user1).decreasePosition(user0.address, btc.address, btc.address, 0, 0, true, user2.address)) + .to.be.revertedWith("Vault: invalid msg.sender") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, 0, toUsd(1000), true, user2.address)) + .to.be.revertedWith("Vault: empty position") + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + // test that minProfitBasisPoints works as expected + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 - 1)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 - 1)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 - 1)) + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("2195121951219512195121951219") // ~0.00219512195 USD + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 + 307)) // 41000 * 0.75% => 307.5 + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 + 307)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 + 307)) + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("0") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 + 308)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 + 308)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 + 308)) + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("676097560975609756097560975609") // ~0.676 USD + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45100)) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("2195121951219512195121951219512") // ~2.1951 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(46100)) + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("2195121951219512195121951219512") // ~2.1951 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(47100)) + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(9)) + + let leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(90817) // ~9X leverage + + await expect(vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, 0, toUsd(100), true, user2.address)) + .to.be.revertedWith("Vault: position size exceeded") + + await expect(vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(10), toUsd(50), true, user2.address)) + .to.be.revertedWith("Vault: position collateral exceeded") + + await expect(vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(8.91), toUsd(50), true, user2.address)) + .to.be.revertedWith("Vault: liquidation fees exceed collateral") + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + const tx = await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(3), toUsd(50), true, user2.address) + await reportGasUsed(provider, tx, "decreasePosition gas used") + + leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(57887) // ~5.8X leverage + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(40)) // size + expect(position[1]).eq(toUsd(9.91 - 3)) // collateral + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000 / 90 * 40) // reserveAmount, 0.00225 * 40,000 => 90 + expect(position[5]).eq(toUsd(5)) // pnl + expect(position[6]).eq(true) + + expect(await vault.feeReserves(btc.address)).eq(969 + 106) // 0.00000106 * 45100 => ~0.05 USD + expect(await vault.reservedAmounts(btc.address)).eq(225000 / 90 * 40) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(33.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219 - 16878 - 106 - 1) + expect(await btc.balanceOf(user2.address)).eq(16878) // 0.00016878 * 47100 => 7.949538 USD + + await validateVaultBalance(expect, vault, btc, 1) + }) + + it("decreasePosition long minProfitBasisPoints", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await expect(vault.connect(user1).decreasePosition(user0.address, btc.address, btc.address, 0, 0, true, user2.address)) + .to.be.revertedWith("Vault: invalid msg.sender") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, 0, toUsd(1000), true, user2.address)) + .to.be.revertedWith("Vault: empty position") + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + // test that minProfitBasisPoints works as expected + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 - 1)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 - 1)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 - 1)) + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("2195121951219512195121951219") // ~0.00219512195 USD + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 + 307)) // 41000 * 0.75% => 307.5 + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 + 307)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000 + 307)) + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("0") + + await increaseTime(provider, 50 * 60) + await mineBlock(provider) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("0") + + await increaseTime(provider, 10 * 60 + 10) + await mineBlock(provider) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("673902439024390243902439024390") // 0.67390243902 + }) + + it("decreasePosition long with loss", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40790)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40690)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40590)) + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(0.9)) + + await expect(vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(4), toUsd(50), true, user2.address)) + .to.be.revertedWith("liquidation fees exceed collateral") + + const tx = await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(0), toUsd(50), true, user2.address) + await reportGasUsed(provider, tx, "decreasePosition gas used") + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(40)) // size + expect(position[1]).eq(toUsd(9.36)) // collateral + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(100000) // reserveAmount, 0.00100 * 40,000 => 40 + expect(position[5]).eq(toUsd(0.5)) // pnl + expect(position[6]).eq(false) + + expect(await vault.feeReserves(btc.address)).eq(969 + 122) // 0.00000122 * 40790 => ~0.05 USD + expect(await vault.reservedAmounts(btc.address)).eq(100000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(30.64)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219 - 122) + expect(await btc.balanceOf(user2.address)).eq(0) + + await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(0), toUsd(40), true, user2.address) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) + + expect(await vault.feeReserves(btc.address)).eq(969 + 122 + 98) // 0.00000098 * 40790 => ~0.04 USD + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219 - 122 - 98 - 21868) + expect(await btc.balanceOf(user2.address)).eq(21868) // 0.00021868 * 40790 => ~8.92 USD + + await validateVaultBalance(expect, vault, btc) + }) +}) diff --git a/test/core/Vault/decreaseShortPosition.js b/test/core/Vault/decreaseShortPosition.js new file mode 100644 index 00000000..7bc77b70 --- /dev/null +++ b/test/core/Vault/decreaseShortPosition.js @@ -0,0 +1,329 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("./helpers") + +use(solidity) + +describe("Vault.decreaseShortPosition", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("decreasePosition short", async () => { + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await expect(vault.connect(user1).decreasePosition(user0.address, btc.address, btc.address, 0, 0, false, user2.address)) + .to.be.revertedWith("Vault: invalid msg.sender") + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await expect(vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, 0, toUsd(1000), false, user2.address)) + .to.be.revertedWith("Vault: empty position") + + await dai.mint(user0.address, expandDecimals(1000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.connect(user0).transfer(vault.address, expandDecimals(10, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) // reserveAmount + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) // hasRealisedProfit + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(44000)) + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(9)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(9)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(89.99775)) + + let leverage = await vault.getPositionLeverage(user0.address, dai.address, btc.address, false) + expect(leverage).eq(90817) // ~9X leverage + + await expect(vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, 0, toUsd(100), false, user2.address)) + .to.be.revertedWith("Vault: position size exceeded") + + await expect(vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, toUsd(10), toUsd(50), false, user2.address)) + .to.be.revertedWith("Vault: position collateral exceeded") + + await expect(vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, toUsd(5), toUsd(50), false, user2.address)) + .to.be.revertedWith("Vault: liquidation fees exceed collateral") + + expect(await vault.feeReserves(dai.address)).eq("130000000000000000") // 0.13, 0.4 + 0.9 + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(90, 18)) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("99960000000000000000") // 99.96 + expect(await dai.balanceOf(user2.address)).eq(0) + + const tx = await vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, toUsd(3), toUsd(50), false, user2.address) + await reportGasUsed(provider, tx, "decreasePosition gas used") + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(40)) // size + expect(position[1]).eq(toUsd(9.91 - 3)) // collateral + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(40, 18)) // reserveAmount + expect(position[5]).eq(toUsd(49.99875)) // pnl + expect(position[6]).eq(true) // hasRealisedProfit + + expect(await vault.feeReserves(dai.address)).eq("180000000000000000") // 0.18, 0.4 + 0.9 + 0.5 + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(40, 18)) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("49961250000000000000") // 49.96125 + expect(await dai.balanceOf(user2.address)).eq("52948750000000000000") // 52.94875 + + // (9.91-3) + 0.44 + 49.70125 + 52.94875 => 110 + + leverage = await vault.getPositionLeverage(user0.address, dai.address, btc.address, false) + expect(leverage).eq(57887) // ~5.8X leverage + }) + + it("decreasePosition short minProfitBasisPoints", async () => { + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 60 * 60, // _minProfitTime + false // _hasDynamicFees + ) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await expect(vault.connect(user1).decreasePosition(user0.address, btc.address, btc.address, 0, 0, false, user2.address)) + .to.be.revertedWith("Vault: invalid msg.sender") + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await expect(vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, 0, toUsd(1000), false, user2.address)) + .to.be.revertedWith("Vault: empty position") + + await dai.mint(user0.address, expandDecimals(1000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.connect(user0).transfer(vault.address, expandDecimals(10, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) // reserveAmount + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) // hasRealisedProfit + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39701)) // 40,000 * (100 - 0.75)% => 39700 + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39701)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39701)) + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(0)) + + await increaseTime(provider, 50 * 60) + await mineBlock(provider) + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq("0") + + await increaseTime(provider, 10 * 60 + 10) + await mineBlock(provider) + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq("672750000000000000000000000000") // 0.67275 + }) + + it("decreasePosition short with loss", async () => { + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await dai.mint(user0.address, expandDecimals(1000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.connect(user0).transfer(vault.address, expandDecimals(10, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) // reserveAmount + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) // hasRealisedProfit + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40400)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40400)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40400)) + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(0.9)) + + let leverage = await vault.getPositionLeverage(user0.address, dai.address, btc.address, false) + expect(leverage).eq(90817) // ~9X leverage + + expect(await vault.feeReserves(dai.address)).eq("130000000000000000") // 0.13 + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(90, 18)) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("99960000000000000000") // 99.96 + expect(await dai.balanceOf(user2.address)).eq(0) + + await expect(vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, toUsd(4), toUsd(50), false, user2.address)) + .to.be.revertedWith("Vault: liquidation fees exceed collateral") + + await vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, toUsd(0), toUsd(50), false, user2.address) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(40)) // size + expect(position[1]).eq(toUsd(9.36)) // collateral, 9.91 - 0.5 (losses) - 0.05 (fees) + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(40, 18)) // reserveAmount + expect(position[5]).eq(toUsd(0.5)) // pnl + expect(position[6]).eq(false) // hasRealisedProfit + + expect(await vault.feeReserves(dai.address)).eq("180000000000000000") // 0.18 + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(40, 18)) // 40 + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("100460000000000000000") // 100.46 + expect(await dai.balanceOf(user2.address)).eq(0) + + await vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, toUsd(0), toUsd(40), false, user2.address) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + expect(position[5]).eq(0) // pnl + expect(position[6]).eq(true) // hasRealisedProfit + + expect(await vault.feeReserves(dai.address)).eq("220000000000000000") // 0.22 + expect(await vault.reservedAmounts(dai.address)).eq(0) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("100860000000000000000") // 100.86 + expect(await dai.balanceOf(user2.address)).eq("8920000000000000000") // 8.92 + }) +}) diff --git a/test/core/Vault/depositCollateral.js b/test/core/Vault/depositCollateral.js new file mode 100644 index 00000000..6e35cb34 --- /dev/null +++ b/test/core/Vault/depositCollateral.js @@ -0,0 +1,179 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.depositCollateral", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("deposit collateral", async () => { + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + await btc.mint(user0.address, expandDecimals(1, 8)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.connect(user0).transfer(vault.address, 117500 - 1) // 0.001174 BTC => 47 + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(47), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + expect(await vault.feeReserves(btc.address)).eq(0) + expect(await vault.usdgAmounts(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(0) + + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(0) + await vault.buyUSDG(btc.address, user1.address) + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(toUsd("46.8584")) + + expect(await vault.feeReserves(btc.address)).eq(353) // (117500 - 1) * 0.3% => 353 + expect(await vault.usdgAmounts(btc.address)).eq("46858400000000000000") // (117500 - 1 - 353) * 40000 + expect(await vault.poolAmounts(btc.address)).eq(117500 - 1 - 353) + + await btc.connect(user0).transfer(vault.address, 117500 - 1) + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(100), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.buyUSDG(btc.address, user1.address) + + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(toUsd("93.7168")) + + expect(await vault.feeReserves(btc.address)).eq(353 * 2) // (117500 - 1) * 0.3% * 2 + expect(await vault.usdgAmounts(btc.address)).eq("93716800000000000000") // (117500 - 1 - 353) * 40000 * 2 + expect(await vault.poolAmounts(btc.address)).eq((117500 - 1 - 353) * 2) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(47), true)) + .to.be.revertedWith("Vault: insufficient collateral for fees") + + await btc.connect(user0).transfer(vault.address, 22500) + + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + + const tx0 = await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(47), true) + await reportGasUsed(provider, tx0, "increasePosition gas used") + + expect(await vault.poolAmounts(btc.address)).eq(256792 - 114) + expect(await vault.reservedAmounts(btc.address)).eq(117500) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(38.047)) + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(toUsd(92.79)) // (256792 - 117500) sats * 40000 => 51.7968, 47 / 40000 * 41000 => ~45.8536 + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(47)) // size + expect(position[1]).eq(toUsd(8.953)) // collateral, 0.000225 BTC => 9, 9 - 0.047 => 8.953 + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(117500) // reserveAmount + + expect(await vault.feeReserves(btc.address)).eq(353 * 2 + 114) // fee is 0.047 USD => 0.00000114 BTC + expect(await vault.usdgAmounts(btc.address)).eq("93716800000000000000") // (117500 - 1 - 353) * 40000 * 2 + expect(await vault.poolAmounts(btc.address)).eq((117500 - 1 - 353) * 2 + 22500 - 114) + + let leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(52496) // ~5.2x + + await btc.connect(user0).transfer(vault.address, 22500) + + const tx1 = await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, 0, true) + await reportGasUsed(provider, tx1, "deposit collateral gas used") + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(47)) // size + expect(position[1]).eq(toUsd(8.953 + 9)) // collateral + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(117500) // reserveAmount + + expect(await vault.feeReserves(btc.address)).eq(353 * 2 + 114) // fee is 0.047 USD => 0.00000114 BTC + expect(await vault.usdgAmounts(btc.address)).eq("93716800000000000000") // (117500 - 1 - 353) * 40000 * 2 + expect(await vault.poolAmounts(btc.address)).eq((117500 - 1 - 353) * 2 + 22500 + 22500 - 114) + + leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(26179) // ~2.6x + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(51000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + await btc.connect(user0).transfer(vault.address, 100) + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, 0, true) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(47)) // size + expect(position[1]).eq(toUsd(8.953 + 9 + 0.05)) // collateral + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(117500) // reserveAmount + + expect(await vault.feeReserves(btc.address)).eq(353 * 2 + 114) // fee is 0.047 USD => 0.00000114 BTC + expect(await vault.usdgAmounts(btc.address)).eq("93716800000000000000") // (117500 - 1 - 353) * 40000 * 2 + expect(await vault.poolAmounts(btc.address)).eq((117500 - 1 - 353) * 2 + 22500 + 22500 + 100 - 114) + + leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(26106) // ~2.6x + + await validateVaultBalance(expect, vault, btc) + }) +}) diff --git a/test/core/Vault/directPoolDeposit.js b/test/core/Vault/directPoolDeposit.js new file mode 100644 index 00000000..2120c61c --- /dev/null +++ b/test/core/Vault/directPoolDeposit.js @@ -0,0 +1,78 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.settings", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("directPoolDeposit", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + + await expect(vault.connect(user0).directPoolDeposit(bnb.address)) + .to.be.revertedWith("Vault: _token not whitelisted") + + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await expect(vault.connect(user0).directPoolDeposit(bnb.address)) + .to.be.revertedWith("Vault: invalid tokenAmount") + + await bnb.mint(user0.address, 1000) + await bnb.connect(user0).transfer(vault.address, 1000) + + expect(await vault.poolAmounts(bnb.address)).eq(0) + await vault.connect(user0).directPoolDeposit(bnb.address) + expect(await vault.poolAmounts(bnb.address)).eq(1000) + + await validateVaultBalance(expect, vault, bnb) + }) +}) diff --git a/test/core/Vault/fundingRate.js b/test/core/Vault/fundingRate.js new file mode 100644 index 00000000..2773fbf9 --- /dev/null +++ b/test/core/Vault/fundingRate.js @@ -0,0 +1,155 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.fundingRates", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("funding rate", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(46100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(47100)) + + let leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(90817) // ~9X leverage + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + const tx0 = await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(3), toUsd(50), true, user2.address) + await reportGasUsed(provider, tx0, "decreasePosition gas used") + + leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(57887) // ~5.8X leverage + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(40)) // size + expect(position[1]).eq(toUsd(9.91 - 3)) // collateral + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000 / 90 * 40) // reserveAmount, 0.00225 * 40,000 => 90 + expect(position[5]).eq(toUsd(5)) // pnl + expect(position[6]).eq(true) + + expect(await vault.feeReserves(btc.address)).eq(969 + 106) // 0.00000106 * 45100 => ~0.05 USD + expect(await vault.reservedAmounts(btc.address)).eq(225000 / 90 * 40) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(33.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 16878 - 106 - 1 - 219) // 257046 + expect(await btc.balanceOf(user2.address)).eq(16878) // 0.00016878 * 47100 => 7.949538 USD + + await increaseTime(provider, 8 * 60 * 60 + 10) + await mineBlock(provider) + + await expect(vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(3), 0, true, user2.address)) + .to.be.revertedWith("Vault: liquidation fees exceed collateral") + + const tx1 = await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(1), 0, true, user2.address) + await reportGasUsed(provider, tx1, "withdraw collateral gas used") + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(40)) // size + expect(position[1]).eq(toUsd(9.91 - 3 - 1)) // collateral + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(233) // entryFundingRate + expect(position[4]).eq(225000 / 90 * 40) // reserveAmount, 0.00225 * 40,000 => 90 + expect(position[5]).eq(toUsd(5)) // pnl + expect(position[6]).eq(true) + + expect(await vault.getUtilisation(btc.address)).eq(392275) // 100000 / 254923 => ~39.2% + + // funding rate factor => 600 / 1000000 (0.06%) + // utilisation => ~39.1% + // funding fee % => 0.02351628% + // position size => 40 USD + // funding fee => 0.0094 USD + // 0.00000019 BTC => 0.00000019 * 47100 => ~0.009 USD + + expect(await vault.feeReserves(btc.address)).eq(969 + 106 + 19) + expect(await vault.reservedAmounts(btc.address)).eq(225000 / 90 * 40) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(34.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 16878 - 106 - 1 - 2123 - 219) // 0.00002123* 47100 => 1 USD + expect(await btc.balanceOf(user2.address)).eq(16878 + 2123 - 20) + + await validateVaultBalance(expect, vault, btc, 2) + }) +}) diff --git a/test/core/Vault/getFeeBasisPoints.js b/test/core/Vault/getFeeBasisPoints.js new file mode 100644 index 00000000..adfae6fa --- /dev/null +++ b/test/core/Vault/getFeeBasisPoints.js @@ -0,0 +1,162 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.getFeeBasisPoints", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 20, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + true // _hasDynamicFees + ) + }) + + it("getFeeBasisPoints", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + expect(await vault.getTargetUsdgAmount(bnb.address)).eq(0) + + await bnb.mint(vault.address, 100) + await vault.connect(user0).buyUSDG(bnb.address, wallet.address) + + expect(await vault.usdgAmounts(bnb.address)).eq(29700) + expect(await vault.getTargetUsdgAmount(bnb.address)).eq(29700) + + // usdgAmount(bnb) is 29700, targetAmount(bnb) is 29700 + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 100, 50, true)).eq(100) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 100, 50, true)).eq(104) + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 100, 50, false)).eq(100) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 100, 50, false)).eq(104) + + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 50, 100, true)).eq(51) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 50, 100, true)).eq(58) + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 50, 100, false)).eq(51) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 50, 100, false)).eq(58) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + expect(await vault.getTargetUsdgAmount(bnb.address)).eq(14850) + expect(await vault.getTargetUsdgAmount(dai.address)).eq(14850) + + // usdgAmount(bnb) is 29700, targetAmount(bnb) is 14850 + // incrementing bnb has an increased fee, while reducing bnb has a decreased fee + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 100, 50, true)).eq(150) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 100, 50, true)).eq(150) + expect(await vault.getFeeBasisPoints(bnb.address, 10000, 100, 50, true)).eq(150) + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 100, 50, false)).eq(50) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 100, 50, false)).eq(50) + expect(await vault.getFeeBasisPoints(bnb.address, 10000, 100, 50, false)).eq(50) + expect(await vault.getFeeBasisPoints(bnb.address, 20000, 100, 50, false)).eq(50) + expect(await vault.getFeeBasisPoints(bnb.address, 25000, 100, 50, false)).eq(50) + expect(await vault.getFeeBasisPoints(bnb.address, 100000, 100, 50, false)).eq(150) + + await dai.mint(vault.address, 20000) + await vault.connect(user0).buyUSDG(dai.address, wallet.address) + + expect(await vault.getTargetUsdgAmount(bnb.address)).eq(24850) + expect(await vault.getTargetUsdgAmount(dai.address)).eq(24850) + + const bnbConfig = getBnbConfig(bnb, bnbPriceFeed) + bnbConfig[2] = 30000 + await vault.setTokenConfig(...bnbConfig) + + expect(await vault.getTargetUsdgAmount(bnb.address)).eq(37275) + expect(await vault.getTargetUsdgAmount(dai.address)).eq(12425) + + expect(await vault.usdgAmounts(bnb.address)).eq(29700) + + // usdgAmount(bnb) is 29700, targetAmount(bnb) is 37270 + // incrementing bnb has a decreased fee, while reducing bnb has an increased fee + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 100, 50, true)).eq(90) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 100, 50, true)).eq(90) + expect(await vault.getFeeBasisPoints(bnb.address, 10000, 100, 50, true)).eq(90) + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 100, 50, false)).eq(110) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 100, 50, false)).eq(113) + expect(await vault.getFeeBasisPoints(bnb.address, 10000, 100, 50, false)).eq(116) + + bnbConfig[2] = 5000 + await vault.setTokenConfig(...bnbConfig) + + await bnb.mint(vault.address, 200) + await vault.connect(user0).buyUSDG(bnb.address, wallet.address) + + expect(await vault.usdgAmounts(bnb.address)).eq(89100) + expect(await vault.getTargetUsdgAmount(bnb.address)).eq(36366) + expect(await vault.getTargetUsdgAmount(dai.address)).eq(72733) + + // usdgAmount(bnb) is 88800, targetAmount(bnb) is 36266 + // incrementing bnb has an increased fee, while reducing bnb has a decreased fee + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 100, 50, true)).eq(150) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 100, 50, true)).eq(150) + expect(await vault.getFeeBasisPoints(bnb.address, 10000, 100, 50, true)).eq(150) + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 100, 50, false)).eq(28) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 100, 50, false)).eq(28) + expect(await vault.getFeeBasisPoints(bnb.address, 20000, 100, 50, false)).eq(28) + expect(await vault.getFeeBasisPoints(bnb.address, 50000, 100, 50, false)).eq(28) + + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 50, 100, true)).eq(150) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 50, 100, true)).eq(150) + expect(await vault.getFeeBasisPoints(bnb.address, 10000, 50, 100, true)).eq(150) + expect(await vault.getFeeBasisPoints(bnb.address, 1000, 50, 100, false)).eq(0) + expect(await vault.getFeeBasisPoints(bnb.address, 5000, 50, 100, false)).eq(0) + expect(await vault.getFeeBasisPoints(bnb.address, 20000, 50, 100, false)).eq(0) + expect(await vault.getFeeBasisPoints(bnb.address, 50000, 50, 100, false)).eq(0) + }) +}) diff --git a/test/core/Vault/getPrice.js b/test/core/Vault/getPrice.js new file mode 100644 index 00000000..95d39026 --- /dev/null +++ b/test/core/Vault/getPrice.js @@ -0,0 +1,198 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed, newWallet } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getDaiConfig, getBnbConfig, getBtcConfig } = require("./helpers") + +use(solidity) + +describe("Vault.getPrice", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let eth + let ethPriceFeed + let dai + let daiPriceFeed + let busd + let busdPriceFeed + let usdc + let usdcPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + usdc = await deployContract("Token", []) + usdcPriceFeed = await deployContract("PriceFeed", []) + + busd = await deployContract("Token", []) + busdPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(usdc.address, usdcPriceFeed.address, 8, true) + }) + + it("getPrice", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + expect(await vaultPriceFeed.getPrice(dai.address, true, true, true)).eq(expandDecimals(1, 30)) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1.1)) + expect(await vaultPriceFeed.getPrice(dai.address, true, true, true)).eq(expandDecimals(11, 29)) + + await usdcPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig( + usdc.address, // _token + 18, // _tokenDecimals + 10000, // _tokenWeight + 75, // _minProfitBps, + 0, // _maxUsdgAmount + false, // _isStable + true // _isShortable + ) + + expect(await vaultPriceFeed.getPrice(usdc.address, true, true, true)).eq(expandDecimals(1, 30)) + await usdcPriceFeed.setLatestAnswer(toChainlinkPrice(1.1)) + expect(await vaultPriceFeed.getPrice(usdc.address, true, true, true)).eq(expandDecimals(11, 29)) + + await vaultPriceFeed.setMaxStrictPriceDeviation(expandDecimals(1, 29)) + expect(await vaultPriceFeed.getPrice(usdc.address, true, true, true)).eq(expandDecimals(1, 30)) + + await usdcPriceFeed.setLatestAnswer(toChainlinkPrice(1.11)) + expect(await vaultPriceFeed.getPrice(usdc.address, true, true, true)).eq(expandDecimals(111, 28)) + + expect(await vaultPriceFeed.getPrice(usdc.address, false, true, true)).eq(expandDecimals(1, 30)) + + await usdcPriceFeed.setLatestAnswer(toChainlinkPrice(0.9)) + expect(await vaultPriceFeed.getPrice(usdc.address, false, true, true)).eq(expandDecimals(1, 30)) + + await vaultPriceFeed.setSpreadBasisPoints(usdc.address, 20) + expect(await vaultPriceFeed.getPrice(usdc.address, false, true, true)).eq(expandDecimals(1, 30)) + + await vaultPriceFeed.setSpreadBasisPoints(usdc.address, 0) + await usdcPriceFeed.setLatestAnswer(toChainlinkPrice(0.89)) + expect(await vaultPriceFeed.getPrice(usdc.address, false, true, true)).eq(expandDecimals(89, 28)) + + await vaultPriceFeed.setSpreadBasisPoints(usdc.address, 20) + expect(await vaultPriceFeed.getPrice(usdc.address, false, true, true)).eq("888220000000000000000000000000") + + await vaultPriceFeed.setUseV2Pricing(true) + expect(await vaultPriceFeed.getPrice(usdc.address, false, true, true)).eq("888220000000000000000000000000") + }) + + it("includes AMM price", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(600)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(80000)) + await busdPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + const bnbBusd = await deployContract("PancakePair", []) + await bnbBusd.setReserves(expandDecimals(1000, 18), expandDecimals(300 * 1000, 18)) + + const ethBnb = await deployContract("PancakePair", []) + await ethBnb.setReserves(expandDecimals(800, 18), expandDecimals(100, 18)) + + const btcBnb = await deployContract("PancakePair", []) + await btcBnb.setReserves(expandDecimals(10, 18), expandDecimals(2000, 18)) + + await vaultPriceFeed.setTokens(btc.address, eth.address, bnb.address) + await vaultPriceFeed.setPairs(bnbBusd.address, ethBnb.address, btcBnb.address) + + await vaultPriceFeed.setIsAmmEnabled(false) + + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(600)) + expect(await vaultPriceFeed.getPrice(btc.address, false, true, true)).eq(toNormalizedPrice(80000)) + + await vaultPriceFeed.setIsAmmEnabled(true) + + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(300)) + expect(await vaultPriceFeed.getPrice(btc.address, false, true, true)).eq(toNormalizedPrice(60000)) + + await vaultPriceFeed.setIsAmmEnabled(false) + + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(600)) + expect(await vaultPriceFeed.getPrice(btc.address, false, true, true)).eq(toNormalizedPrice(80000)) + + await vaultPriceFeed.setIsAmmEnabled(true) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(200)) + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(200)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + expect(await vaultPriceFeed.getPrice(btc.address, false, true, true)).eq(toNormalizedPrice(50000)) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(250)) + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(200)) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(280)) + expect(await vaultPriceFeed.getPrice(bnb.address, true, true, true)).eq(toNormalizedPrice(300)) + + await vaultPriceFeed.setSpreadBasisPoints(bnb.address, 20) + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(199.6)) + expect(await vaultPriceFeed.getPrice(bnb.address, true, true, true)).eq(toNormalizedPrice(300.6)) + + await vaultPriceFeed.setUseV2Pricing(true) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(301)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(302)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(303)) + + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(299.4)) + expect(await vaultPriceFeed.getPrice(bnb.address, true, true, true)).eq(toNormalizedPrice(303.606)) + + await vaultPriceFeed.setSpreadThresholdBasisPoints(90) + + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(299.4)) + expect(await vaultPriceFeed.getPrice(bnb.address, true, true, true)).eq(toNormalizedPrice(303.606)) + + await vaultPriceFeed.setSpreadThresholdBasisPoints(100) + + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(299.4)) + expect(await vaultPriceFeed.getPrice(bnb.address, true, true, true)).eq(toNormalizedPrice(300.6)) + + await vaultPriceFeed.setFavorPrimaryPrice(true) + + expect(await vaultPriceFeed.getPrice(bnb.address, false, true, true)).eq(toNormalizedPrice(300.398)) + expect(await vaultPriceFeed.getPrice(bnb.address, true, true, true)).eq(toNormalizedPrice(303.606)) + }) +}) diff --git a/test/core/Vault/helpers.js b/test/core/Vault/helpers.js new file mode 100644 index 00000000..f2cebdda --- /dev/null +++ b/test/core/Vault/helpers.js @@ -0,0 +1,148 @@ +const { expandDecimals } = require("../../shared/utilities") +const { toUsd } = require("../../shared/units") +const { deployContract } = require("../../shared/fixtures") + +const errors = [ + "Vault: zero error", + "Vault: already initialized", + "Vault: invalid _maxLeverage", + "Vault: invalid _taxBasisPoints", + "Vault: invalid _stableTaxBasisPoints", + "Vault: invalid _mintBurnFeeBasisPoints", + "Vault: invalid _swapFeeBasisPoints", + "Vault: invalid _stableSwapFeeBasisPoints", + "Vault: invalid _marginFeeBasisPoints", + "Vault: invalid _liquidationFeeUsd", + "Vault: invalid _fundingInterval", + "Vault: invalid _fundingRateFactor", + "Vault: invalid _stableFundingRateFactor", + "Vault: token not whitelisted", + "Vault: _token not whitelisted", + "Vault: invalid tokenAmount", + "Vault: _token not whitelisted", + "Vault: invalid tokenAmount", + "Vault: invalid usdgAmount", + "Vault: _token not whitelisted", + "Vault: invalid usdgAmount", + "Vault: invalid redemptionAmount", + "Vault: invalid amountOut", + "Vault: swaps not enabled", + "Vault: _tokenIn not whitelisted", + "Vault: _tokenOut not whitelisted", + "Vault: invalid tokens", + "Vault: invalid amountIn", + "Vault: leverage not enabled", + "Vault: insufficient collateral for fees", + "Vault: invalid position.size", + "Vault: empty position", + "Vault: position size exceeded", + "Vault: position collateral exceeded", + "Vault: invalid liquidator", + "Vault: empty position", + "Vault: position cannot be liquidated", + "Vault: invalid position", + "Vault: invalid _averagePrice", + "Vault: collateral should be withdrawn", + "Vault: _size must be more than _collateral", + "Vault: invalid msg.sender", + "Vault: mismatched tokens", + "Vault: _collateralToken not whitelisted", + "Vault: _collateralToken must not be a stableToken", + "Vault: _collateralToken not whitelisted", + "Vault: _collateralToken must be a stableToken", + "Vault: _indexToken must not be a stableToken", + "Vault: _indexToken not shortable", + "Vault: invalid increase", + "Vault: reserve exceeds pool", + "Vault: max USDG exceeded", + "Vault: reserve exceeds pool", + "Vault: forbidden", + "Vault: forbidden", + "Vault: maxGasPrice exceeded" +] + +async function initVaultErrors(vault) { + const vaultErrorController = await deployContract("VaultErrorController", []) + await vault.setErrorController(vaultErrorController.address) + await vaultErrorController.setErrors(vault.address, errors); +} + +async function initVault(vault, router, usdg, priceFeed) { + await vault.initialize( + router.address, // router + usdg.address, // usdg + priceFeed.address, // priceFeed + toUsd(5), // liquidationFeeUsd + 600, // fundingRateFactor + 600 // stableFundingRateFactor + ) + await initVaultErrors(vault) +} + +async function validateVaultBalance(expect, vault, token, offset) { + if (!offset) { offset = 0 } + const poolAmount = await vault.poolAmounts(token.address) + const feeReserve = await vault.feeReserves(token.address) + const balance = await token.balanceOf(vault.address) + let amount = poolAmount.add(feeReserve) + expect(balance).gt(0) + expect(poolAmount.add(feeReserve).add(offset)).eq(balance) +} + +function getBnbConfig(bnb, bnbPriceFeed) { + return [ + bnb.address, // _token + 18, // _tokenDecimals + 10000, // _tokenWeight + 75, // _minProfitBps, + 0, // _maxUsdgAmount + false, // _isStable + true // _isShortable + ] +} + +function getEthConfig(eth, ethPriceFeed) { + return [ + eth.address, // _token + 18, // _tokenDecimals + 10000, // _tokenWeight + 75, // _minProfitBps + 0, // _maxUsdgAmount + false, // _isStable + true // _isShortable + ] +} + +function getBtcConfig(btc, btcPriceFeed) { + return [ + btc.address, // _token + 8, // _tokenDecimals + 10000, // _tokenWeight + 75, // _minProfitBps + 0, // _maxUsdgAmount + false, // _isStable + true // _isShortable + ] +} + +function getDaiConfig(dai, daiPriceFeed) { + return [ + dai.address, // _token + 18, // _tokenDecimals + 10000, // _tokenWeight + 75, // _minProfitBps + 0, // _maxUsdgAmount + true, // _isStable + false // _isShortable + ] +} + +module.exports = { + errors, + initVault, + validateVaultBalance, + getBnbConfig, + getBtcConfig, + getEthConfig, + getDaiConfig +} diff --git a/test/core/Vault/increaseLongPosition.js b/test/core/Vault/increaseLongPosition.js new file mode 100644 index 00000000..5d2c9505 --- /dev/null +++ b/test/core/Vault/increaseLongPosition.js @@ -0,0 +1,335 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.increaseLongPosition", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + let glpManager + let glp + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + glp = await deployContract("GLP", []) + glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 24 * 60 * 60]) + }) + + it("increasePosition long validations", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setMaxGasPrice("20000000000") // 20 gwei + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + await expect(vault.connect(user1).increasePosition(user0.address, btc.address, btc.address, 0, true)) + .to.be.revertedWith("Vault: invalid msg.sender") + await expect(vault.connect(user1).increasePosition(user0.address, btc.address, btc.address, 0, true, { gasPrice: "21000000000" })) + .to.be.revertedWith("Vault: maxGasPrice exceeded") + await vault.setMaxGasPrice(0) + await vault.setIsLeverageEnabled(false) + await expect(vault.connect(user1).increasePosition(user0.address, btc.address, btc.address, 0, true, { gasPrice: "21000000000" })) + .to.be.revertedWith("Vault: leverage not enabled") + await vault.setIsLeverageEnabled(true) + await vault.connect(user0).addRouter(user1.address) + await expect(vault.connect(user1).increasePosition(user0.address, btc.address, bnb.address, 0, true)) + .to.be.revertedWith("Vault: mismatched tokens") + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, bnb.address, toUsd(1000), true)) + .to.be.revertedWith("Vault: mismatched tokens") + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, dai.address, toUsd(1000), true)) + .to.be.revertedWith("Vault: _collateralToken must not be a stableToken") + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(1000), true)) + .to.be.revertedWith("Vault: _collateralToken not whitelisted") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(1000), true)) + .to.be.revertedWith("Vault: insufficient collateral for fees") + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, 0, true)) + .to.be.revertedWith("Vault: invalid position.size") + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user0).transfer(vault.address, 2500 - 1) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(1000), true)) + .to.be.revertedWith("Vault: insufficient collateral for fees") + + await btc.connect(user0).transfer(vault.address, 1) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(1000), true)) + .to.be.revertedWith("Vault: losses exceed collateral") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(1000), true)) + .to.be.revertedWith("Vault: fees exceed collateral") + + await btc.connect(user0).transfer(vault.address, 10000) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(1000), true)) + .to.be.revertedWith("Vault: liquidation fees exceed collateral") + + await btc.connect(user0).transfer(vault.address, 10000) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(500), true)) + .to.be.revertedWith("Vault: maxLeverage exceeded") + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(8), true)) + .to.be.revertedWith("Vault: _size must be more than _collateral") + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(47), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + }) + + it("increasePosition long", async () => { + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + await btc.mint(user0.address, expandDecimals(1, 8)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.connect(user0).transfer(vault.address, 117500 - 1) // 0.001174 BTC => 47 + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(47), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + expect(await vault.feeReserves(btc.address)).eq(0) + expect(await vault.usdgAmounts(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(0) + + expect(await glpManager.getAumInUsdg(true)).eq(0) + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(0) + await vault.buyUSDG(btc.address, user1.address) + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(toUsd("46.8584")) + expect(await glpManager.getAumInUsdg(true)).eq("48029860000000000000") // 48.02986 + expect(await glpManager.getAumInUsdg(false)).eq("46858400000000000000") // 46.8584 + + expect(await vault.feeReserves(btc.address)).eq(353) // (117500 - 1) * 0.3% => 353 + expect(await vault.usdgAmounts(btc.address)).eq("46858400000000000000") // (117500 - 1 - 353) * 40000 + expect(await vault.poolAmounts(btc.address)).eq(117500 - 1 - 353) + + await btc.connect(user0).transfer(vault.address, 117500 - 1) + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(100), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.buyUSDG(btc.address, user1.address) + + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(toUsd("93.7168")) + expect(await glpManager.getAumInUsdg(true)).eq("96059720000000000000") // 96.05972 + expect(await glpManager.getAumInUsdg(false)).eq("93716800000000000000") // 93.7168 + + expect(await vault.feeReserves(btc.address)).eq(353 * 2) // (117500 - 1) * 0.3% * 2 + expect(await vault.usdgAmounts(btc.address)).eq("93716800000000000000") // (117500 - 1 - 353) * 40000 * 2 + expect(await vault.poolAmounts(btc.address)).eq((117500 - 1 - 353) * 2) + + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(47), true)) + .to.be.revertedWith("Vault: insufficient collateral for fees") + + await btc.connect(user0).transfer(vault.address, 22500) + + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + expect(position[5]).eq(0) // realisedPnl + expect(position[6]).eq(true) // hasProfit + expect(position[7]).eq(0) // lastIncreasedTime + + const tx = await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(47), true) + await reportGasUsed(provider, tx, "increasePosition gas used") + + const blockTime = await getBlockTime(provider) + + expect(await vault.poolAmounts(btc.address)).eq(256792 - 114) + expect(await vault.reservedAmounts(btc.address)).eq(117500) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(38.047)) + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(toUsd(92.79)) + expect(await glpManager.getAumInUsdg(true)).eq("95109980000000000000") // 95.10998 + expect(await glpManager.getAumInUsdg(false)).eq("93718200000000000000") // 93.7182 + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(47)) // size + expect(position[1]).eq(toUsd(8.953)) // collateral, 0.000225 BTC => 9, 9 - 0.047 => 8.953 + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(117500) // reserveAmount + expect(position[5]).eq(0) // realisedPnl + expect(position[6]).eq(true) // hasProfit + expect(position[7]).eq(blockTime) // lastIncreasedTime + + expect(await vault.feeReserves(btc.address)).eq(353 * 2 + 114) // fee is 0.047 USD => 0.00000114 BTC + expect(await vault.usdgAmounts(btc.address)).eq("93716800000000000000") // (117500 - 1 - 353) * 40000 * 2 + expect(await vault.poolAmounts(btc.address)).eq((117500 - 1 - 353) * 2 + 22500 - 114) + + expect(await vault.globalShortSizes(btc.address)).eq(0) + expect(await vault.globalShortAveragePrices(btc.address)).eq(0) + + await validateVaultBalance(expect, vault, btc) + }) + + it("increasePosition long aum", async () => { + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(100000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(100000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(100000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(1, 8)) + + expect(await vault.feeReserves(btc.address)).eq(0) + expect(await vault.usdgAmounts(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(0) + + expect(await glpManager.getAumInUsdg(true)).eq(0) + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(0) + await vault.buyUSDG(btc.address, user1.address) + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(toUsd(99700)) + expect(await glpManager.getAumInUsdg(true)).eq(expandDecimals(99700, 18)) + + expect(await vault.feeReserves(btc.address)).eq("300000") // 0.003 BTC + expect(await vault.usdgAmounts(btc.address)).eq(expandDecimals(99700, 18)) + expect(await vault.poolAmounts(btc.address)).eq("99700000") // 0.997 + + await btc.mint(user0.address, expandDecimals(5, 7)) + await btc.connect(user0).transfer(vault.address, expandDecimals(5, 7)) + + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + expect(position[5]).eq(0) // realisedPnl + expect(position[6]).eq(true) // hasProfit + expect(position[7]).eq(0) // lastIncreasedTime + + const tx = await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(80000), true) + await reportGasUsed(provider, tx, "increasePosition gas used") + + const blockTime = await getBlockTime(provider) + + expect(await vault.poolAmounts(btc.address)).eq("149620000") // 1.4962 BTC + expect(await vault.reservedAmounts(btc.address)).eq("80000000") // 0.8 BTC + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(30080)) // 80000 - 49920 + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq(toUsd(99700)) + expect(await glpManager.getAumInUsdg(true)).eq(expandDecimals(99700, 18)) + expect(await glpManager.getAumInUsdg(false)).eq(expandDecimals(99700, 18)) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(80000)) // size + expect(position[1]).eq(toUsd(49920)) // collateral + expect(position[2]).eq(toNormalizedPrice(100000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq("80000000") // 0.8 BTC + expect(position[5]).eq(0) // realisedPnl + expect(position[6]).eq(true) // hasProfit + expect(position[7]).eq(blockTime) // lastIncreasedTime + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(150000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(150000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(150000)) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(40000)) + expect(await glpManager.getAumInUsdg(true)).eq(expandDecimals(134510, 18)) // 30080 + (1.4962-0.8)*150000 + expect(await glpManager.getAumInUsdg(false)).eq(expandDecimals(134510, 18)) // 30080 + (1.4962-0.8)*150000 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(75000)) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(40000)) + expect(await glpManager.getAumInUsdg(true)).eq(expandDecimals(82295, 18)) // 30080 + (1.4962-0.8)*75000 + expect(await glpManager.getAumInUsdg(false)).eq(expandDecimals(64890, 18)) // 30080 + (1.4962-0.8)*50000 + + await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, 0, toUsd(80000), true, user2.address) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + expect(position[5]).eq(0) // realisedPnl + expect(position[6]).eq(true) // hasProfit + expect(position[7]).eq(0) // lastIncreasedTime + + expect(await vault.poolAmounts(btc.address)).eq("136393334") // 1.36393334 BTC + expect(await vault.reservedAmounts(btc.address)).eq(0) // 0.8 BTC + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(0)) + expect(await vault.getRedemptionCollateralUsd(btc.address)).eq("68196667000000000000000000000000000") + expect(await glpManager.getAumInUsdg(true)).eq("102295000500000000000000") // 102295.0005 + expect(await glpManager.getAumInUsdg(false)).eq("68196667000000000000000") // 68196.667 + + expect(await vault.globalShortSizes(btc.address)).eq(0) + expect(await vault.globalShortAveragePrices(btc.address)).eq(0) + + await validateVaultBalance(expect, vault, btc) + }) +}) diff --git a/test/core/Vault/increaseShortPosition.js b/test/core/Vault/increaseShortPosition.js new file mode 100644 index 00000000..a8255dcb --- /dev/null +++ b/test/core/Vault/increaseShortPosition.js @@ -0,0 +1,334 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("./helpers") + +use(solidity) + +describe("Vault.increaseShortPosition", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let glpManager + let vaultPriceFeed + let glp + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + glp = await deployContract("GLP", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 24 * 60 * 60]) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("increasePosition short validations", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + await expect(vault.connect(user1).increasePosition(user0.address, dai.address, btc.address, 0, false)) + .to.be.revertedWith("Vault: invalid msg.sender") + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(1000), false)) + .to.be.revertedWith("Vault: _collateralToken not whitelisted") + await expect(vault.connect(user0).increasePosition(user0.address, bnb.address, bnb.address, toUsd(1000), false)) + .to.be.revertedWith("Vault: _collateralToken must be a stableToken") + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, dai.address, toUsd(1000), false)) + .to.be.revertedWith("Vault: _indexToken must not be a stableToken") + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(1000), false)) + .to.be.revertedWith("Vault: _indexToken not shortable") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig( + btc.address, // _token + 8, // _tokenDecimals + 10000, // _tokenWeight + 75, // _minProfitBps + 0, // _maxUsdgAmount + false, // _isStable + false // _isShortable + ) + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(1000), false)) + .to.be.revertedWith("Vault: _indexToken not shortable") + + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(1000), false)) + .to.be.revertedWith("Vault: insufficient collateral for fees") + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, 0, false)) + .to.be.revertedWith("Vault: invalid position.size") + + await dai.mint(user0.address, expandDecimals(1000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(9, 17)) + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(1000), false)) + .to.be.revertedWith("Vault: insufficient collateral for fees") + + await dai.connect(user0).transfer(vault.address, expandDecimals(4, 18)) + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(1000), false)) + .to.be.revertedWith("Vault: losses exceed collateral") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(100), false)) + .to.be.revertedWith("Vault: liquidation fees exceed collateral") + + await dai.connect(user0).transfer(vault.address, expandDecimals(6, 18)) + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(8), false)) + .to.be.revertedWith("Vault: _size must be more than _collateral") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(600), false)) + .to.be.revertedWith("Vault: maxLeverage exceeded") + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(100), false)) + .to.be.revertedWith("Vault: reserve exceeds pool") + }) + + it("increasePosition short", async () => { + let globalDelta = await vault.getGlobalShortDelta(btc.address) + expect(await globalDelta[0]).eq(false) + expect(await globalDelta[1]).eq(0) + expect(await glpManager.getAumInUsdg(true)).eq(0) + expect(await glpManager.getAumInUsdg(false)).eq(0) + + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await dai.mint(user0.address, expandDecimals(1000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(500, 18)) + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(99), false)) + .to.be.revertedWith("Vault: _size must be more than _collateral") + + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(501), false)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + expect(await vault.feeReserves(dai.address)).eq(0) + expect(await vault.usdgAmounts(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq(0) + + expect(await vault.getRedemptionCollateralUsd(dai.address)).eq(0) + await vault.buyUSDG(dai.address, user1.address) + expect(await vault.getRedemptionCollateralUsd(dai.address)).eq("499800000000000000000000000000000") + + expect(await vault.feeReserves(dai.address)).eq("200000000000000000") // 0.2 + expect(await vault.usdgAmounts(dai.address)).eq("499800000000000000000") // 499.8 + expect(await vault.poolAmounts(dai.address)).eq("499800000000000000000") // 499.8 + + globalDelta = await vault.getGlobalShortDelta(btc.address) + expect(await globalDelta[0]).eq(false) + expect(await globalDelta[1]).eq(0) + expect(await glpManager.getAumInUsdg(true)).eq("499800000000000000000") + expect(await glpManager.getAumInUsdg(false)).eq("499800000000000000000") + + await dai.connect(user0).transfer(vault.address, expandDecimals(20, 18)) + await expect(vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(501), false)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + expect(position[5]).eq(0) // realisedPnl + expect(position[6]).eq(true) // hasProfit + expect(position[7]).eq(0) // lastIncreasedTime + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + const tx = await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + await reportGasUsed(provider, tx, "increasePosition gas used") + + expect(await vault.poolAmounts(dai.address)).eq("499800000000000000000") + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(90, 18)) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.getRedemptionCollateralUsd(dai.address)).eq("499800000000000000000000000000000") + + const blockTime = await getBlockTime(provider) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(19.91)) // collateral + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) // reserveAmount + expect(position[5]).eq(0) // realisedPnl + expect(position[6]).eq(true) // hasProfit + expect(position[7]).eq(blockTime) // lastIncreasedTime + + expect(await vault.feeReserves(dai.address)).eq("290000000000000000") // 0.29 + expect(await vault.usdgAmounts(dai.address)).eq("499800000000000000000") // 499.8 + expect(await vault.poolAmounts(dai.address)).eq("499800000000000000000") // 499.8 + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(90)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + + globalDelta = await vault.getGlobalShortDelta(btc.address) + expect(await globalDelta[0]).eq(false) + expect(await globalDelta[1]).eq(toUsd(2.25)) + expect(await glpManager.getAumInUsdg(true)).eq("502050000000000000000") + expect(await glpManager.getAumInUsdg(false)).eq("499800000000000000000") + + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(2.25)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(42000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(42000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(42000)) + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(4.5)) + + globalDelta = await vault.getGlobalShortDelta(btc.address) + expect(await globalDelta[0]).eq(false) + expect(await globalDelta[1]).eq(toUsd(4.5)) + expect(await glpManager.getAumInUsdg(true)).eq("504300000000000000000") // 499.8 + 4.5 + expect(await glpManager.getAumInUsdg(false)).eq("504300000000000000000") // 499.8 + 4.5 + + await vault.connect(user0).decreasePosition(user0.address, dai.address, btc.address, toUsd(3), toUsd(50), false, user2.address) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(40)) // size + expect(position[1]).eq(toUsd(14.41)) // collateral + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(40, 18)) // reserveAmount + expect(position[5]).eq(toUsd(2.5)) // realisedPnl + expect(position[6]).eq(false) // hasProfit + expect(position[7]).eq(blockTime) // lastIncreasedTime + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(2)) + + expect(await vault.feeReserves(dai.address)).eq("340000000000000000") // 0.18 + expect(await vault.usdgAmounts(dai.address)).eq("499800000000000000000") // 499.8 + expect(await vault.poolAmounts(dai.address)).eq("502300000000000000000") // 502.3 + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(40)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + + globalDelta = await vault.getGlobalShortDelta(btc.address) + expect(await globalDelta[0]).eq(false) + expect(await globalDelta[1]).eq(toUsd(2)) + expect(await glpManager.getAumInUsdg(true)).eq("504300000000000000000") // 499.8 + 4.5 + expect(await glpManager.getAumInUsdg(false)).eq("504300000000000000000") // 499.8 + 4.5 + + await dai.mint(vault.address, expandDecimals(50, 18)) + await vault.connect(user1).increasePosition(user1.address, dai.address, btc.address, toUsd(200), false) + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(240)) + expect(await vault.globalShortAveragePrices(btc.address)).eq("41652892561983471074380165289256198") + + globalDelta = await vault.getGlobalShortDelta(btc.address) + expect(await globalDelta[0]).eq(false) + expect(await globalDelta[1]).eq(toUsd(2)) + expect(await glpManager.getAumInUsdg(true)).eq("504300000000000000000") // 502.3 + 2 + expect(await glpManager.getAumInUsdg(false)).eq("504300000000000000000") // 502.3 + 2 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(1)) + + delta = await vault.getPositionDelta(user1.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq("4761904761904761904761904761904") // 4.76 + + globalDelta = await vault.getGlobalShortDelta(btc.address) + expect(await globalDelta[0]).eq(true) + expect(await globalDelta[1]).eq("3761904761904761904761904761904") + expect(await glpManager.getAumInUsdg(true)).eq("498538095238095238095") // 502.3 + 1 - 4.76 => 498.53 + expect(await glpManager.getAumInUsdg(false)).eq("492776190476190476190") // 492.77619047619047619 + + await dai.mint(vault.address, expandDecimals(20, 18)) + await vault.connect(user2).increasePosition(user2.address, dai.address, btc.address, toUsd(60), false) + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(300)) + expect(await vault.globalShortAveragePrices(btc.address)).eq("41311475409836065573770491803278614") + + globalDelta = await vault.getGlobalShortDelta(btc.address) + expect(await globalDelta[0]).eq(true) + expect(await globalDelta[1]).eq("2261904761904761904761904761904") + expect(await glpManager.getAumInUsdg(true)).eq("500038095238095238095") // 500.038095238095238095 + expect(await glpManager.getAumInUsdg(false)).eq("492776190476190476190") // 492.77619047619047619 + }) +}) diff --git a/test/core/Vault/liquidateLongPosition.js b/test/core/Vault/liquidateLongPosition.js new file mode 100644 index 00000000..c8eac449 --- /dev/null +++ b/test/core/Vault/liquidateLongPosition.js @@ -0,0 +1,371 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed, newWallet } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("./helpers") + +use(solidity) + +describe("Vault.liquidateLongPosition", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let eth + let ethPriceFeed + let dai + let daiPriceFeed + let busd + let busdPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + busd = await deployContract("Token", []) + busdPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("liquidate long", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).liquidatePosition(user0.address, btc.address, btc.address, true, user2.address)) + .to.be.revertedWith("Vault: empty position") + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43500)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43500)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43500)) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("5487804878048780487804878048780") // ~5.48 + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("4390243902439024390243902439024") // ~4.39 + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(0) + + await expect(vault.liquidatePosition(user0.address, btc.address, btc.address, true, user2.address)) + .to.be.revertedWith("Vault: position cannot be liquidated") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(38700)) + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("5048780487804878048780487804878") // ~5.04 + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(1) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + expect(await vault.inPrivateLiquidationMode()).eq(false) + await vault.setInPrivateLiquidationMode(true) + expect(await vault.inPrivateLiquidationMode()).eq(true) + + await expect(vault.connect(user1).liquidatePosition(user0.address, btc.address, btc.address, true, user2.address)) + .to.be.revertedWith("Vault: invalid liquidator") + + expect(await vault.isLiquidator(user1.address)).eq(false) + await vault.setLiquidator(user1.address, true) + expect(await vault.isLiquidator(user1.address)).eq(true) + + const tx = await vault.connect(user1).liquidatePosition(user0.address, btc.address, btc.address, true, user2.address) + await reportGasUsed(provider, tx, "liquidatePosition gas used") + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + + expect(await vault.feeReserves(btc.address)).eq(1175) + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(262756 - 219 - 206) + expect(await btc.balanceOf(user2.address)).eq(11494) // 0.00011494 * 43500 => ~5 + + expect(await btc.balanceOf(vault.address)).eq(263506) + + const balance = await btc.balanceOf(vault.address) + const poolAmount = await vault.poolAmounts(btc.address) + const feeReserve = await vault.feeReserves(btc.address) + expect(poolAmount.add(feeReserve).sub(balance)).eq(0) + + await vault.withdrawFees(btc.address, user0.address) + + await btc.mint(vault.address, 1000) + await vault.buyUSDG(btc.address, user1.address) + }) + + it("automatic stop-loss", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).liquidatePosition(user0.address, btc.address, btc.address, true, user2.address)) + .to.be.revertedWith("Vault: empty position") + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 5000000) // 0.05 BTC => 2000 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(1000), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(1000)) // size + expect(position[1]).eq(toUsd(99)) // collateral, 100 - 1000 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq("2500000") // reserveAmount, 0.025 * 40,000 => 1000 + + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43500)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43500)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43500)) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(true) + expect(delta[1]).eq("60975609756097560975609756097560") // ~60.9756097561 + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("48780487804878048780487804878048") // ~48.7804878049 + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(0) + + await expect(vault.liquidatePosition(user0.address, btc.address, btc.address, true, user2.address)) + .to.be.revertedWith("Vault: position cannot be liquidated") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(37760)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(37760)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(37760)) + + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("79024390243902439024390243902439") // ~79.0243902439 + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(2) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(1000)) // size + expect(position[1]).eq(toUsd(99)) // collateral, 100 - 1000 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq("2500000") // reserveAmount, 0.025 * 40,000 => 1000 + + expect(await vault.feeReserves(btc.address)).eq("17439") + expect(await vault.reservedAmounts(btc.address)).eq("2500000") + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(901)) + expect(await vault.poolAmounts(btc.address)).eq(5000000 + 250000 - 17439) + expect(await btc.balanceOf(wallet.address)).eq(0) + expect(await btc.balanceOf(user0.address)).eq(0) + expect(await btc.balanceOf(user1.address)).eq("194750000") + expect(await btc.balanceOf(user2.address)).eq(0) + + const tx = await vault.liquidatePosition(user0.address, btc.address, btc.address, true, user2.address) + await reportGasUsed(provider, tx, "liquidatePosition gas used") + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + + expect(await vault.feeReserves(btc.address)).eq(17439 + 2648) + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(5000000 + 250000 - 17439 - 2648 - 50253) + expect(await btc.balanceOf(wallet.address)).eq(0) + expect(await btc.balanceOf(user0.address)).eq("50253") // 50253 / (10**8) * 37760 => 18.9755328 + expect(await btc.balanceOf(user1.address)).eq("194750000") + expect(await btc.balanceOf(user2.address)).eq(0) + + expect(await btc.balanceOf(vault.address)).eq(5000000 + 250000 - 50253) + + const balance = await btc.balanceOf(vault.address) + const poolAmount = await vault.poolAmounts(btc.address) + const feeReserve = await vault.feeReserves(btc.address) + expect(poolAmount.add(feeReserve).sub(balance)).eq(0) + + await vault.withdrawFees(btc.address, user0.address) + + await btc.mint(vault.address, 1000) + await vault.buyUSDG(btc.address, user1.address) + }) + + it("excludes AMM price", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(600)) + await busdPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + + const bnbBusd = await deployContract("PancakePair", []) + await bnbBusd.setReserves(expandDecimals(1000, 18), expandDecimals(1000 * 1000, 18)) + + const ethBnb = await deployContract("PancakePair", []) + await ethBnb.setReserves(expandDecimals(800, 18), expandDecimals(100, 18)) + + const btcBnb = await deployContract("PancakePair", []) + await btcBnb.setReserves(expandDecimals(25, 18), expandDecimals(1000, 18)) + + await vaultPriceFeed.setTokens(btc.address, eth.address, bnb.address) + await vaultPriceFeed.setPairs(bnbBusd.address, ethBnb.address, btcBnb.address) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43500)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43500)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43500)) + + let delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("2195121951219512195121951219512") // ~2.195 + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(0) + + await btcBnb.setReserves(expandDecimals(26, 18), expandDecimals(1000, 18)) + delta = await vault.getPositionDelta(user0.address, btc.address, btc.address, true) + expect(delta[0]).eq(false) + expect(delta[1]).eq("5572232645403377110694183864916") // ~5.572 + expect((await vault.validateLiquidation(user0.address, btc.address, btc.address, true, false))[0]).eq(1) + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + await expect(vault.liquidatePosition(user0.address, btc.address, btc.address, true, user2.address)) + .to.be.revertedWith("Vault: position cannot be liquidated") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(38700)) + + const tx = await vault.liquidatePosition(user0.address, btc.address, btc.address, true, user2.address) + await reportGasUsed(provider, tx, "liquidatePosition gas used") + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + + expect(await vault.feeReserves(btc.address)).eq(1175) + expect(await vault.reservedAmounts(btc.address)).eq(0) + expect(await vault.guaranteedUsd(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(262756 - 219 - 206) + expect(await btc.balanceOf(user2.address)).eq(11494) // 0.00011494 * 43500 => ~5 + }) +}) diff --git a/test/core/Vault/liquidateShortPosition.js b/test/core/Vault/liquidateShortPosition.js new file mode 100644 index 00000000..affce4e6 --- /dev/null +++ b/test/core/Vault/liquidateShortPosition.js @@ -0,0 +1,471 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig, validateVaultBalance } = require("./helpers") + +use(solidity) + +describe("Vault.liquidateShortPosition", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let glpManager + let vaultPriceFeed + let glp + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + glp = await deployContract("GLP", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 24 * 60 * 60]) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("liquidate short", async () => { + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).liquidatePosition(user0.address, dai.address, btc.address, false, user2.address)) + .to.be.revertedWith("Vault: empty position") + + expect(await vault.globalShortSizes(btc.address)).eq(0) + expect(await vault.globalShortAveragePrices(btc.address)).eq(0) + expect(await glpManager.getAumInUsdg(true)).eq(0) + + await dai.mint(user0.address, expandDecimals(1000, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await dai.connect(user0).transfer(vault.address, expandDecimals(10, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(90), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) // reserveAmount + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(90)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + expect(await glpManager.getAumInUsdg(false)).eq("99960000000000000000") // 99.96 + + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(2.25)) // 1000 / 40,000 * 90 + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(2.25)) + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(0) + + await expect(vault.liquidatePosition(user0.address, dai.address, btc.address, false, user2.address)) + .to.be.revertedWith("Vault: position cannot be liquidated") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(42500)) + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq("5625000000000000000000000000000") // 2500 / 40,000 * 90 => 5.625 + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(1) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(90, 18)) // reserveAmount + + expect(await vault.feeReserves(dai.address)).eq("130000000000000000") // 0.13 + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(90, 18)) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("99960000000000000000") + expect(await dai.balanceOf(user2.address)).eq(0) + + const tx = await vault.liquidatePosition(user0.address, dai.address, btc.address, false, user2.address) + await reportGasUsed(provider, tx, "liquidatePosition gas used") + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + + expect(await vault.feeReserves(dai.address)).eq("220000000000000000") // 0.22 + expect(await vault.reservedAmounts(dai.address)).eq(0) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("104780000000000000000") // 104.78 + expect(await dai.balanceOf(user2.address)).eq(expandDecimals(5, 18)) + + expect(await vault.globalShortSizes(btc.address)).eq(0) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + expect(await glpManager.getAumInUsdg(true)).eq("104780000000000000000") // 104.78 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + await dai.connect(user0).transfer(vault.address, expandDecimals(20, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(100), false) + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(100)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(50000)) + expect(await glpManager.getAumInUsdg(true)).eq("104780000000000000000") // 104.78 + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + await validateVaultBalance(expect, vault, dai, position[1].mul(expandDecimals(10, 18)).div(expandDecimals(10, 30))) + }) + + it("automatic stop-loss", async () => { + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).liquidatePosition(user0.address, dai.address, btc.address, false, user2.address)) + .to.be.revertedWith("Vault: empty position") + + expect(await vault.globalShortSizes(btc.address)).eq(0) + expect(await vault.globalShortAveragePrices(btc.address)).eq(0) + expect(await glpManager.getAumInUsdg(true)).eq(0) + + await dai.mint(user0.address, expandDecimals(1001, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(1001, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await dai.mint(user0.address, expandDecimals(100, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(1000), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(1000)) // size + expect(position[1]).eq(toUsd(99)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(1000, 18)) // reserveAmount + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(1000)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + expect(await glpManager.getAumInUsdg(false)).eq("1000599600000000000000") // 1000.5996 + + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(25)) // 1000 / 40,000 * 1000 + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(25)) + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(0) + + await expect(vault.liquidatePosition(user0.address, dai.address, btc.address, false, user2.address)) + .to.be.revertedWith("Vault: position cannot be liquidated") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45000)) + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(125)) // 5000 / 40,000 * 1000 => 125 + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(1) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43600)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43600)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(43600)) + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(90)) // 3600 / 40,000 * 1000 => 90 + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(2) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(1000)) // size + expect(position[1]).eq(toUsd(99)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(1000, 18)) // reserveAmount + + expect(await vault.feeReserves(dai.address)).eq("1400400000000000000") // 1.4004 + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(1000, 18)) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("1000599600000000000000") // 1000.5996 + expect(await dai.balanceOf(wallet.address)).eq(0) + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await dai.balanceOf(user1.address)).eq(0) + expect(await dai.balanceOf(user2.address)).eq(0) + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(1000)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + expect(await glpManager.getAumInUsdg(true)).eq("1090599600000000000000") // 1090.5996 + + const tx = await vault.liquidatePosition(user0.address, dai.address, btc.address, false, user2.address) + await reportGasUsed(provider, tx, "liquidatePosition gas used") + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + + expect(await vault.feeReserves(dai.address)).eq("2400400000000000000") // 2.4004 + expect(await vault.reservedAmounts(dai.address)).eq(0) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("1090599600000000000000") // 1090.5996 + expect(await dai.balanceOf(wallet.address)).eq(0) + expect(await dai.balanceOf(user0.address)).eq(expandDecimals(8, 18)) + expect(await dai.balanceOf(user1.address)).eq(0) + expect(await dai.balanceOf(user2.address)).eq(0) + + expect(await vault.globalShortSizes(btc.address)).eq(0) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + expect(await glpManager.getAumInUsdg(true)).eq("1090599600000000000000") // 1090.5996 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + await dai.mint(user0.address, expandDecimals(20, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(20, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(100), false) + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(100)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(50000)) + expect(await glpManager.getAumInUsdg(true)).eq("1090599600000000000000") // 1090.5996 + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + await validateVaultBalance(expect, vault, dai, position[1].mul(expandDecimals(10, 18)).div(expandDecimals(10, 30))) + }) + + it("global AUM", async () => { + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await expect(vault.connect(user0).liquidatePosition(user0.address, dai.address, btc.address, false, user2.address)) + .to.be.revertedWith("Vault: empty position") + + expect(await vault.globalShortSizes(btc.address)).eq(0) + expect(await vault.globalShortAveragePrices(btc.address)).eq(0) + expect(await glpManager.getAumInUsdg(true)).eq(0) + + await dai.mint(user0.address, expandDecimals(1001, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(1001, 18)) + await vault.buyUSDG(dai.address, user1.address) + + await dai.mint(user0.address, expandDecimals(100, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(1000), false) + + let position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(1000)) // size + expect(position[1]).eq(toUsd(99)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(1000, 18)) // reserveAmount + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(1000)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + expect(await glpManager.getAumInUsdg(false)).eq("1000599600000000000000") // 1000.5996 + + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(39000)) + + let delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(true) + expect(delta[1]).eq(toUsd(25)) // 1000 / 40,000 * 1000 + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(25)) + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(0) + + await expect(vault.liquidatePosition(user0.address, dai.address, btc.address, false, user2.address)) + .to.be.revertedWith("Vault: position cannot be liquidated") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45000)) + delta = await vault.getPositionDelta(user0.address, dai.address, btc.address, false) + expect(delta[0]).eq(false) + expect(delta[1]).eq(toUsd(125)) // 5000 / 40,000 * 1000 => 125 + expect((await vault.validateLiquidation(user0.address, dai.address, btc.address, false, false))[0]).eq(1) + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(toUsd(1000)) // size + expect(position[1]).eq(toUsd(99)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(40000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(expandDecimals(1000, 18)) // reserveAmount + + expect(await vault.feeReserves(dai.address)).eq("1400400000000000000") // 1.4004 + expect(await vault.reservedAmounts(dai.address)).eq(expandDecimals(1000, 18)) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("1000599600000000000000") // 1000.5996 + expect(await dai.balanceOf(wallet.address)).eq(0) + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await dai.balanceOf(user1.address)).eq(0) + expect(await dai.balanceOf(user2.address)).eq(0) + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(1000)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + expect(await glpManager.getAumInUsdg(true)).eq("1125599600000000000000") // 1125.5996 + + const tx = await vault.liquidatePosition(user0.address, dai.address, btc.address, false, user2.address) + await reportGasUsed(provider, tx, "liquidatePosition gas used") + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + expect(position[0]).eq(0) // size + expect(position[1]).eq(0) // collateral + expect(position[2]).eq(0) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(0) // reserveAmount + + expect(await vault.feeReserves(dai.address)).eq("2400400000000000000") // 2.4004 + expect(await vault.reservedAmounts(dai.address)).eq(0) + expect(await vault.guaranteedUsd(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq("1093599600000000000000") // 1093.5996 + expect(await dai.balanceOf(wallet.address)).eq(0) + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await dai.balanceOf(user1.address)).eq(0) + expect(await dai.balanceOf(user2.address)).eq(expandDecimals(5, 18)) + + expect(await vault.globalShortSizes(btc.address)).eq(0) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(40000)) + expect(await glpManager.getAumInUsdg(true)).eq("1093599600000000000000") // 1093.5996 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + await dai.mint(user0.address, expandDecimals(20, 18)) + await dai.connect(user0).transfer(vault.address, expandDecimals(20, 18)) + await vault.connect(user0).increasePosition(user0.address, dai.address, btc.address, toUsd(100), false) + + expect(await vault.globalShortSizes(btc.address)).eq(toUsd(100)) + expect(await vault.globalShortAveragePrices(btc.address)).eq(toNormalizedPrice(50000)) + expect(await glpManager.getAumInUsdg(true)).eq("1093599600000000000000") // 1093.5996 + + position = await vault.getPosition(user0.address, dai.address, btc.address, false) + await validateVaultBalance(expect, vault, dai, position[1].mul(expandDecimals(10, 18)).div(expandDecimals(10, 30))) + }) +}) diff --git a/test/core/Vault/sellUSDG.js b/test/core/Vault/sellUSDG.js new file mode 100644 index 00000000..e92d8a2d --- /dev/null +++ b/test/core/Vault/sellUSDG.js @@ -0,0 +1,277 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("./helpers") + +use(solidity) + +describe("Vault.sellUSDG", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + let glpManager + let glp + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + glp = await deployContract("GLP", []) + glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 24 * 60 * 60]) + }) + + it("sellUSDG", async () => { + await expect(vault.connect(user0).sellUSDG(bnb.address, user1.address)) + .to.be.revertedWith("Vault: _token not whitelisted") + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnb.mint(user0.address, 100) + + expect(await glpManager.getAumInUsdg(true)).eq(0) + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + expect(await bnb.balanceOf(user0.address)).eq(100) + await bnb.connect(user0).transfer(vault.address, 100) + await vault.connect(user0).buyUSDG(bnb.address, user0.address) + expect(await usdg.balanceOf(user0.address)).eq(29700) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(1) + expect(await vault.usdgAmounts(bnb.address)).eq(29700) + expect(await vault.poolAmounts(bnb.address)).eq(100 - 1) + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await glpManager.getAumInUsdg(true)).eq(29700) + + await expect(vault.connect(user0).sellUSDG(bnb.address, user1.address)) + .to.be.revertedWith("Vault: invalid usdgAmount") + + await usdg.connect(user0).transfer(vault.address, 15000) + + await expect(vault.connect(user0).sellUSDG(btc.address, user1.address)) + .to.be.revertedWith("Vault: invalid redemptionAmount") + + await vault.setInManagerMode(true) + await expect(vault.connect(user0).sellUSDG(bnb.address, user1.address)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setManager(user0.address, true) + + const tx = await vault.connect(user0).sellUSDG(bnb.address, user1.address, { gasPrice: "10000000000" } ) + await reportGasUsed(provider, tx, "sellUSDG gas used") + expect(await usdg.balanceOf(user0.address)).eq(29700 - 15000) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(2) + expect(await vault.usdgAmounts(bnb.address)).eq(29700 - 15000) + expect(await vault.poolAmounts(bnb.address)).eq(100 - 1 - 50) + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await bnb.balanceOf(user1.address)).eq(50 - 1) // (15000 / 300) => 50 + expect(await glpManager.getAumInUsdg(true)).eq(29700 - 15000) + }) + + it("sellUSDG after a price increase", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await bnb.mint(user0.address, 100) + + expect(await glpManager.getAumInUsdg(true)).eq(0) + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + expect(await bnb.balanceOf(user0.address)).eq(100) + await bnb.connect(user0).transfer(vault.address, 100) + await vault.connect(user0).buyUSDG(bnb.address, user0.address) + + expect(await usdg.balanceOf(user0.address)).eq(29700) + expect(await usdg.balanceOf(user1.address)).eq(0) + + expect(await vault.feeReserves(bnb.address)).eq(1) + expect(await vault.usdgAmounts(bnb.address)).eq(29700) + expect(await vault.poolAmounts(bnb.address)).eq(100 - 1) + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await glpManager.getAumInUsdg(true)).eq(29700) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(400)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(600)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(500)) + + expect(await glpManager.getAumInUsdg(false)).eq(39600) + + await usdg.connect(user0).transfer(vault.address, 15000) + await vault.connect(user0).sellUSDG(bnb.address, user1.address) + + expect(await usdg.balanceOf(user0.address)).eq(29700 - 15000) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(2) + expect(await vault.usdgAmounts(bnb.address)).eq(29700 - 15000) + expect(await vault.poolAmounts(bnb.address)).eq(100 - 1 - 25) + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await bnb.balanceOf(user1.address)).eq(25 - 1) // (15000 / 600) => 25 + expect(await glpManager.getAumInUsdg(false)).eq(29600) + }) + + it("sellUSDG redeem based on price", async () => { + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btc.mint(user0.address, expandDecimals(2, 8)) + + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(btc.address)).eq(0) + expect(await vault.usdgAmounts(btc.address)).eq(0) + expect(await vault.poolAmounts(btc.address)).eq(0) + expect(await btc.balanceOf(user0.address)).eq(expandDecimals(2, 8)) + + expect(await glpManager.getAumInUsdg(true)).eq(0) + await btc.connect(user0).transfer(vault.address, expandDecimals(2, 8)) + await vault.connect(user0).buyUSDG(btc.address, user0.address) + expect(await glpManager.getAumInUsdg(true)).eq("119640000000000000000000") // 119,640 + + expect(await usdg.balanceOf(user0.address)).eq("119640000000000000000000") // 119,640 + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(btc.address)).eq("600000") // 0.006 BTC, 2 * 0.03% + expect(await vault.usdgAmounts(btc.address)).eq("119640000000000000000000") // 119,640 + expect(await vault.poolAmounts(btc.address)).eq("199400000") // 1.994 BTC + expect(await btc.balanceOf(user0.address)).eq(0) + expect(await btc.balanceOf(user1.address)).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(82000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(80000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(83000)) + + expect(await glpManager.getAumInUsdg(false)).eq(expandDecimals(159520, 18)) // 199400000 / (10 ** 8) * 80,000 + await usdg.connect(user0).transfer(vault.address, expandDecimals(10000, 18)) + await vault.connect(user0).sellUSDG(btc.address, user1.address) + + expect(await btc.balanceOf(user1.address)).eq("12012047") // 0.12012047 BTC, 0.12012047 * 83000 => 9969.999 + expect(await vault.feeReserves(btc.address)).eq("636145") // 0.00636145 + expect(await vault.poolAmounts(btc.address)).eq("187351808") // 199400000-(636145-600000)-12012047 => 187351808 + expect(await glpManager.getAumInUsdg(false)).eq("149881446400000000000000") // 149881.4464, 187351808 / (10 ** 8) * 80,000 + }) + + it("sellUSDG for stableTokens", async () => { + await vault.setFees( + 50, // _taxBasisPoints + 10, // _stableTaxBasisPoints + 4, // _mintBurnFeeBasisPoints + 30, // _swapFeeBasisPoints + 4, // _stableSwapFeeBasisPoints + 10, // _marginFeeBasisPoints + toUsd(5), // _liquidationFeeUsd + 0, // _minProfitTime + false // _hasDynamicFees + ) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await dai.mint(user0.address, expandDecimals(10000, 18)) + + expect(await usdg.balanceOf(user0.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(dai.address)).eq(0) + expect(await vault.usdgAmounts(dai.address)).eq(0) + expect(await vault.poolAmounts(dai.address)).eq(0) + expect(await dai.balanceOf(user0.address)).eq(expandDecimals(10000, 18)) + expect(await glpManager.getAumInUsdg(true)).eq(0) + + await dai.connect(user0).transfer(vault.address, expandDecimals(10000, 18)) + await vault.connect(user0).buyUSDG(dai.address, user0.address) + + expect(await glpManager.getAumInUsdg(true)).eq(expandDecimals(9996, 18)) + expect(await usdg.balanceOf(user0.address)).eq(expandDecimals(9996, 18)) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(dai.address)).eq(expandDecimals(4, 18)) + expect(await vault.usdgAmounts(dai.address)).eq(expandDecimals(9996, 18)) + expect(await vault.poolAmounts(dai.address)).eq(expandDecimals(9996, 18)) + expect(await dai.balanceOf(user0.address)).eq(0) + expect(await dai.balanceOf(user1.address)).eq(0) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(5000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btc.mint(user0.address, expandDecimals(1, 8)) + + expect(await dai.balanceOf(user2.address)).eq(0) + + await btc.connect(user0).transfer(vault.address, expandDecimals(1, 8)) + await vault.connect(user0).swap(btc.address, dai.address, user2.address) + + expect(await glpManager.getAumInUsdg(true)).eq(expandDecimals(9996, 18)) + + expect(await vault.feeReserves(dai.address)).eq(expandDecimals(19, 18)) + expect(await vault.usdgAmounts(dai.address)).eq(expandDecimals(4996, 18)) + expect(await vault.poolAmounts(dai.address)).eq(expandDecimals(4996, 18)) + + expect(await vault.feeReserves(btc.address)).eq(0) + expect(await vault.usdgAmounts(btc.address)).eq(expandDecimals(5000, 18)) + expect(await vault.poolAmounts(btc.address)).eq(expandDecimals(1, 8)) + + expect(await dai.balanceOf(user2.address)).eq(expandDecimals(4985, 18)) + + await usdg.connect(user0).approve(router.address, expandDecimals(5000, 18)) + await expect(router.connect(user0).swap([usdg.address, dai.address], expandDecimals(5000, 18), 0, user3.address)) + .to.be.revertedWith("Vault: poolAmount exceeded") + + expect(await dai.balanceOf(user3.address)).eq(0) + await router.connect(user0).swap([usdg.address, dai.address], expandDecimals(4000, 18), 0, user3.address) + expect(await dai.balanceOf(user3.address)).eq("3998400000000000000000") // 3998.4 + + expect(await vault.feeReserves(dai.address)).eq("20600000000000000000") // 20.6 + expect(await vault.usdgAmounts(dai.address)).eq(expandDecimals(996, 18)) + expect(await vault.poolAmounts(dai.address)).eq(expandDecimals(996, 18)) + + expect(await glpManager.getAumInUsdg(true)).eq(expandDecimals(5996, 18)) + }) +}) diff --git a/test/core/Vault/settings.js b/test/core/Vault/settings.js new file mode 100644 index 00000000..f80ff22f --- /dev/null +++ b/test/core/Vault/settings.js @@ -0,0 +1,503 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig } = require("./helpers") + +use(solidity) + +describe("Vault.settings", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("inits", async () => { + expect(await usdg.gov()).eq(wallet.address) + expect(await usdg.vaults(vault.address)).eq(true) + expect(await usdg.vaults(user0.address)).eq(false) + + expect(await vault.gov()).eq(wallet.address) + expect(await vault.isInitialized()).eq(true) + expect(await vault.router()).eq(router.address) + expect(await vault.usdg()).eq(usdg.address) + expect(await vault.priceFeed()).eq(vaultPriceFeed.address) + expect(await vault.liquidationFeeUsd()).eq(toUsd(5)) + expect(await vault.fundingRateFactor()).eq(600) + expect(await vault.stableFundingRateFactor()).eq(600) + }) + + it("setInManagerMode", async () => { + await expect(vault.connect(user0).setInManagerMode(true)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.inManagerMode()).eq(false) + await vault.connect(user0).setInManagerMode(true) + expect(await vault.inManagerMode()).eq(true) + }) + + it("setManager", async () => { + await expect(vault.connect(user0).setManager(user1.address, true)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.isManager(user1.address)).eq(false) + await vault.connect(user0).setManager(user1.address, true) + expect(await vault.isManager(user1.address)).eq(true) + }) + + it("setInPrivateLiquidationMode", async () => { + await expect(vault.connect(user0).setInPrivateLiquidationMode(true)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.inPrivateLiquidationMode()).eq(false) + await vault.connect(user0).setInPrivateLiquidationMode(true) + expect(await vault.inPrivateLiquidationMode()).eq(true) + }) + + it("setIsSwapEnabled", async () => { + await expect(vault.connect(user0).setIsSwapEnabled(false)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.isSwapEnabled()).eq(true) + await vault.connect(user0).setIsSwapEnabled(false) + expect(await vault.isSwapEnabled()).eq(false) + }) + + it("setIsLeverageEnabled", async () => { + await expect(vault.connect(user0).setIsLeverageEnabled(false)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.isLeverageEnabled()).eq(true) + await vault.connect(user0).setIsLeverageEnabled(false) + expect(await vault.isLeverageEnabled()).eq(false) + }) + + it("setMaxGasPrice", async () => { + await expect(vault.connect(user0).setMaxGasPrice(20)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.maxGasPrice()).eq(0) + await vault.connect(user0).setMaxGasPrice(20) + expect(await vault.maxGasPrice()).eq(20) + }) + + it("setGov", async () => { + await expect(vault.connect(user0).setGov(user1.address)) + .to.be.revertedWith("Vault: forbidden") + + expect(await vault.gov()).eq(wallet.address) + + await vault.setGov(user0.address) + expect(await vault.gov()).eq(user0.address) + + await vault.connect(user0).setGov(user1.address) + expect(await vault.gov()).eq(user1.address) + }) + + it("setPriceFeed", async () => { + await expect(vault.connect(user0).setPriceFeed(user1.address)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.priceFeed()).eq(vaultPriceFeed.address) + await vault.connect(user0).setPriceFeed(user1.address) + expect(await vault.priceFeed()).eq(user1.address) + }) + + it("setMaxLeverage", async () => { + await expect(vault.connect(user0).setMaxLeverage(10000)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + await expect(vault.connect(user0).setMaxLeverage(10000)) + .to.be.revertedWith("Vault: invalid _maxLeverage") + + expect(await vault.maxLeverage()).eq(50 * 10000) + await vault.connect(user0).setMaxLeverage(10001) + expect(await vault.maxLeverage()).eq(10001) + }) + + it("setBufferAmount", async () => { + await expect(vault.connect(user0).setBufferAmount(bnb.address, 700)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.bufferAmounts(bnb.address)).eq(0) + await vault.connect(user0).setBufferAmount(bnb.address, 700) + expect(await vault.bufferAmounts(bnb.address)).eq(700) + }) + + it("setFees", async () => { + await expect(vault.connect(user0).setFees( + 90, // _taxBasisPoints + 91, // _stableTaxBasisPoints + 92, // _mintBurnFeeBasisPoints + 93, // _swapFeeBasisPoints + 94, // _stableSwapFeeBasisPoints + 95, // _marginFeeBasisPoints + toUsd(8), // _liquidationFeeUsd + 96, // _minProfitTime + true // _hasDynamicFees + )).to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.taxBasisPoints()).eq(50) + expect(await vault.stableTaxBasisPoints()).eq(20) + expect(await vault.mintBurnFeeBasisPoints()).eq(30) + expect(await vault.swapFeeBasisPoints()).eq(30) + expect(await vault.stableSwapFeeBasisPoints()).eq(4) + expect(await vault.marginFeeBasisPoints()).eq(10) + expect(await vault.liquidationFeeUsd()).eq(toUsd(5)) + expect(await vault.minProfitTime()).eq(0) + expect(await vault.hasDynamicFees()).eq(false) + await vault.connect(user0).setFees( + 90, // _taxBasisPoints + 91, // _stableTaxBasisPoints + 92, // _mintBurnFeeBasisPoints + 93, // _swapFeeBasisPoints + 94, // _stableSwapFeeBasisPoints + 95, // _marginFeeBasisPoints + toUsd(8), // _liquidationFeeUsd + 96, // _minProfitTime + true // _hasDynamicFees + ) + expect(await vault.taxBasisPoints()).eq(90) + expect(await vault.stableTaxBasisPoints()).eq(91) + expect(await vault.mintBurnFeeBasisPoints()).eq(92) + expect(await vault.swapFeeBasisPoints()).eq(93) + expect(await vault.stableSwapFeeBasisPoints()).eq(94) + expect(await vault.marginFeeBasisPoints()).eq(95) + expect(await vault.liquidationFeeUsd()).eq(toUsd(8)) + expect(await vault.minProfitTime()).eq(96) + expect(await vault.hasDynamicFees()).eq(true) + }) + + it("setFundingRate", async () => { + await expect(vault.connect(user0).setFundingRate(59 * 60, 10001, 10001)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + await expect(vault.connect(user0).setFundingRate(59 * 60, 10001, 10001)) + .to.be.revertedWith("Vault: invalid _fundingInterval") + + await expect(vault.connect(user0).setFundingRate(60 * 60, 10001, 10001)) + .to.be.revertedWith("Vault: invalid _fundingRateFactor") + + await expect(vault.connect(user0).setFundingRate(60 * 60, 10000, 10001)) + .to.be.revertedWith("Vault: invalid _stableFundingRateFactor") + + expect(await vault.fundingInterval()).eq(8 * 60 * 60) + expect(await vault.fundingRateFactor()).eq(600) + expect(await vault.stableFundingRateFactor()).eq(600) + await vault.connect(user0).setFundingRate(60 * 60, 10000, 10000) + expect(await vault.fundingInterval()).eq(60 * 60) + expect(await vault.fundingRateFactor()).eq(10000) + expect(await vault.stableFundingRateFactor()).eq(10000) + + await vault.connect(user0).setFundingRate(120 * 60, 1000,2000) + expect(await vault.fundingInterval()).eq(120 * 60) + expect(await vault.fundingRateFactor()).eq(1000) + expect(await vault.stableFundingRateFactor()).eq(2000) + }) + + it("setTokenConfig", async () => { + const params = [ + bnb.address, // _token + 18, // _tokenDecimals + 10000, // _tokenWeight + 75, // _minProfitBps + 0, // _maxUsdgAmount + true, // _isStable + true // _isShortable + ] + + await expect(vault.connect(user0).setTokenConfig(...params)) + .to.be.revertedWith("Vault: forbidden") + + await expect(vault.setTokenConfig(...params)) + .to.be.revertedWith("VaultPriceFeed: could not fetch price") + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + + expect(await vault.whitelistedTokenCount()).eq(0) + expect(await vault.whitelistedTokens(bnb.address)).eq(false) + expect(await vault.tokenDecimals(bnb.address)).eq(0) + expect(await vault.tokenWeights(bnb.address)).eq(0) + expect(await vault.totalTokenWeights()).eq(0) + expect(await vault.minProfitBasisPoints(bnb.address)).eq(0) + expect(await vault.maxUsdgAmounts(bnb.address)).eq(0) + expect(await vault.stableTokens(bnb.address)).eq(false) + expect(await vault.shortableTokens(bnb.address)).eq(false) + expect(await vault.allWhitelistedTokensLength()).eq(0) + + await vault.setTokenConfig(...params) + + expect(await vault.whitelistedTokenCount()).eq(1) + expect(await vault.whitelistedTokens(bnb.address)).eq(true) + expect(await vault.tokenDecimals(bnb.address)).eq(18) + expect(await vault.tokenWeights(bnb.address)).eq(10000) + expect(await vault.totalTokenWeights()).eq(10000) + expect(await vault.minProfitBasisPoints(bnb.address)).eq(75) + expect(await vault.maxUsdgAmounts(bnb.address)).eq(0) + expect(await vault.stableTokens(bnb.address)).eq(true) + expect(await vault.shortableTokens(bnb.address)).eq(true) + expect(await vault.allWhitelistedTokensLength()).eq(1) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + + await vault.setTokenConfig( + dai.address, // _token + 2, // _tokenDecimals + 5000, // _tokenWeight + 50, // _minProfitBps + 1000, // _maxUsdgAmount + false, // _isStable + false // _isShortable + ) + + expect(await vault.whitelistedTokenCount()).eq(2) + expect(await vault.whitelistedTokens(dai.address)).eq(true) + expect(await vault.tokenDecimals(dai.address)).eq(2) + expect(await vault.tokenWeights(dai.address)).eq(5000) + expect(await vault.totalTokenWeights()).eq(15000) + expect(await vault.minProfitBasisPoints(dai.address)).eq(50) + expect(await vault.maxUsdgAmounts(dai.address)).eq(1000) + expect(await vault.stableTokens(dai.address)).eq(false) + expect(await vault.shortableTokens(dai.address)).eq(false) + expect(await vault.allWhitelistedTokensLength()).eq(2) + + await vault.setTokenConfig( + dai.address, // _token + 20, // _tokenDecimals + 7000, // _tokenWeight + 10, // _minProfitBps + 500, // _maxUsdgAmount + true, // _isStable + false // _isShortable + ) + + expect(await vault.whitelistedTokenCount()).eq(2) + expect(await vault.whitelistedTokens(dai.address)).eq(true) + expect(await vault.tokenDecimals(dai.address)).eq(20) + expect(await vault.tokenWeights(dai.address)).eq(7000) + expect(await vault.totalTokenWeights()).eq(17000) + expect(await vault.minProfitBasisPoints(dai.address)).eq(10) + expect(await vault.maxUsdgAmounts(dai.address)).eq(500) + expect(await vault.stableTokens(dai.address)).eq(true) + expect(await vault.shortableTokens(dai.address)).eq(false) + expect(await vault.allWhitelistedTokensLength()).eq(2) + }) + + it("clearTokenConfig", async () => { + const params = [ + bnb.address, // _token + 18, // _tokenDecimals + 7000, // _tokenWeight + 75, // _minProfitBps + 500, // _maxUsdgAmount + true, // _isStable + true // _isShortable + ] + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + + expect(await vault.whitelistedTokenCount()).eq(0) + expect(await vault.whitelistedTokens(bnb.address)).eq(false) + expect(await vault.tokenDecimals(bnb.address)).eq(0) + expect(await vault.tokenWeights(bnb.address)).eq(0) + expect(await vault.totalTokenWeights()).eq(0) + expect(await vault.minProfitBasisPoints(bnb.address)).eq(0) + expect(await vault.maxUsdgAmounts(bnb.address)).eq(0) + expect(await vault.stableTokens(bnb.address)).eq(false) + expect(await vault.shortableTokens(bnb.address)).eq(false) + + await vault.setTokenConfig(...params) + + expect(await vault.whitelistedTokenCount()).eq(1) + expect(await vault.whitelistedTokens(bnb.address)).eq(true) + expect(await vault.tokenDecimals(bnb.address)).eq(18) + expect(await vault.tokenWeights(bnb.address)).eq(7000) + expect(await vault.totalTokenWeights()).eq(7000) + expect(await vault.minProfitBasisPoints(bnb.address)).eq(75) + expect(await vault.maxUsdgAmounts(bnb.address)).eq(500) + expect(await vault.stableTokens(bnb.address)).eq(true) + expect(await vault.shortableTokens(bnb.address)).eq(true) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig( + dai.address, // _token + 20, // _tokenDecimals + 5000, // _tokenWeight + 10, // _minProfitBps + 500, // _maxUsdgAmount + true, // _isStable + false // _isShortable + ) + + expect(await vault.whitelistedTokenCount()).eq(2) + expect(await vault.whitelistedTokens(bnb.address)).eq(true) + expect(await vault.tokenDecimals(bnb.address)).eq(18) + expect(await vault.tokenWeights(bnb.address)).eq(7000) + expect(await vault.totalTokenWeights()).eq(12000) + expect(await vault.minProfitBasisPoints(bnb.address)).eq(75) + expect(await vault.maxUsdgAmounts(bnb.address)).eq(500) + expect(await vault.stableTokens(bnb.address)).eq(true) + expect(await vault.shortableTokens(bnb.address)).eq(true) + + await expect(vault.connect(user0).clearTokenConfig(bnb.address)) + .to.be.revertedWith("Vault: forbidden") + + await vault.clearTokenConfig(bnb.address) + + expect(await vault.whitelistedTokenCount()).eq(1) + expect(await vault.whitelistedTokens(bnb.address)).eq(false) + expect(await vault.tokenDecimals(bnb.address)).eq(0) + expect(await vault.tokenWeights(bnb.address)).eq(0) + expect(await vault.totalTokenWeights()).eq(5000) + expect(await vault.minProfitBasisPoints(bnb.address)).eq(0) + expect(await vault.maxUsdgAmounts(bnb.address)).eq(0) + expect(await vault.stableTokens(bnb.address)).eq(false) + expect(await vault.shortableTokens(bnb.address)).eq(false) + + await expect(vault.clearTokenConfig(bnb.address)) + .to.be.revertedWith("Vault: token not whitelisted") + }) + + it("addRouter", async () => { + expect(await vault.approvedRouters(user0.address, user1.address)).eq(false) + await vault.connect(user0).addRouter(user1.address) + expect(await vault.approvedRouters(user0.address, user1.address)).eq(true) + }) + + it("removeRouter", async () => { + expect(await vault.approvedRouters(user0.address, user1.address)).eq(false) + await vault.connect(user0).addRouter(user1.address) + expect(await vault.approvedRouters(user0.address, user1.address)).eq(true) + await vault.connect(user0).removeRouter(user1.address) + expect(await vault.approvedRouters(user0.address, user1.address)).eq(false) + }) + + it("setUsdgAmount", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + await bnb.mint(user0.address, 100) + await bnb.connect(user0).transfer(vault.address, 100) + await vault.connect(user0).buyUSDG(bnb.address, user1.address) + + expect(await vault.usdgAmounts(bnb.address)).eq(29700) + + await expect(vault.connect(user0).setUsdgAmount(bnb.address, 50000)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await vault.usdgAmounts(bnb.address)).eq(29700) + await vault.connect(user0).setUsdgAmount(bnb.address, 50000) + expect(await vault.usdgAmounts(bnb.address)).eq(50000) + + await vault.connect(user0).setUsdgAmount(bnb.address, 10000) + expect(await vault.usdgAmounts(bnb.address)).eq(10000) + }) + + it("upgradeVault", async () => { + await bnb.mint(vault.address, 1000) + + await expect(vault.connect(user0).upgradeVault(user1.address, bnb.address, 1000)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + expect(await bnb.balanceOf(vault.address)).eq(1000) + expect(await bnb.balanceOf(user1.address)).eq(0) + await vault.connect(user0).upgradeVault(user1.address, bnb.address, 1000) + expect(await bnb.balanceOf(vault.address)).eq(0) + expect(await bnb.balanceOf(user1.address)).eq(1000) + }) + + it("setErrorController", async () => { + const vaultErrorController = await deployContract("VaultErrorController", []) + await expect(vaultErrorController.setErrors(vault.address, ["Example Error 1", "Example Error 2"])) + .to.be.revertedWith("Vault: invalid errorController") + + await expect(vault.connect(user0).setErrorController(vaultErrorController.address)) + .to.be.revertedWith("Vault: forbidden") + + await vault.setGov(user0.address) + + await vault.connect(user0).setErrorController(vaultErrorController.address) + expect(await vault.errorController()).eq(vaultErrorController.address) + + expect(await vault.errors(0)).eq("Vault: zero error") + expect(await vault.errors(1)).eq("Vault: already initialized") + expect(await vault.errors(2)).eq("Vault: invalid _maxLeverage") + + await expect(vaultErrorController.connect(user0).setErrors(vault.address, ["Example Error 1", "Example Error 2"])) + .to.be.revertedWith("Governable: forbidden") + + await vaultErrorController.setErrors(vault.address, ["Example Error 1", "Example Error 2"]) + + expect(await vault.errors(0)).eq("Example Error 1") + expect(await vault.errors(1)).eq("Example Error 2") + expect(await vault.errors(2)).eq("Vault: invalid _maxLeverage") + }) +}) diff --git a/test/core/Vault/swap.js b/test/core/Vault/swap.js new file mode 100644 index 00000000..22e88dad --- /dev/null +++ b/test/core/Vault/swap.js @@ -0,0 +1,275 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getEthConfig, getDaiConfig } = require("./helpers") + +use(solidity) + +describe("Vault.swap", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let eth + let ethPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + let glpManager + let glp + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(eth.address, ethPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + glp = await deployContract("GLP", []) + glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 24 * 60 * 60]) + }) + + it("swap", async () => { + await expect(vault.connect(user1).swap(bnb.address, btc.address, user2.address)) + .to.be.revertedWith("Vault: _tokenIn not whitelisted") + + await vault.setIsSwapEnabled(false) + + await expect(vault.connect(user1).swap(bnb.address, btc.address, user2.address)) + .to.be.revertedWith("Vault: swaps not enabled") + + await vault.setIsSwapEnabled(true) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await expect(vault.connect(user1).swap(bnb.address, btc.address, user2.address)) + .to.be.revertedWith("Vault: _tokenOut not whitelisted") + + await expect(vault.connect(user1).swap(bnb.address, bnb.address, user2.address)) + .to.be.revertedWith("Vault: invalid tokens") + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnb.mint(user0.address, expandDecimals(200, 18)) + await btc.mint(user0.address, expandDecimals(1, 8)) + + expect(await glpManager.getAumInUsdg(false)).eq(0) + + await bnb.connect(user0).transfer(vault.address, expandDecimals(200, 18)) + await vault.connect(user0).buyUSDG(bnb.address, user0.address) + + expect(await glpManager.getAumInUsdg(false)).eq(expandDecimals(59820, 18)) // 60,000 * 99.7% + + await btc.connect(user0).transfer(vault.address, expandDecimals(1, 8)) + await vault.connect(user0).buyUSDG(btc.address, user0.address) + + expect(await glpManager.getAumInUsdg(false)).eq(expandDecimals(119640, 18)) // 59,820 + (60,000 * 99.7%) + + expect(await usdg.balanceOf(user0.address)).eq(expandDecimals(120000, 18).sub(expandDecimals(360, 18))) // 120,000 * 0.3% => 360 + + expect(await vault.feeReserves(bnb.address)).eq("600000000000000000") // 200 * 0.3% => 0.6 + expect(await vault.usdgAmounts(bnb.address)).eq(expandDecimals(200 * 300, 18).sub(expandDecimals(180, 18))) // 60,000 * 0.3% => 180 + expect(await vault.poolAmounts(bnb.address)).eq(expandDecimals(200, 18).sub("600000000000000000")) + + expect(await vault.feeReserves(btc.address)).eq("300000") // 1 * 0.3% => 0.003 + expect(await vault.usdgAmounts(btc.address)).eq(expandDecimals(200 * 300, 18).sub(expandDecimals(180, 18))) + expect(await vault.poolAmounts(btc.address)).eq(expandDecimals(1, 8).sub("300000")) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(400)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(600)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(500)) + + expect(await glpManager.getAumInUsdg(false)).eq(expandDecimals(139580, 18)) // 59,820 / 300 * 400 + 59820 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(90000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(100000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(80000)) + + expect(await glpManager.getAumInUsdg(false)).eq(expandDecimals(159520, 18)) // 59,820 / 300 * 400 + 59820 / 60000 * 80000 + + await bnb.mint(user1.address, expandDecimals(100, 18)) + await bnb.connect(user1).transfer(vault.address, expandDecimals(100, 18)) + + expect(await btc.balanceOf(user1.address)).eq(0) + expect(await btc.balanceOf(user2.address)).eq(0) + const tx = await vault.connect(user1).swap(bnb.address, btc.address, user2.address) + await reportGasUsed(provider, tx, "swap gas used") + + expect(await glpManager.getAumInUsdg(false)).eq(expandDecimals(167520, 18)) // 159520 + (100 * 400) - 32000 + + expect(await btc.balanceOf(user1.address)).eq(0) + expect(await btc.balanceOf(user2.address)).eq(expandDecimals(4, 7).sub("120000")) // 0.8 - 0.0012 + + expect(await vault.feeReserves(bnb.address)).eq("600000000000000000") // 200 * 0.3% => 0.6 + expect(await vault.usdgAmounts(bnb.address)).eq(expandDecimals(100 * 400, 18).add(expandDecimals(200 * 300, 18)).sub(expandDecimals(180, 18))) + expect(await vault.poolAmounts(bnb.address)).eq(expandDecimals(100, 18).add(expandDecimals(200, 18)).sub("600000000000000000")) + + expect(await vault.feeReserves(btc.address)).eq("420000") // 1 * 0.3% => 0.003, 0.4 * 0.3% => 0.0012 + expect(await vault.usdgAmounts(btc.address)).eq(expandDecimals(200 * 300, 18).sub(expandDecimals(180, 18)).sub(expandDecimals(100 * 400, 18))) + expect(await vault.poolAmounts(btc.address)).eq(expandDecimals(1, 8).sub("300000").sub(expandDecimals(4, 7))) // 59700000, 0.597 BTC, 0.597 * 100,000 => 59700 + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(400)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(500)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(450)) + + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await bnb.balanceOf(user3.address)).eq(0) + await usdg.connect(user0).transfer(vault.address, expandDecimals(50000, 18)) + await vault.sellUSDG(bnb.address, user3.address) + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await bnb.balanceOf(user3.address)).eq("99700000000000000000") // 99.7, 50000 / 500 * 99.7% + + await usdg.connect(user0).transfer(vault.address, expandDecimals(50000, 18)) + await vault.sellUSDG(btc.address, user3.address) + + await usdg.connect(user0).transfer(vault.address, expandDecimals(10000, 18)) + await expect(vault.sellUSDG(btc.address, user3.address)) + .to.be.revertedWith("Vault: poolAmount exceeded") + }) + + it("caps max USDG amount", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(600)) + await ethPriceFeed.setLatestAnswer(toChainlinkPrice(3000)) + + const bnbConfig = getBnbConfig(bnb, bnbPriceFeed) + const ethConfig = getBnbConfig(eth, ethPriceFeed) + + bnbConfig[4] = expandDecimals(299000, 18) + await vault.setTokenConfig(...bnbConfig) + + ethConfig[4] = expandDecimals(30000, 18) + await vault.setTokenConfig(...ethConfig) + + await bnb.mint(user0.address, expandDecimals(499, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(499, 18)) + await vault.connect(user0).buyUSDG(bnb.address, user0.address) + + await eth.mint(user0.address, expandDecimals(10, 18)) + await eth.connect(user0).transfer(vault.address, expandDecimals(10, 18)) + await vault.connect(user0).buyUSDG(eth.address, user1.address) + + await bnb.mint(user0.address, expandDecimals(1, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(1, 18)) + + await expect(vault.connect(user0).buyUSDG(bnb.address, user0.address)) + .to.be.revertedWith("Vault: max USDG exceeded") + + bnbConfig[4] = expandDecimals(299100, 18) + await vault.setTokenConfig(...bnbConfig) + + await vault.connect(user0).buyUSDG(bnb.address, user0.address) + + await bnb.mint(user0.address, expandDecimals(1, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(1, 18)) + await expect(vault.connect(user0).swap(bnb.address, eth.address, user1.address)) + .to.be.revertedWith("Vault: max USDG exceeded") + + bnbConfig[4] = expandDecimals(299700, 18) + await vault.setTokenConfig(...bnbConfig) + await vault.connect(user0).swap(bnb.address, eth.address, user1.address) + }) + + it("does not cap max USDG debt", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(600)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await ethPriceFeed.setLatestAnswer(toChainlinkPrice(3000)) + await vault.setTokenConfig(...getEthConfig(eth, ethPriceFeed)) + + await bnb.mint(user0.address, expandDecimals(100, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.connect(user0).buyUSDG(bnb.address, user0.address) + + await eth.mint(user0.address, expandDecimals(10, 18)) + + expect(await eth.balanceOf(user0.address)).eq(expandDecimals(10, 18)) + expect(await bnb.balanceOf(user1.address)).eq(0) + + await eth.connect(user0).transfer(vault.address, expandDecimals(10, 18)) + await vault.connect(user0).swap(eth.address, bnb.address, user1.address) + + expect(await eth.balanceOf(user0.address)).eq(0) + expect(await bnb.balanceOf(user1.address)).eq("49850000000000000000") + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + + await eth.mint(user0.address, expandDecimals(1, 18)) + await eth.connect(user0).transfer(vault.address, expandDecimals(1, 18)) + await vault.connect(user0).swap(eth.address, bnb.address, user1.address) + }) + + it("ensures poolAmount >= buffer", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(600)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await ethPriceFeed.setLatestAnswer(toChainlinkPrice(3000)) + await vault.setTokenConfig(...getEthConfig(eth, ethPriceFeed)) + + await bnb.mint(user0.address, expandDecimals(100, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(100, 18)) + await vault.connect(user0).buyUSDG(bnb.address, user0.address) + + await vault.setBufferAmount(bnb.address, "94700000000000000000") // 94.7 + + expect(await vault.poolAmounts(bnb.address)).eq("99700000000000000000") // 99.7 + expect(await vault.poolAmounts(eth.address)).eq(0) + expect(await bnb.balanceOf(user1.address)).eq(0) + expect(await eth.balanceOf(user1.address)).eq(0) + + await eth.mint(user0.address, expandDecimals(1, 18)) + await eth.connect(user0).transfer(vault.address, expandDecimals(1, 18)) + await vault.connect(user0).swap(eth.address, bnb.address, user1.address) + + expect(await vault.poolAmounts(bnb.address)).eq("94700000000000000000") // 94.7 + expect(await vault.poolAmounts(eth.address)).eq(expandDecimals(1, 18)) + expect(await bnb.balanceOf(user1.address)).eq("4985000000000000000") // 4.985 + expect(await eth.balanceOf(user1.address)).eq(0) + + await eth.mint(user0.address, expandDecimals(1, 18)) + await eth.connect(user0).transfer(vault.address, expandDecimals(1, 18)) + await expect(vault.connect(user0).swap(eth.address, bnb.address, user1.address)) + .to.be.revertedWith("Vault: poolAmount < buffer") + }) +}) diff --git a/test/core/Vault/withdrawCollateral.js b/test/core/Vault/withdrawCollateral.js new file mode 100644 index 00000000..d3329b6d --- /dev/null +++ b/test/core/Vault/withdrawCollateral.js @@ -0,0 +1,141 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("./helpers") + +use(solidity) + +describe("Vault.withdrawCollateral", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("withdraw collateral", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(41000)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(40000)) + + await btc.mint(user1.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 250000) // 0.0025 BTC => 100 USD + await vault.buyUSDG(btc.address, user1.address) + + await btc.mint(user0.address, expandDecimals(1, 8)) + await btc.connect(user1).transfer(vault.address, 25000) // 0.00025 BTC => 10 USD + await expect(vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(110), true)) + .to.be.revertedWith("Vault: reserve exceeds pool") + + await vault.connect(user0).increasePosition(user0.address, btc.address, btc.address, toUsd(90), true) + + let position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(90)) // size + expect(position[1]).eq(toUsd(9.91)) // collateral, 10 - 90 * 0.1% + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000) // reserveAmount, 0.00225 * 40,000 => 90 + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(45100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(46100)) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(47100)) + + let leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(90817) // ~9X leverage + + expect(await vault.feeReserves(btc.address)).eq(969) + expect(await vault.reservedAmounts(btc.address)).eq(225000) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(80.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 219) + expect(await btc.balanceOf(user2.address)).eq(0) + + const tx0 = await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(3), toUsd(50), true, user2.address) + await reportGasUsed(provider, tx0, "decreasePosition gas used") + + leverage = await vault.getPositionLeverage(user0.address, btc.address, btc.address, true) + expect(leverage).eq(57887) // ~5.8X leverage + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(40)) // size + expect(position[1]).eq(toUsd(9.91 - 3)) // collateral + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000 / 90 * 40) // reserveAmount, 0.00225 * 40,000 => 90 + expect(position[5]).eq(toUsd(5)) // pnl + expect(position[6]).eq(true) + + expect(await vault.feeReserves(btc.address)).eq(969 + 106) // 0.00000106 * 45100 => ~0.05 USD + expect(await vault.reservedAmounts(btc.address)).eq(225000 / 90 * 40) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(33.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 16878 - 106 - 1 - 219) + expect(await btc.balanceOf(user2.address)).eq(16878) // 0.00016878 * 47100 => 7.949538 USD + + await expect(vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(3), 0, true, user2.address)) + .to.be.revertedWith("Vault: liquidation fees exceed collateral") + + const tx1 = await vault.connect(user0).decreasePosition(user0.address, btc.address, btc.address, toUsd(1), 0, true, user2.address) + await reportGasUsed(provider, tx1, "withdraw collateral gas used") + + position = await vault.getPosition(user0.address, btc.address, btc.address, true) + expect(position[0]).eq(toUsd(40)) // size + expect(position[1]).eq(toUsd(9.91 - 3 - 1)) // collateral + expect(position[2]).eq(toNormalizedPrice(41000)) // averagePrice + expect(position[3]).eq(0) // entryFundingRate + expect(position[4]).eq(225000 / 90 * 40) // reserveAmount, 0.00225 * 40,000 => 90 + expect(position[5]).eq(toUsd(5)) // pnl + expect(position[6]).eq(true) + + expect(await vault.feeReserves(btc.address)).eq(969 + 106) // 0.00000106 * 45100 => ~0.05 USD + expect(await vault.reservedAmounts(btc.address)).eq(225000 / 90 * 40) + expect(await vault.guaranteedUsd(btc.address)).eq(toUsd(34.09)) + expect(await vault.poolAmounts(btc.address)).eq(274250 - 16878 - 106 - 1 - 2123 - 219) // 0.00002123* 47100 => 1 USD + expect(await btc.balanceOf(user2.address)).eq(16878 + 2123) + }) +}) diff --git a/test/core/Vault/withdrawFees.js b/test/core/Vault/withdrawFees.js new file mode 100644 index 00000000..f563f7f3 --- /dev/null +++ b/test/core/Vault/withdrawFees.js @@ -0,0 +1,193 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../../shared/utilities") +const { toChainlinkPrice } = require("../../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../../shared/units") +const { initVault, getBnbConfig, getBtcConfig } = require("./helpers") + +use(solidity) + +describe("Vault.withdrawFees", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("withdrawFees", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnb.mint(user0.address, expandDecimals(900, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(900, 18)) + + expect(await usdg.balanceOf(wallet.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + + await vault.connect(user0).buyUSDG(bnb.address, user1.address) + + expect(await usdg.balanceOf(wallet.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq("269190000000000000000000") // 269,190 USDG, 810 fee + expect(await vault.feeReserves(bnb.address)).eq("2700000000000000000") // 2.7, 900 * 0.3% + expect(await vault.usdgAmounts(bnb.address)).eq("269190000000000000000000") // 269,190 + expect(await vault.poolAmounts(bnb.address)).eq("897300000000000000000") // 897.3 + expect(await usdg.totalSupply()).eq("269190000000000000000000") + + await bnb.mint(user0.address, expandDecimals(200, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(200, 18)) + + await btc.mint(user0.address, expandDecimals(2, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(2, 8)) + + await vault.connect(user0).buyUSDG(btc.address, user1.address) + expect(await vault.usdgAmounts(btc.address)).eq("119640000000000000000000") // 119,640 + expect(await usdg.totalSupply()).eq("388830000000000000000000") // 388,830 + + await btc.mint(user0.address, expandDecimals(2, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(2, 8)) + + await vault.connect(user0).buyUSDG(btc.address, user1.address) + expect(await vault.usdgAmounts(btc.address)).eq("239280000000000000000000") // 239,280 + expect(await usdg.totalSupply()).eq("508470000000000000000000") // 508,470 + + expect(await vault.usdgAmounts(bnb.address)).eq("269190000000000000000000") // 269,190 + expect(await vault.poolAmounts(bnb.address)).eq("897300000000000000000") // 897.3 + + await vault.connect(user0).buyUSDG(bnb.address, user1.address) + + expect(await vault.usdgAmounts(bnb.address)).eq("329010000000000000000000") // 329,010 + expect(await vault.poolAmounts(bnb.address)).eq("1096700000000000000000") // 1096.7 + + expect(await vault.feeReserves(bnb.address)).eq("3300000000000000000") // 3.3 BNB + expect(await vault.feeReserves(btc.address)).eq("1200000") // 0.012 BTC + + await expect(vault.connect(user0).withdrawFees(bnb.address, user2.address)) + .to.be.revertedWith("Vault: forbidden") + + expect(await bnb.balanceOf(user2.address)).eq(0) + await vault.withdrawFees(bnb.address, user2.address) + expect(await bnb.balanceOf(user2.address)).eq("3300000000000000000") + + expect(await btc.balanceOf(user2.address)).eq(0) + await vault.withdrawFees(btc.address, user2.address) + expect(await btc.balanceOf(user2.address)).eq("1200000") + }) + + it("withdrawFees using timelock", async () => { + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnb.mint(user0.address, expandDecimals(900, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(900, 18)) + + expect(await usdg.balanceOf(wallet.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq(0) + expect(await vault.feeReserves(bnb.address)).eq(0) + expect(await vault.usdgAmounts(bnb.address)).eq(0) + expect(await vault.poolAmounts(bnb.address)).eq(0) + + await vault.connect(user0).buyUSDG(bnb.address, user1.address) + + expect(await usdg.balanceOf(wallet.address)).eq(0) + expect(await usdg.balanceOf(user1.address)).eq("269190000000000000000000") // 269,190 USDG, 810 fee + expect(await vault.feeReserves(bnb.address)).eq("2700000000000000000") // 2.7, 900 * 0.3% + expect(await vault.usdgAmounts(bnb.address)).eq("269190000000000000000000") // 269,190 + expect(await vault.poolAmounts(bnb.address)).eq("897300000000000000000") // 897.3 + expect(await usdg.totalSupply()).eq("269190000000000000000000") + + await bnb.mint(user0.address, expandDecimals(200, 18)) + await bnb.connect(user0).transfer(vault.address, expandDecimals(200, 18)) + + await btc.mint(user0.address, expandDecimals(2, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(2, 8)) + + await vault.connect(user0).buyUSDG(btc.address, user1.address) + expect(await vault.usdgAmounts(btc.address)).eq("119640000000000000000000") // 119,640 + expect(await usdg.totalSupply()).eq("388830000000000000000000") // 388,830 + + await btc.mint(user0.address, expandDecimals(2, 8)) + await btc.connect(user0).transfer(vault.address, expandDecimals(2, 8)) + + await vault.connect(user0).buyUSDG(btc.address, user1.address) + expect(await vault.usdgAmounts(btc.address)).eq("239280000000000000000000") // 239,280 + expect(await usdg.totalSupply()).eq("508470000000000000000000") // 508,470 + + expect(await vault.usdgAmounts(bnb.address)).eq("269190000000000000000000") // 269,190 + expect(await vault.poolAmounts(bnb.address)).eq("897300000000000000000") // 897.3 + + await vault.connect(user0).buyUSDG(bnb.address, user1.address) + + expect(await vault.usdgAmounts(bnb.address)).eq("329010000000000000000000") // 329,010 + expect(await vault.poolAmounts(bnb.address)).eq("1096700000000000000000") // 1096.7 + + expect(await vault.feeReserves(bnb.address)).eq("3300000000000000000") // 3.3 BNB + expect(await vault.feeReserves(btc.address)).eq("1200000") // 0.012 BTC + + await expect(vault.connect(user0).withdrawFees(bnb.address, user2.address)) + .to.be.revertedWith("Vault: forbidden") + + const timelock = await deployContract("Timelock", [5 * 24 * 60 * 60]) + await vault.setGov(timelock.address) + + await expect(timelock.connect(user0).withdrawFees(vault.address, bnb.address, user2.address)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await bnb.balanceOf(user2.address)).eq(0) + await timelock.withdrawFees(vault.address, bnb.address, user2.address) + expect(await bnb.balanceOf(user2.address)).eq("3300000000000000000") + + expect(await btc.balanceOf(user2.address)).eq(0) + await timelock.withdrawFees(vault.address, btc.address, user2.address) + expect(await btc.balanceOf(user2.address)).eq("1200000") + }) +}) diff --git a/test/gambit-token/GMT.js b/test/gambit-token/GMT.js new file mode 100644 index 00000000..156a8701 --- /dev/null +++ b/test/gambit-token/GMT.js @@ -0,0 +1,223 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock } = require("../shared/utilities") + +use(solidity) + +describe("GMT", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let gmt + + beforeEach(async () => { + gmt = await deployContract("GMT", [expandDecimals(1000 * 1000, 18)]) + }) + + it("inits", async () => { + expect(await gmt.gov()).eq(wallet.address) + expect(await gmt.admins(wallet.address)).eq(true) + expect(await gmt.balanceOf(wallet.address)).eq(expandDecimals(1000 * 1000, 18)) + expect(await gmt.totalSupply()).eq(expandDecimals(1000 * 1000, 18)) + }) + + it("setGov", async () => { + await expect(gmt.connect(user0).setGov(user1.address)) + .to.be.revertedWith("GMT: forbidden") + + expect(await gmt.gov()).eq(wallet.address) + + await gmt.setGov(user0.address) + expect(await gmt.gov()).eq(user0.address) + + await gmt.connect(user0).setGov(user1.address) + expect(await gmt.gov()).eq(user1.address) + }) + + it("addAdmin", async () => { + await expect(gmt.connect(user0).addAdmin(user1.address)) + .to.be.revertedWith("GMT: forbidden") + + expect(await gmt.admins(user1.address)).eq(false) + await gmt.addAdmin(user1.address) + expect(await gmt.admins(user1.address)).eq(true) + }) + + it("removeAdmin", async () => { + await expect(gmt.connect(user0).removeAdmin(user1.address)) + .to.be.revertedWith("GMT: forbidden") + + expect(await gmt.admins(user1.address)).eq(false) + + await gmt.addAdmin(user1.address) + expect(await gmt.admins(user1.address)).eq(true) + + await gmt.removeAdmin(user1.address) + expect(await gmt.admins(user1.address)).eq(false) + }) + + it("setNextMigrationTime", async () => { + await expect(gmt.connect(user0).setNextMigrationTime(1000)) + .to.be.revertedWith("GMT: forbidden") + + expect(await gmt.migrationTime()).eq(0) + + await gmt.setNextMigrationTime(1000) + expect(await gmt.migrationTime()).eq(1000) + + await expect(gmt.setNextMigrationTime(999)) + .to.be.revertedWith("GMT: invalid _migrationTime") + + await gmt.setNextMigrationTime(1001) + expect(await gmt.migrationTime()).eq(1001) + }) + + it("beginMigration", async () => { + await expect(gmt.connect(user0).beginMigration()) + .to.be.revertedWith("GMT: forbidden") + + await gmt.addAdmin(user0.address) + expect(await gmt.hasActiveMigration()).eq(false) + await gmt.connect(user0).beginMigration() + expect(await gmt.hasActiveMigration()).eq(true) + + await gmt.connect(user0).endMigration() + expect(await gmt.hasActiveMigration()).eq(false) + + const nextMigrationTime = (await getBlockTime(provider)) + 1000 + await gmt.setNextMigrationTime(nextMigrationTime) + + await expect(gmt.connect(user0).beginMigration()) + .to.be.revertedWith("GMT: migrationTime not yet passed") + + await increaseTime(provider, 1010) + await mineBlock(provider) + + expect(await gmt.hasActiveMigration()).eq(false) + await gmt.connect(user0).beginMigration() + expect(await gmt.hasActiveMigration()).eq(true) + }) + + it("addBlockedRecipient", async () => { + await expect(gmt.connect(user0).addBlockedRecipient(user1.address)) + .to.be.revertedWith("GMT: forbidden") + + expect(await gmt.blockedRecipients(user1.address)).eq(false) + await gmt.addBlockedRecipient(user1.address) + expect(await gmt.blockedRecipients(user1.address)).eq(true) + }) + + it("removeBlockedRecipient", async () => { + await expect(gmt.connect(user0).removeBlockedRecipient(user1.address)) + .to.be.revertedWith("GMT: forbidden") + + expect(await gmt.blockedRecipients(user1.address)).eq(false) + + await gmt.addBlockedRecipient(user1.address) + expect(await gmt.blockedRecipients(user1.address)).eq(true) + + await gmt.removeBlockedRecipient(user1.address) + expect(await gmt.blockedRecipients(user1.address)).eq(false) + }) + + it("addMsgSender", async () => { + await expect(gmt.connect(user0).addMsgSender(user1.address)) + .to.be.revertedWith("GMT: forbidden") + + expect(await gmt.allowedMsgSenders(user1.address)).eq(false) + await gmt.addMsgSender(user1.address) + expect(await gmt.allowedMsgSenders(user1.address)).eq(true) + }) + + it("removeMsgSender", async () => { + await expect(gmt.connect(user0).removeMsgSender(user1.address)) + .to.be.revertedWith("GMT: forbidden") + + expect(await gmt.allowedMsgSenders(user1.address)).eq(false) + + await gmt.addMsgSender(user1.address) + expect(await gmt.allowedMsgSenders(user1.address)).eq(true) + + await gmt.removeMsgSender(user1.address) + expect(await gmt.allowedMsgSenders(user1.address)).eq(false) + }) + + it("withdrawToken", async () => { + const token = await deployContract("Token", []) + await token.mint(wallet.address, 1000) + + expect(await token.balanceOf(wallet.address)).eq(1000) + expect(await token.balanceOf(gmt.address)).eq(0) + await token.transfer(gmt.address, 1000) + expect(await token.balanceOf(wallet.address)).eq(0) + expect(await token.balanceOf(gmt.address)).eq(1000) + + await expect(gmt.connect(user0).withdrawToken(token.address, user1.address, 1000)) + .to.be.revertedWith("GMT: forbidden") + + expect(await token.balanceOf(wallet.address)).eq(0) + expect(await token.balanceOf(gmt.address)).eq(1000) + expect(await token.balanceOf(user1.address)).eq(0) + await gmt.withdrawToken(token.address, user1.address, 1000) + expect(await token.balanceOf(wallet.address)).eq(0) + expect(await token.balanceOf(gmt.address)).eq(0) + expect(await token.balanceOf(user1.address)).eq(1000) + }) + + it("transfer", async () => { + expect(await gmt.balanceOf(wallet.address)).eq(expandDecimals(1000 * 1000, 18)) + expect(await gmt.balanceOf(user0.address)).eq(0) + await gmt.transfer(user0.address, expandDecimals(1000, 18)) + expect(await gmt.balanceOf(wallet.address)).eq(expandDecimals(999 * 1000, 18)) + expect(await gmt.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + + await expect(gmt.connect(user0).transfer(user1.address, expandDecimals(1001, 18))) + .to.be.revertedWith("GMT: transfer amount exceeds balance") + }) + + it("approve", async () => { + expect(await gmt.allowance(wallet.address, user0.address)).eq(0) + await gmt.approve(user0.address, 1000) + expect(await gmt.allowance(wallet.address, user0.address)).eq(1000) + }) + + it("transferFrom", async () => { + expect(await gmt.allowance(wallet.address, user0.address)).eq(0) + await gmt.approve(user0.address, expandDecimals(2000, 18)) + expect(await gmt.allowance(wallet.address, user0.address)).eq(expandDecimals(2000, 18)) + + expect(await gmt.balanceOf(wallet.address)).eq(expandDecimals(1000 * 1000, 18)) + expect(await gmt.balanceOf(user1.address)).eq(0) + + await gmt.connect(user0).transferFrom(wallet.address, user1.address, expandDecimals(1000, 18)) + expect(await gmt.allowance(wallet.address, user0.address)).eq(expandDecimals(1000, 18)) + + expect(await gmt.balanceOf(wallet.address)).eq(expandDecimals(999 * 1000, 18)) + expect(await gmt.balanceOf(user1.address)).eq(expandDecimals(1000, 18)) + + await expect(gmt.connect(user0).transferFrom(wallet.address, user1.address, expandDecimals(1001, 18))) + .to.be.revertedWith("GMT: transfer amount exceeds allowance") + }) + + it("allows migrations", async () => { + await gmt.beginMigration() + await gmt.approve(user0.address, expandDecimals(1000, 18)) + await expect(gmt.connect(user0).transferFrom(wallet.address, user1.address, expandDecimals(500, 18))) + .to.be.revertedWith("GMT: forbidden msg.sender") + + await gmt.addMsgSender(user0.address) + + expect(await gmt.balanceOf(user1.address)).eq(0) + await gmt.connect(user0).transferFrom(wallet.address, user1.address, expandDecimals(500, 18)) + expect(await gmt.balanceOf(user1.address)).eq(expandDecimals(500, 18)) + + await gmt.addBlockedRecipient(user1.address) + await expect(gmt.connect(user0).transferFrom(wallet.address, user1.address, expandDecimals(500, 18))) + .to.be.revertedWith("GMT: forbidden recipient") + + await gmt.removeBlockedRecipient(user1.address) + + await gmt.connect(user0).transferFrom(wallet.address, user1.address, expandDecimals(500, 18)) + expect(await gmt.balanceOf(user1.address)).eq(expandDecimals(1000, 18)) + }) +}) diff --git a/test/gambit-token/Treasury.js b/test/gambit-token/Treasury.js new file mode 100644 index 00000000..61e70581 --- /dev/null +++ b/test/gambit-token/Treasury.js @@ -0,0 +1,430 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock } = require("../shared/utilities") + +use(solidity) + +const PRECISION = 1000000 + +describe("Treasury", function () { + const provider = waffle.provider + const [wallet, fund, user0, user1, user2, user3] = provider.getWallets() + let treasury + + let gmt + let busd + let pair + let router + + let gmtPresalePrice = 4.5 * PRECISION + let gmtListingPrice = 5 * PRECISION + let busdSlotCap = expandDecimals(2000, 18) + let busdHardCap = expandDecimals(900 * 1000, 18) + let busdBasisPoints = 5000 // 50% + let unlockTime + + beforeEach(async () => { + treasury = await deployContract("Treasury", []) + gmt = await deployContract("GMT", [expandDecimals(1000 * 1000, 18)]) + busd = await deployContract("Token", []) + pair = await deployContract("Token", []) + router = await deployContract("PancakeRouter", [pair.address]) + + unlockTime = await getBlockTime(provider) + 1000 + + await treasury.initialize( + [ + gmt.address, + busd.address, + router.address, + fund.address + ], + [ + gmtPresalePrice, + gmtListingPrice, + busdSlotCap, + busdHardCap, + busdBasisPoints, + unlockTime + ] + ) + + await gmt.beginMigration() + + await gmt.addAdmin(treasury.address) + await gmt.addMsgSender(treasury.address) + await gmt.addMsgSender(wallet.address) + }) + + it("initialize", async () => { + await expect(treasury.initialize( + [ + gmt.address, + busd.address, + router.address, + fund.address + ], + [ + gmtPresalePrice, + gmtListingPrice, + busdSlotCap, + busdHardCap, + busdBasisPoints, + unlockTime + ] + )).to.be.revertedWith("Treasury: already initialized") + + await expect(treasury.connect(user0).initialize( + [ + gmt.address, + busd.address, + router.address, + fund.address + ], + [ + gmtPresalePrice, + gmtListingPrice, + busdSlotCap, + busdHardCap, + busdBasisPoints, + unlockTime + ] + )).to.be.revertedWith("Treasury: forbidden") + + expect(await treasury.isInitialized()).eq(true) + + expect(await treasury.gmt()).eq(gmt.address) + expect(await treasury.busd()).eq(busd.address) + expect(await treasury.router()).eq(router.address) + expect(await treasury.fund()).eq(fund.address) + + expect(await treasury.gmtPresalePrice()).eq(gmtPresalePrice) + expect(await treasury.gmtListingPrice()).eq(gmtListingPrice) + expect(await treasury.busdSlotCap()).eq(busdSlotCap) + expect(await treasury.busdHardCap()).eq(busdHardCap) + expect(await treasury.busdBasisPoints()).eq(busdBasisPoints) + expect(await treasury.unlockTime()).eq(unlockTime) + }) + + it("setGov", async () => { + await expect(treasury.connect(user0).setGov(user1.address)) + .to.be.revertedWith("Treasury: forbidden") + + expect(await treasury.gov()).eq(wallet.address) + + await treasury.setGov(user0.address) + expect(await treasury.gov()).eq(user0.address) + + await treasury.connect(user0).setGov(user1.address) + expect(await treasury.gov()).eq(user1.address) + }) + + it("setFund", async () => { + await expect(treasury.connect(user0).setFund(user2.address)) + .to.be.revertedWith("Treasury: forbidden") + + expect(await treasury.fund()).eq(fund.address) + await treasury.setFund(user2.address) + expect(await treasury.fund()).eq(user2.address) + }) + + it("extendUnlockTime", async () => { + await expect(treasury.connect(user0).extendUnlockTime(unlockTime + 100)) + .to.be.revertedWith("Treasury: forbidden") + + await expect(treasury.extendUnlockTime(unlockTime - 100)) + .to.be.revertedWith("Treasury: invalid _unlockTime") + + expect(await treasury.unlockTime()).eq(unlockTime) + + await treasury.extendUnlockTime(unlockTime + 100) + expect(await treasury.unlockTime()).eq(unlockTime + 100) + }) + + it("addWhitelists", async () => { + const whitelist = [user0.address, user1.address, user2.address] + + await expect(treasury.connect(user0).addWhitelists(whitelist)) + .to.be.revertedWith("Treasury: forbidden") + + expect(await treasury.swapWhitelist(user0.address)).eq(false) + expect(await treasury.swapWhitelist(user1.address)).eq(false) + expect(await treasury.swapWhitelist(user2.address)).eq(false) + expect(await treasury.swapWhitelist(user3.address)).eq(false) + + await treasury.addWhitelists(whitelist) + + expect(await treasury.swapWhitelist(user0.address)).eq(true) + expect(await treasury.swapWhitelist(user1.address)).eq(true) + expect(await treasury.swapWhitelist(user2.address)).eq(true) + expect(await treasury.swapWhitelist(user3.address)).eq(false) + }) + + it("removeWhitelists", async () => { + const whitelist = [user0.address, user1.address, user2.address] + + await expect(treasury.connect(user0).removeWhitelists(whitelist)) + .to.be.revertedWith("Treasury: forbidden") + + expect(await treasury.swapWhitelist(user0.address)).eq(false) + expect(await treasury.swapWhitelist(user1.address)).eq(false) + expect(await treasury.swapWhitelist(user2.address)).eq(false) + expect(await treasury.swapWhitelist(user3.address)).eq(false) + + await treasury.addWhitelists(whitelist) + + expect(await treasury.swapWhitelist(user0.address)).eq(true) + expect(await treasury.swapWhitelist(user1.address)).eq(true) + expect(await treasury.swapWhitelist(user2.address)).eq(true) + expect(await treasury.swapWhitelist(user3.address)).eq(false) + + await treasury.removeWhitelists([user0.address, user1.address]) + + expect(await treasury.swapWhitelist(user0.address)).eq(false) + expect(await treasury.swapWhitelist(user1.address)).eq(false) + expect(await treasury.swapWhitelist(user2.address)).eq(true) + expect(await treasury.swapWhitelist(user3.address)).eq(false) + }) + + it("updateWhitelist", async () => { + const whitelist = [user0.address, user1.address, user2.address] + + expect(await treasury.swapWhitelist(user0.address)).eq(false) + expect(await treasury.swapWhitelist(user1.address)).eq(false) + expect(await treasury.swapWhitelist(user2.address)).eq(false) + expect(await treasury.swapWhitelist(user3.address)).eq(false) + + await treasury.addWhitelists(whitelist) + + expect(await treasury.swapWhitelist(user0.address)).eq(true) + expect(await treasury.swapWhitelist(user1.address)).eq(true) + expect(await treasury.swapWhitelist(user2.address)).eq(true) + expect(await treasury.swapWhitelist(user3.address)).eq(false) + + await expect(treasury.connect(user0).updateWhitelist(user0.address, user3.address)) + .to.be.revertedWith("Treasury: forbidden") + + await expect(treasury.updateWhitelist(user3.address, user0.address)) + .to.be.revertedWith("Treasury: invalid prevAccount") + + await treasury.updateWhitelist(user0.address, user3.address) + + expect(await treasury.swapWhitelist(user0.address)).eq(false) + expect(await treasury.swapWhitelist(user1.address)).eq(true) + expect(await treasury.swapWhitelist(user2.address)).eq(true) + expect(await treasury.swapWhitelist(user3.address)).eq(true) + }) + + it("swap", async () => { + await busd.mint(user0.address, expandDecimals(1000 * 1000, 18)) + + const whitelist = [user0.address, user1.address, user2.address] + await treasury.addWhitelists(whitelist) + + await expect(treasury.connect(user3).swap(2000)) + .to.be.revertedWith("Treasury: forbidden") + + await expect(treasury.connect(user0).swap(0)) + .to.be.revertedWith("Treasury: invalid _busdAmount") + + await expect(treasury.connect(user0).swap(2000)) + .to.be.revertedWith("ERC20: transfer amount exceeds allowance") + + await busd.connect(user0).approve(treasury.address, expandDecimals(1000 * 1000, 18)) + await expect(treasury.connect(user0).swap(2000)) + .to.be.revertedWith("GMT: transfer amount exceeds balance") + + await gmt.transfer(treasury.address, expandDecimals(1000 * 1000, 18)) + + expect(await treasury.swapAmounts(user0.address)).eq(0) + expect(await treasury.busdReceived()).eq(0) + expect(await busd.balanceOf(treasury.address)).eq(0) + expect(await busd.balanceOf(user0.address)).eq(expandDecimals(1000 * 1000, 18)) + expect(await gmt.balanceOf(treasury.address)).eq(expandDecimals(1000 * 1000, 18)) + expect(await gmt.balanceOf(user0.address)).eq(0) + + await treasury.connect(user0).swap(2000) + + expect(await treasury.swapAmounts(user0.address)).eq(2000) + expect(await treasury.busdReceived()).eq(2000) + expect(await busd.balanceOf(treasury.address)).eq(2000) + expect(await busd.balanceOf(user0.address)).eq("999999999999999999998000") + expect(await gmt.balanceOf(treasury.address)).eq("999999999999999999999556") + expect(await gmt.balanceOf(user0.address)).eq(444) + + await expect(gmt.connect(user0).transfer(user1.address, 100)) + .to.be.revertedWith("GMT: forbidden msg.sender") + + await treasury.connect(user0).swap(1000) + + expect(await treasury.swapAmounts(user0.address)).eq(3000) + expect(await treasury.busdReceived()).eq(3000) + expect(await busd.balanceOf(treasury.address)).eq(3000) + expect(await busd.balanceOf(user0.address)).eq("999999999999999999997000") + expect(await gmt.balanceOf(treasury.address)).eq("999999999999999999999334") + expect(await gmt.balanceOf(user0.address)).eq(666) + }) + + it("validates swap.busdSlotCap", async () => { + await busd.mint(user0.address, expandDecimals(1000 * 1000, 18)) + + const whitelist = [user0.address, user1.address, user2.address] + await treasury.addWhitelists(whitelist) + + await busd.connect(user0).approve(treasury.address, expandDecimals(1000 * 1000, 18)) + await gmt.transfer(treasury.address, expandDecimals(1000 * 1000, 18)) + + expect(await treasury.swapAmounts(user0.address)).eq(0) + expect(await treasury.busdReceived()).eq(0) + expect(await busd.balanceOf(treasury.address)).eq(0) + expect(await busd.balanceOf(user0.address)).eq(expandDecimals(1000 * 1000, 18)) + expect(await gmt.balanceOf(treasury.address)).eq(expandDecimals(1000 * 1000, 18)) + expect(await gmt.balanceOf(user0.address)).eq(0) + + await expect(treasury.connect(user0).swap(expandDecimals(2001, 18))) + .to.be.revertedWith("Treasury: busdSlotCap exceeded") + + await treasury.connect(user0).swap(expandDecimals(1000, 18)) + + expect(await treasury.swapAmounts(user0.address)).eq(expandDecimals(1000, 18)) + expect(await treasury.busdReceived()).eq(expandDecimals(1000, 18)) + expect(await busd.balanceOf(treasury.address)).eq(expandDecimals(1000, 18)) + expect(await busd.balanceOf(user0.address)).eq("999000000000000000000000") + expect(await gmt.balanceOf(treasury.address)).eq("999777777777777777777778") + expect(await gmt.balanceOf(user0.address)).eq("222222222222222222222") + + await expect(treasury.connect(user0).swap(expandDecimals(1001, 18))) + .to.be.revertedWith("Treasury: busdSlotCap exceeded") + + await treasury.connect(user0).swap(expandDecimals(1000, 18)) + + expect(await treasury.swapAmounts(user0.address)).eq(expandDecimals(2000, 18)) + expect(await treasury.busdReceived()).eq(expandDecimals(2000, 18)) + expect(await busd.balanceOf(treasury.address)).eq(expandDecimals(2000, 18)) + expect(await busd.balanceOf(user0.address)).eq("998000000000000000000000") + expect(await gmt.balanceOf(treasury.address)).eq("999555555555555555555556") + expect(await gmt.balanceOf(user0.address)).eq("444444444444444444444") + + await expect(treasury.connect(user0).swap("1")) + .to.be.revertedWith("Treasury: busdSlotCap exceeded") + }) + + it("validates swap.busdHardCap", async () => { + await busd.mint(user0.address, expandDecimals(1000 * 1000, 18)) + + const whitelist = [user0.address, user1.address, user2.address] + await treasury.addWhitelists(whitelist) + + await busd.connect(user0).approve(treasury.address, expandDecimals(1000 * 1000, 18)) + await gmt.transfer(treasury.address, expandDecimals(1000 * 1000, 18)) + + expect(await treasury.busdReceived()).eq(0) + await expect(treasury.connect(user0).swap(expandDecimals(901 * 1000, 18))) + .to.be.revertedWith("Treasury: busdHardCap exceeded") + + await expect(treasury.connect(user0).swap(expandDecimals(900 * 1000, 18))) + .to.be.revertedWith("Treasury: busdSlotCap exceeded") + + await treasury.connect(user0).swap(expandDecimals(2000, 18)) + expect(await treasury.busdReceived()).eq(expandDecimals(2000, 18)) + + await expect(treasury.connect(user0).swap(expandDecimals(899 * 1000, 18))) + .to.be.revertedWith("Treasury: busdHardCap exceeded") + + await expect(treasury.connect(user0).swap(expandDecimals(898 * 1000, 18))) + .to.be.revertedWith("Treasury: busdSlotCap exceeded") + }) + + it("validates swap.isSwapActive", async () => { + await busd.mint(user0.address, expandDecimals(1000 * 1000, 18)) + + const whitelist = [user0.address, user1.address, user2.address] + await treasury.addWhitelists(whitelist) + + await busd.connect(user0).approve(treasury.address, expandDecimals(1000 * 1000, 18)) + await gmt.transfer(treasury.address, expandDecimals(1000 * 1000, 18)) + + await treasury.connect(user0).swap(expandDecimals(1000, 18)) + await treasury.endSwap() + + await expect(treasury.connect(user0).swap(expandDecimals(1000, 18))) + .to.be.revertedWith("Treasury: swap is no longer active") + }) + + it("addLiquidity", async () => { + await busd.mint(user0.address, expandDecimals(1000 * 1000, 18)) + + const whitelist = [user0.address, user1.address, user2.address] + await treasury.addWhitelists(whitelist) + + await busd.connect(user0).approve(treasury.address, expandDecimals(1000 * 1000, 18)) + await gmt.transfer(treasury.address, expandDecimals(1000 * 1000, 18)) + + await treasury.connect(user0).swap(expandDecimals(1000, 18)) + await treasury.endSwap() + + await treasury.increaseBusdBasisPoints(7500) + + await expect(treasury.connect(user0).addLiquidity()) + .to.be.revertedWith("Treasury: forbidden") + + expect(await treasury.isLiquidityAdded()).eq(false) + expect(await busd.balanceOf(treasury.address)).eq(expandDecimals(1000, 18)) + expect(await busd.balanceOf(pair.address)).eq(0) + expect(await busd.balanceOf(fund.address)).eq(0) + expect(await gmt.balanceOf(pair.address)).eq(0) + expect(await gmt.balanceOf(fund.address)).eq(0) + + await treasury.addLiquidity() + + expect(await treasury.isLiquidityAdded()).eq(true) + expect(await busd.balanceOf(treasury.address)).eq(0) + expect(await busd.balanceOf(pair.address)).eq(expandDecimals(750, 18)) + expect(await busd.balanceOf(fund.address)).eq(expandDecimals(250, 18)) + expect(await gmt.balanceOf(pair.address)).eq(expandDecimals(150, 18)) // ~150, 750 / 5 + expect(await gmt.balanceOf(fund.address)).eq(0) + + await expect(treasury.addLiquidity()) + .to.be.revertedWith("Treasury: liquidity already added") + }) + + it("withdrawToken", async () => { + await expect(treasury.connect(user0).withdrawToken(busd.address, user1.address, 1000)) + .to.be.revertedWith("Treasury: forbidden") + + await expect(treasury.withdrawToken(busd.address, user1.address, 1000)) + .to.be.revertedWith("Treasury: unlockTime not yet passed") + + await increaseTime(provider, 2000) + await mineBlock(provider) + + await busd.mint(wallet.address, 1000) + await busd.transfer(treasury.address, 1000) + + expect(await busd.balanceOf(user1.address)).eq(0) + await treasury.withdrawToken(busd.address, user1.address, 1000) + expect(await busd.balanceOf(user1.address)).eq(1000) + }) + + it("increaseBusdBasisPoints", async () => { + await expect(treasury.connect(user0).increaseBusdBasisPoints(4000)) + .to.be.revertedWith("Treasury: forbidden") + + await expect(treasury.increaseBusdBasisPoints(4000)) + .to.be.revertedWith("Treasury: invalid _busdBasisPoints") + + expect(await treasury.busdBasisPoints()).eq(5000) + await treasury.increaseBusdBasisPoints(5001) + expect(await treasury.busdBasisPoints()).eq(5001) + }) + + it("endSwap", async () => { + await expect(treasury.connect(user0).endSwap()) + .to.be.revertedWith("Treasury: forbidden") + + expect(await treasury.isSwapActive()).eq(true) + await treasury.endSwap() + expect(await treasury.isSwapActive()).eq(false) + }) +}) diff --git a/test/gmx/GmxFloor.js b/test/gmx/GmxFloor.js new file mode 100644 index 00000000..09542e8c --- /dev/null +++ b/test/gmx/GmxFloor.js @@ -0,0 +1,460 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed, print } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") + +use(solidity) + +const { AddressZero } = ethers.constants + +describe("GmxFloor", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3, signer0, signer1, signer2] = provider.getWallets() + let gmx + let eth + let gmxFloor + let timelock + let nft0 + let nft1 + const nftId = 17 + + beforeEach(async () => { + gmx = await deployContract("GMX", []) + eth = await deployContract("Token", []) + gmxFloor = await deployContract("GmxFloor", [ + gmx.address, // _gmx + eth.address, // _reserveToken + expandDecimals(1000, 18), // _backedSupply + expandDecimals(5, 30 - 4), // _baseMintPrice, 1 ETH => $4000, 0.0005 ETH => $2 + expandDecimals(5, 30 - 4 - 6), // _mintMultiplier, 0.0025 ETH => $10, 0.0025 / 5 million, 0.0005 / 1 million + expandDecimals(1, 18), // _multiplierPrecision + 2 + ]) + + await gmxFloor.initialize([signer0.address, signer1.address, signer2.address]) + + nft0 = await deployContract("ERC721", ["NFT0", "NFT0"]) + nft1 = await deployContract("ERC721", ["NFT1", "NFT1"]) + + timelock = await deployContract("Timelock", [5 * 24 * 60 * 60, gmxFloor.address, 1000]) + }) + + it("inits", async () => { + expect(await gmxFloor.gmx()).eq(gmx.address) + expect(await gmxFloor.reserveToken()).eq(eth.address) + expect(await gmxFloor.backedSupply()).eq(expandDecimals(1000, 18)) + expect(await gmxFloor.baseMintPrice()).eq("500000000000000000000000000") + expect(await gmxFloor.mintMultiplier()).eq("500000000000000000000") + expect(await gmxFloor.multiplierPrecision()).eq(expandDecimals(1, 18)) + + await expect(gmxFloor.initialize([signer0.address, signer1.address, signer2.address])) + .to.be.revertedWith("TokenManager: already initialized") + + expect(await gmxFloor.signers(0)).eq(signer0.address) + expect(await gmxFloor.signers(1)).eq(signer1.address) + expect(await gmxFloor.signers(2)).eq(signer2.address) + expect(await gmxFloor.signersLength()).eq(3) + + expect(await gmxFloor.isSigner(user0.address)).eq(false) + expect(await gmxFloor.isSigner(signer0.address)).eq(true) + expect(await gmxFloor.isSigner(signer1.address)).eq(true) + expect(await gmxFloor.isSigner(signer2.address)).eq(true) + }) + + it("setHandler", async () => { + await expect(gmxFloor.connect(user0).setHandler(user0.address, true)) + .to.be.revertedWith("TokenManager: forbidden") + + expect(await gmxFloor.isHandler(user0.address)).eq(false) + await gmxFloor.connect(wallet).setHandler(user0.address, true) + expect(await gmxFloor.isHandler(user0.address)).eq(true) + }) + + it("setBackedSupply", async () => { + await expect(gmxFloor.connect(user0).setBackedSupply(expandDecimals(999, 18))) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(wallet).setBackedSupply(expandDecimals(999, 18))) + .to.be.revertedWith("GmxFloor: invalid _backedSupply") + + expect(await gmxFloor.backedSupply()).eq(expandDecimals(1000, 18)) + await gmxFloor.connect(wallet).setBackedSupply(expandDecimals(1001, 18)) + expect(await gmxFloor.backedSupply()).eq(expandDecimals(1001, 18)) + }) + + it("setMintMultiplier", async () => { + await expect(gmxFloor.connect(user0).setMintMultiplier(expandDecimals(5, 30 - 10))) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(wallet).setMintMultiplier(expandDecimals(4, 30 - 10))) + .to.be.revertedWith("GmxFloor: invalid _mintMultiplier") + + expect(await gmxFloor.mintMultiplier()).eq("500000000000000000000") + await gmxFloor.connect(wallet).setMintMultiplier(expandDecimals(6, 30 - 10)) + expect(await gmxFloor.mintMultiplier()).eq("600000000000000000000") + }) + + it("mint, burn", async () => { + expect(await gmxFloor.getMintPrice()).eq(expandDecimals(5, 30 - 4)) + expect(await gmxFloor.backedSupply()).eq(expandDecimals(1000, 18)) + expect(await gmxFloor.getBurnAmountOut(expandDecimals(1000, 18))).eq(0) + + await eth.mint(user0.address, expandDecimals(1, 18)) + expect(await gmx.balanceOf(user1.address)).eq(0) + await expect(gmxFloor.connect(user0).mint(expandDecimals(1000, 18), expandDecimals(1, 18), user1.address)) + .to.be.revertedWith("GmxFloor: forbidden") + + await gmxFloor.setHandler(user0.address, true) + + await expect(gmxFloor.connect(user0).mint(expandDecimals(1000, 18), expandDecimals(1, 18), user1.address)) + .to.be.revertedWith("ERC20: transfer amount exceeds allowance") + + await eth.connect(user0).approve(gmxFloor.address, expandDecimals(1, 18)) + + await expect(gmxFloor.connect(user0).mint(expandDecimals(1000, 18), expandDecimals(1, 18), user1.address)) + .to.be.revertedWith("BaseToken: transfer amount exceeds balance") + + await gmx.setMinter(wallet.address, true) + await gmx.mint(gmxFloor.address, expandDecimals(2000, 18)) + + expect(await eth.balanceOf(user0.address)).eq(expandDecimals(1, 18)) + expect(await gmx.balanceOf(user1.address)).eq(0) + + await expect(gmxFloor.connect(user0).mint(expandDecimals(2000, 18), expandDecimals(1, 18), user1.address)) + .to.be.revertedWith("GmxFloor: _maxCost exceeded") + + await gmxFloor.connect(user0).mint(expandDecimals(1900, 18), expandDecimals(1, 18), user1.address) + + expect(await eth.balanceOf(user0.address)).eq("49097500000000000") // 0.0490975 + expect(await gmx.balanceOf(user1.address)).eq(expandDecimals(1900, 18)) + + expect(await gmxFloor.getMintPrice()).eq("500950000000000000000000000") + expect(await gmxFloor.backedSupply()).eq(expandDecimals(2900, 18)) + + expect(await gmxFloor.getBurnAmountOut(expandDecimals(1000, 18))).eq("295107672413793103") // 1000 * 0.9509025 / 2900 * 0.9, 0.295107672413793103 ETH, 1180 USD + + await gmxFloor.setHandler(user0.address, false) + await expect(gmxFloor.connect(user0).burn(expandDecimals(1000, 18), "296000000000000000", user1.address)) + .to.be.revertedWith("GmxFloor: forbidden") + + await gmxFloor.setHandler(user0.address, true) + await expect(gmxFloor.connect(user0).burn(expandDecimals(1000, 18), "296000000000000000", user1.address)) + .to.be.revertedWith("GmxFloor: insufficient amountOut") + + await expect(gmxFloor.connect(user0).burn(expandDecimals(1000, 18), "295000000000000000", user1.address)) + .to.be.revertedWith("MintableBaseToken: forbidden") + + await gmx.setMinter(gmxFloor.address, true) + await expect(gmxFloor.connect(user0).burn(expandDecimals(1000, 18), "295000000000000000", user1.address)) + .to.be.revertedWith("BaseToken: burn amount exceeds balance") + + await gmx.connect(user1).transfer(user0.address, expandDecimals(1000, 18)) + await gmxFloor.connect(user0).burn(expandDecimals(1000, 18), "295000000000000000", user1.address) + }) + + it("signalApprove", async () => { + await expect(gmxFloor.connect(user0).signalApprove(eth.address, user2.address, expandDecimals(5, 18))) + .to.be.revertedWith("TokenManager: forbidden") + + await gmxFloor.connect(wallet).signalApprove(eth.address, user2.address, expandDecimals(5, 18)) + }) + + it("signApprove", async () => { + await expect(gmxFloor.connect(user0).signApprove(eth.address, user2.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(signer2).signApprove(eth.address, user2.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await gmxFloor.connect(wallet).signalApprove(eth.address, user2.address, expandDecimals(5, 18)) + + await expect(gmxFloor.connect(user0).signApprove(eth.address, user2.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await gmxFloor.connect(signer2).signApprove(eth.address, user2.address, expandDecimals(5, 18), 1) + + await expect(gmxFloor.connect(signer2).signApprove(eth.address, user2.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: already signed") + + await gmxFloor.connect(signer1).signApprove(eth.address, user2.address, expandDecimals(5, 18), 1) + }) + + it("approve", async () => { + await eth.mint(gmxFloor.address, expandDecimals(5, 18)) + + await expect(gmxFloor.connect(user0).approve(eth.address, user2.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(wallet).approve(eth.address, user2.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await gmxFloor.connect(wallet).signalApprove(eth.address, user2.address, expandDecimals(5, 18)) + + await expect(gmxFloor.connect(wallet).approve(gmx.address, user2.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(wallet).approve(eth.address, user0.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(wallet).approve(eth.address, user2.address, expandDecimals(6, 18), 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(wallet).approve(eth.address, user2.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: action not authorized") + + await gmxFloor.connect(signer0).signApprove(eth.address, user2.address, expandDecimals(5, 18), 1) + + await expect(gmxFloor.connect(wallet).approve(eth.address, user2.address, expandDecimals(5, 18), 1)) + .to.be.revertedWith("TokenManager: insufficient authorization") + + await gmxFloor.connect(signer2).signApprove(eth.address, user2.address, expandDecimals(5, 18), 1) + + await expect(eth.connect(user2).transferFrom(gmxFloor.address, user1.address, expandDecimals(4, 18))) + .to.be.revertedWith("ERC20: transfer amount exceeds allowance") + + await gmxFloor.connect(wallet).approve(eth.address, user2.address, expandDecimals(5, 18), 1) + + await expect(eth.connect(user2).transferFrom(gmxFloor.address, user1.address, expandDecimals(6, 18))) + .to.be.revertedWith("ERC20: transfer amount exceeds balance") + + expect(await eth.balanceOf(user1.address)).eq(0) + await eth.connect(user2).transferFrom(gmxFloor.address, user1.address, expandDecimals(5, 18)) + expect(await eth.balanceOf(user1.address)).eq(expandDecimals(5, 18)) + }) + + it("signalApproveNFT", async () => { + await expect(gmxFloor.connect(user0).signalApproveNFT(eth.address, user2.address, nftId)) + .to.be.revertedWith("TokenManager: forbidden") + + await gmxFloor.connect(wallet).signalApproveNFT(eth.address, user2.address, nftId) + }) + + it("signApproveNFT", async () => { + await expect(gmxFloor.connect(user0).signApproveNFT(eth.address, user2.address, nftId, 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(signer2).signApproveNFT(eth.address, user2.address, nftId, 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await gmxFloor.connect(wallet).signalApproveNFT(eth.address, user2.address, nftId) + + await expect(gmxFloor.connect(user0).signApproveNFT(eth.address, user2.address, nftId, 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await gmxFloor.connect(signer2).signApproveNFT(eth.address, user2.address, nftId, 1) + + await expect(gmxFloor.connect(signer2).signApproveNFT(eth.address, user2.address, nftId, 1)) + .to.be.revertedWith("TokenManager: already signed") + + await gmxFloor.connect(signer1).signApproveNFT(eth.address, user2.address, nftId, 1) + }) + + it("approveNFT", async () => { + await nft0.mint(gmxFloor.address, nftId) + await nft1.mint(gmxFloor.address, nftId) + + await expect(gmxFloor.connect(user0).approveNFT(nft0.address, user2.address, nftId, 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(wallet).approveNFT(nft0.address, user2.address, nftId, 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await gmxFloor.connect(wallet).signalApproveNFT(nft0.address, user2.address, nftId) + + await expect(gmxFloor.connect(wallet).approveNFT(nft1.address, user2.address, nftId, 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(wallet).approveNFT(nft0.address, user0.address, nftId, 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(wallet).approveNFT(nft0.address, user2.address, nftId + 1, 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(wallet).approveNFT(nft0.address, user2.address, nftId, 1)) + .to.be.revertedWith("TokenManager: action not authorized") + + await gmxFloor.connect(signer0).signApproveNFT(nft0.address, user2.address, nftId, 1) + + await expect(gmxFloor.connect(wallet).approveNFT(nft0.address, user2.address, nftId, 1)) + .to.be.revertedWith("TokenManager: insufficient authorization") + + await gmxFloor.connect(signer2).signApproveNFT(nft0.address, user2.address, nftId, 1) + + await expect(nft0.connect(user2).transferFrom(gmxFloor.address, user1.address, nftId)) + .to.be.revertedWith("ERC721: transfer caller is not owner nor approved") + + await gmxFloor.connect(wallet).approveNFT(nft0.address, user2.address, nftId, 1) + + expect(await nft0.balanceOf(user1.address)).eq(0) + expect(await nft0.balanceOf(gmxFloor.address)).eq(1) + expect(await nft0.ownerOf(nftId)).eq(gmxFloor.address) + + await nft0.connect(user2).transferFrom(gmxFloor.address, user1.address, nftId) + + expect(await nft0.balanceOf(user1.address)).eq(1) + expect(await nft0.balanceOf(gmxFloor.address)).eq(0) + expect(await nft0.ownerOf(nftId)).eq(user1.address) + + await expect(nft0.connect(user2).transferFrom(gmxFloor.address, user1.address, nftId)) + .to.be.revertedWith("ERC721: transfer caller is not owner nor approved") + }) + + it("signalApproveNFTs", async () => { + const nftId0 = 21 + const nftId1 = 22 + + await expect(gmxFloor.connect(user0).signalApproveNFTs(nft0.address, user2.address, [nftId0, nftId1])) + .to.be.revertedWith("TokenManager: forbidden") + + await gmxFloor.connect(wallet).signalApproveNFTs(nft0.address, user2.address, [nftId0, nftId1]) + }) + + it("signApproveNFTs", async () => { + const nftId0 = 21 + const nftId1 = 22 + + await expect(gmxFloor.connect(user0).signApproveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(signer2).signApproveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await gmxFloor.connect(wallet).signalApproveNFTs(nft0.address, user2.address, [nftId0, nftId1]) + + await expect(gmxFloor.connect(user0).signApproveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await gmxFloor.connect(signer2).signApproveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1) + + await expect(gmxFloor.connect(signer2).signApproveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: already signed") + + await gmxFloor.connect(signer1).signApproveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1) + }) + + it("approveNFTs", async () => { + const nftId0 = 21 + const nftId1 = 22 + + await nft0.mint(gmxFloor.address, nftId0) + await nft0.mint(gmxFloor.address, nftId1) + + await expect(gmxFloor.connect(user0).approveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(wallet).approveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await gmxFloor.connect(wallet).signalApproveNFTs(nft0.address, user2.address, [nftId0, nftId1]) + + await expect(gmxFloor.connect(wallet).approveNFTs(nft1.address, user2.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(wallet).approveNFTs(nft0.address, user0.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(wallet).approveNFTs(nft0.address, user2.address, [nftId0, nftId1 + 1], 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(wallet).approveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: action not authorized") + + await gmxFloor.connect(signer0).signApproveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1) + + await expect(gmxFloor.connect(wallet).approveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1)) + .to.be.revertedWith("TokenManager: insufficient authorization") + + await gmxFloor.connect(signer2).signApproveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1) + + await expect(nft0.connect(user2).transferFrom(gmxFloor.address, user1.address, nftId0)) + .to.be.revertedWith("ERC721: transfer caller is not owner nor approved") + + await gmxFloor.connect(wallet).approveNFTs(nft0.address, user2.address, [nftId0, nftId1], 1) + + expect(await nft0.balanceOf(user1.address)).eq(0) + expect(await nft0.balanceOf(gmxFloor.address)).eq(2) + expect(await nft0.ownerOf(nftId0)).eq(gmxFloor.address) + expect(await nft0.ownerOf(nftId1)).eq(gmxFloor.address) + + await nft0.connect(user2).transferFrom(gmxFloor.address, user1.address, nftId0) + + expect(await nft0.balanceOf(user1.address)).eq(1) + expect(await nft0.balanceOf(gmxFloor.address)).eq(1) + expect(await nft0.ownerOf(nftId0)).eq(user1.address) + expect(await nft0.ownerOf(nftId1)).eq(gmxFloor.address) + + await nft0.connect(user2).transferFrom(gmxFloor.address, user1.address, nftId1) + + expect(await nft0.balanceOf(user1.address)).eq(2) + expect(await nft0.balanceOf(gmxFloor.address)).eq(0) + expect(await nft0.ownerOf(nftId0)).eq(user1.address) + expect(await nft0.ownerOf(nftId1)).eq(user1.address) + }) + + it("signalSetAdmin", async () => { + await expect(gmxFloor.connect(user0).signalSetAdmin(timelock.address, user1.address)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(wallet).signalSetAdmin(timelock.address, user1.address)) + .to.be.revertedWith("TokenManager: forbidden") + + await gmxFloor.connect(signer0).signalSetAdmin(timelock.address, user1.address) + }) + + it("signSetAdmin", async () => { + await expect(gmxFloor.connect(user0).signSetAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(wallet).signSetAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(signer1).signSetAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await gmxFloor.connect(signer1).signalSetAdmin(timelock.address, user1.address) + + await expect(gmxFloor.connect(user0).signSetAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(signer1).signSetAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: already signed") + + await gmxFloor.connect(signer2).signSetAdmin(timelock.address, user1.address, 1) + + await expect(gmxFloor.connect(signer2).signSetAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: already signed") + }) + + it("setAdmin", async () => { + await expect(gmxFloor.connect(user0).setAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(wallet).setAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: forbidden") + + await expect(gmxFloor.connect(signer0).setAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await gmxFloor.connect(signer0).signalSetAdmin(timelock.address, user1.address) + + await expect(gmxFloor.connect(signer0).setAdmin(user0.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(signer0).setAdmin(timelock.address, user0.address, 1)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(signer0).setAdmin(timelock.address, user1.address, 2)) + .to.be.revertedWith("TokenManager: action not signalled") + + await expect(gmxFloor.connect(signer0).setAdmin(timelock.address, user1.address, 1)) + .to.be.revertedWith("TokenManager: insufficient authorization") + + await gmxFloor.connect(signer2).signSetAdmin(timelock.address, user1.address, 1) + + expect(await timelock.admin()).eq(wallet.address) + await gmxFloor.connect(signer2).setAdmin(timelock.address, user1.address, 1) + expect(await timelock.admin()).eq(user1.address) + }) +}) diff --git a/test/oracles/FastPriceFeed.js b/test/oracles/FastPriceFeed.js new file mode 100644 index 00000000..145f6c29 --- /dev/null +++ b/test/oracles/FastPriceFeed.js @@ -0,0 +1,344 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, bigNumberify, getBlockTime, increaseTime, + mineBlock, reportGasUsed, newWallet, getPriceBitArray } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") + +use(solidity) + +function getExpandedPrice(price) { + return bigNumberify(price).mul(expandDecimals(1, 30)).div(expandDecimals(1, 3)) +} + +describe("FastPriceFeed", function () { + const provider = waffle.provider + const { AddressZero } = ethers.constants + + const [wallet, rewardRouter, user0, user1, user2, user3, signer0, signer1] = provider.getWallets() + let bnb + let btc + let eth + let fastPriceFeed + + beforeEach(async () => { + bnb = await deployContract("Token", []) + btc = await deployContract("Token", []) + eth = await deployContract("Token", []) + fastPriceFeed = await deployContract("FastPriceFeed", [5 * 60, 250]) + await fastPriceFeed.initialize(2, [signer0.address, signer1.address]) + }) + + it("inits", async () => { + expect(await fastPriceFeed.gov()).eq(wallet.address) + expect(await fastPriceFeed.priceDuration()).eq(5 * 60) + expect(await fastPriceFeed.minAuthorizations()).eq(2) + expect(await fastPriceFeed.isSigner(wallet.address)).eq(false) + expect(await fastPriceFeed.isSigner(signer0.address)).eq(true) + expect(await fastPriceFeed.isSigner(signer1.address)).eq(true) + + await expect(fastPriceFeed.initialize(2, [signer0.address, signer1.address])) + .to.be.revertedWith("FastPriceFeed: already initialized") + }) + + it("setPriceDuration", async () => { + await expect(fastPriceFeed.connect(user0).setVolBasisPoints(20)) + .to.be.revertedWith("Governable: forbidden") + + await fastPriceFeed.setGov(user0.address) + + expect(await fastPriceFeed.volBasisPoints()).eq(0) + await fastPriceFeed.connect(user0).setVolBasisPoints(20) + expect(await fastPriceFeed.volBasisPoints()).eq(20) + }) + + it("setPriceDuration", async () => { + await expect(fastPriceFeed.connect(user0).setPriceDuration(30 * 60)) + .to.be.revertedWith("Governable: forbidden") + + await fastPriceFeed.setGov(user0.address) + + await expect(fastPriceFeed.connect(user0).setPriceDuration(31 * 60)) + .to.be.revertedWith("FastPriceFeed: invalid _priceDuration") + + expect(await fastPriceFeed.priceDuration()).eq(5 * 60) + await fastPriceFeed.connect(user0).setPriceDuration(30 * 60) + expect(await fastPriceFeed.priceDuration()).eq(30 * 60) + }) + + it("setIsSpreadEnabled", async () => { + await expect(fastPriceFeed.connect(user0).setIsSpreadEnabled(true)) + .to.be.revertedWith("Governable: forbidden") + + await fastPriceFeed.setGov(user0.address) + + expect(await fastPriceFeed.isSpreadEnabled()).eq(false) + expect(await fastPriceFeed.favorFastPrice()).eq(true) + await fastPriceFeed.connect(user0).setIsSpreadEnabled(true) + expect(await fastPriceFeed.isSpreadEnabled()).eq(true) + expect(await fastPriceFeed.favorFastPrice()).eq(false) + }) + + it("setPrices", async () => { + await expect(fastPriceFeed.connect(user0).setPrices([btc.address, eth.address, bnb.address], [expandDecimals(60000, 30), expandDecimals(5000, 30), expandDecimals(700, 30)])) + .to.be.revertedWith("Governable: forbidden") + + await fastPriceFeed.setGov(user0.address) + + expect(await fastPriceFeed.lastUpdatedAt()).eq(0) + await fastPriceFeed.connect(user0).setPrices([btc.address, eth.address, bnb.address], [expandDecimals(60000, 30), expandDecimals(5000, 30), expandDecimals(700, 30)]) + + const blockTime = await getBlockTime(provider) + + expect(await fastPriceFeed.prices(btc.address)).eq(expandDecimals(60000, 30)) + expect(await fastPriceFeed.prices(eth.address)).eq(expandDecimals(5000, 30)) + expect(await fastPriceFeed.prices(bnb.address)).eq(expandDecimals(700, 30)) + + expect(await fastPriceFeed.lastUpdatedAt()).eq(blockTime) + }) + + it("favorFastPrice", async () => { + await expect(fastPriceFeed.connect(user0).disableFastPrice()) + .to.be.revertedWith("FastPriceFeed: forbidden") + await expect(fastPriceFeed.connect(user1).disableFastPrice()) + .to.be.revertedWith("FastPriceFeed: forbidden") + + expect(await fastPriceFeed.favorFastPrice()).eq(true) + expect(await fastPriceFeed.disableFastPriceVotes(signer0.address)).eq(false) + expect(await fastPriceFeed.disableFastPriceVoteCount()).eq(0) + + await fastPriceFeed.connect(signer0).disableFastPrice() + + expect(await fastPriceFeed.favorFastPrice()).eq(true) + expect(await fastPriceFeed.disableFastPriceVotes(signer0.address)).eq(true) + expect(await fastPriceFeed.disableFastPriceVoteCount()).eq(1) + + await expect(fastPriceFeed.connect(signer0).disableFastPrice()) + .to.be.revertedWith("FastPriceFeed: already voted") + + expect(await fastPriceFeed.favorFastPrice()).eq(true) + expect(await fastPriceFeed.disableFastPriceVotes(signer1.address)).eq(false) + expect(await fastPriceFeed.disableFastPriceVoteCount()).eq(1) + + await fastPriceFeed.connect(signer1).disableFastPrice() + + expect(await fastPriceFeed.favorFastPrice()).eq(false) + expect(await fastPriceFeed.disableFastPriceVotes(signer1.address)).eq(true) + expect(await fastPriceFeed.disableFastPriceVoteCount()).eq(2) + + await fastPriceFeed.connect(signer1).enableFastPrice() + + expect(await fastPriceFeed.favorFastPrice()).eq(true) + expect(await fastPriceFeed.disableFastPriceVotes(signer1.address)).eq(false) + expect(await fastPriceFeed.disableFastPriceVoteCount()).eq(1) + + await expect(fastPriceFeed.connect(signer1).enableFastPrice()) + .to.be.revertedWith("FastPriceFeed: already enabled") + }) + + it("getPrice", async () => { + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(800) + await fastPriceFeed.setPrices([bnb.address], [801]) + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(801) + await fastPriceFeed.setPrices([bnb.address], [900]) + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(820) + expect(await fastPriceFeed.getPrice(bnb.address, 800, false)).eq(800) + await fastPriceFeed.setPrices([bnb.address], [700]) + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(800) + expect(await fastPriceFeed.getPrice(bnb.address, 800, false)).eq(780) + + await fastPriceFeed.setPrices([bnb.address], [900]) + + await increaseTime(provider, 200) + await mineBlock(provider) + + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(820) + + await increaseTime(provider, 110) + await mineBlock(provider) + + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(800) + + await fastPriceFeed.setPrices([bnb.address], [810]) + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(810) + expect(await fastPriceFeed.getPrice(bnb.address, 800, false)).eq(810) + + await fastPriceFeed.setPrices([bnb.address], [790]) + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(790) + expect(await fastPriceFeed.getPrice(bnb.address, 800, false)).eq(790) + + await fastPriceFeed.setVolBasisPoints(20) + + await fastPriceFeed.setPrices([bnb.address], [810]) + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(810) + expect(await fastPriceFeed.getPrice(bnb.address, 800, false)).eq(808) // 810 * (100 - 0.2)% + + await fastPriceFeed.setPrices([bnb.address], [790]) + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(791) // 790 * (100 + 0.2)% + expect(await fastPriceFeed.getPrice(bnb.address, 800, false)).eq(790) + + await fastPriceFeed.connect(signer0).disableFastPrice() + await fastPriceFeed.connect(signer1).disableFastPrice() + + await fastPriceFeed.setPrices([bnb.address], [810]) + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(810) + expect(await fastPriceFeed.getPrice(bnb.address, 800, false)).eq(800) + + await fastPriceFeed.setPrices([bnb.address], [790]) + expect(await fastPriceFeed.getPrice(bnb.address, 800, true)).eq(800) + expect(await fastPriceFeed.getPrice(bnb.address, 800, false)).eq(790) + }) + + it("setTokens", async () => { + const token1 = await deployContract("Token", []) + const token2 = await deployContract("Token", []) + + await expect(fastPriceFeed.connect(user0).setTokens([token1.address, token2.address])) + .to.be.revertedWith("Governable: forbidden") + + await fastPriceFeed.setGov(user0.address) + + await fastPriceFeed.connect(user0).setTokens([token1.address, token2.address]) + + expect(await fastPriceFeed.tokens(0)).eq(token1.address) + expect(await fastPriceFeed.tokens(1)).eq(token2.address) + }) + + it("setCompactedPrices", async () => { + const price1 = "2009991111" + const price2 = "1004445555" + const price3 = "123" + const price4 = "4567" + const price5 = "891011" + const price6 = "1213141516" + const price7 = "234" + const price8 = "5678" + const price9 = "910910" + const price10 = "10" + + const token1 = await deployContract("Token", []) + const token2 = await deployContract("Token", []) + const token3 = await deployContract("Token", []) + const token4 = await deployContract("Token", []) + const token5 = await deployContract("Token", []) + const token6 = await deployContract("Token", []) + const token7 = await deployContract("Token", []) + const token8 = await deployContract("Token", []) + const token9 = await deployContract("Token", []) + const token10 = await deployContract("Token", []) + + await fastPriceFeed.connect(wallet).setTokens([token1.address, token2.address]) + + let priceBitArray = getPriceBitArray([price1, price2]) + + expect(priceBitArray.length).eq(1) + + await expect(fastPriceFeed.connect(user0).setCompactedPrices(priceBitArray)) + .to.be.revertedWith("Governable: forbidden") + + await fastPriceFeed.setGov(user0.address) + + expect(await fastPriceFeed.lastUpdatedAt()).eq(0) + + await fastPriceFeed.connect(user0).setCompactedPrices(priceBitArray) + const blockTime = await getBlockTime(provider) + + expect(await fastPriceFeed.prices(token1.address)).eq(getExpandedPrice(price1)) + expect(await fastPriceFeed.prices(token2.address)).eq(getExpandedPrice(price2)) + + expect(await fastPriceFeed.lastUpdatedAt()).eq(blockTime) + + await fastPriceFeed.connect(user0).setTokens([ + token1.address, token2.address, token3.address, token4.address, + token5.address, token6.address, token7.address]) + + priceBitArray = getPriceBitArray([ + price1, price2, price3, price4, + price5, price6, price7]) + + expect(priceBitArray.length).eq(1) + + await fastPriceFeed.connect(user0).setCompactedPrices(priceBitArray) + + const p1 = await fastPriceFeed.prices(token1.address) + expect(ethers.utils.formatUnits(p1, 30)).eq("2009991.111") + expect(await fastPriceFeed.prices(token1.address)).eq("2009991111000000000000000000000000000") + expect(await fastPriceFeed.prices(token2.address)).eq(getExpandedPrice(price2)) + expect(await fastPriceFeed.prices(token3.address)).eq(getExpandedPrice(price3)) + expect(await fastPriceFeed.prices(token4.address)).eq(getExpandedPrice(price4)) + expect(await fastPriceFeed.prices(token5.address)).eq(getExpandedPrice(price5)) + expect(await fastPriceFeed.prices(token6.address)).eq(getExpandedPrice(price6)) + expect(await fastPriceFeed.prices(token7.address)).eq(getExpandedPrice(price7)) + + await fastPriceFeed.connect(user0).setTokens([ + token1.address, token2.address, token3.address, token4.address, + token5.address, token6.address, token7.address, token8.address]) + + priceBitArray = getPriceBitArray([ + price1, price2, price3, price4, + price5, price6, price7, price8]) + + expect(priceBitArray.length).eq(1) + + await fastPriceFeed.connect(user0).setCompactedPrices(priceBitArray) + + expect(await fastPriceFeed.prices(token1.address)).eq(getExpandedPrice(price1)) + expect(await fastPriceFeed.prices(token2.address)).eq(getExpandedPrice(price2)) + expect(await fastPriceFeed.prices(token3.address)).eq(getExpandedPrice(price3)) + expect(await fastPriceFeed.prices(token4.address)).eq(getExpandedPrice(price4)) + expect(await fastPriceFeed.prices(token5.address)).eq(getExpandedPrice(price5)) + expect(await fastPriceFeed.prices(token6.address)).eq(getExpandedPrice(price6)) + expect(await fastPriceFeed.prices(token7.address)).eq(getExpandedPrice(price7)) + expect(await fastPriceFeed.prices(token8.address)).eq(getExpandedPrice(price8)) + + await fastPriceFeed.connect(user0).setTokens([ + token1.address, token2.address, token3.address, token4.address, + token5.address, token6.address, token7.address, token8.address, + token9.address]) + + priceBitArray = getPriceBitArray([ + price1, price2, price3, price4, + price5, price6, price7, price8, + price9]) + + expect(priceBitArray.length).eq(2) + + await fastPriceFeed.connect(user0).setCompactedPrices(priceBitArray) + + expect(await fastPriceFeed.prices(token1.address)).eq(getExpandedPrice(price1)) + expect(await fastPriceFeed.prices(token2.address)).eq(getExpandedPrice(price2)) + expect(await fastPriceFeed.prices(token3.address)).eq(getExpandedPrice(price3)) + expect(await fastPriceFeed.prices(token4.address)).eq(getExpandedPrice(price4)) + expect(await fastPriceFeed.prices(token5.address)).eq(getExpandedPrice(price5)) + expect(await fastPriceFeed.prices(token6.address)).eq(getExpandedPrice(price6)) + expect(await fastPriceFeed.prices(token7.address)).eq(getExpandedPrice(price7)) + expect(await fastPriceFeed.prices(token8.address)).eq(getExpandedPrice(price8)) + expect(await fastPriceFeed.prices(token9.address)).eq(getExpandedPrice(price9)) + + await fastPriceFeed.connect(user0).setTokens([ + token1.address, token2.address, token3.address, token4.address, + token5.address, token6.address, token7.address, token8.address, + token9.address, token10.address]) + + priceBitArray = getPriceBitArray([ + price1, price2, price3, price4, + price5, price6, price7, price8, + price9, price10]) + + expect(priceBitArray.length).eq(2) + + await fastPriceFeed.connect(user0).setCompactedPrices(priceBitArray) + + expect(await fastPriceFeed.prices(token1.address)).eq(getExpandedPrice(price1)) + expect(await fastPriceFeed.prices(token2.address)).eq(getExpandedPrice(price2)) + expect(await fastPriceFeed.prices(token3.address)).eq(getExpandedPrice(price3)) + expect(await fastPriceFeed.prices(token4.address)).eq(getExpandedPrice(price4)) + expect(await fastPriceFeed.prices(token5.address)).eq(getExpandedPrice(price5)) + expect(await fastPriceFeed.prices(token6.address)).eq(getExpandedPrice(price6)) + expect(await fastPriceFeed.prices(token7.address)).eq(getExpandedPrice(price7)) + expect(await fastPriceFeed.prices(token8.address)).eq(getExpandedPrice(price8)) + expect(await fastPriceFeed.prices(token9.address)).eq(getExpandedPrice(price9)) + expect(await fastPriceFeed.prices(token10.address)).eq(getExpandedPrice(price10)) + }) +}) diff --git a/test/peripherals/BatchSender.js b/test/peripherals/BatchSender.js new file mode 100644 index 00000000..ac850f3b --- /dev/null +++ b/test/peripherals/BatchSender.js @@ -0,0 +1,47 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") + +use(solidity) + +describe("BatchSender", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let batchSender + let gmt + + beforeEach(async () => { + batchSender = await deployContract("BatchSender", []) + gmt = await deployContract("GMT", [1500]) + await gmt.beginMigration() + }) + + it("send", async () => { + expect(await gmt.balanceOf(wallet.address)).eq(1500) + + expect(await gmt.balanceOf(user0.address)).eq(0) + expect(await gmt.balanceOf(user1.address)).eq(0) + expect(await gmt.balanceOf(user2.address)).eq(0) + expect(await gmt.balanceOf(user3.address)).eq(0) + + const accounts = [user0.address, user1.address, user2.address, user3.address] + const amounts = [100, 200, 300, 400] + + await expect(batchSender.connect(user0).send(gmt.address, accounts, amounts)) + .to.be.revertedWith("BatchSender: forbidden") + + await gmt.approve(batchSender.address, 1000) + + await expect(batchSender.connect(wallet).send(gmt.address, accounts, amounts)) + .to.be.revertedWith("GMT: forbidden msg.sender") + + await gmt.addMsgSender(batchSender.address) + await batchSender.send(gmt.address, accounts, amounts) + + expect(await gmt.balanceOf(user0.address)).eq(100) + expect(await gmt.balanceOf(user1.address)).eq(200) + expect(await gmt.balanceOf(user2.address)).eq(300) + expect(await gmt.balanceOf(user3.address)).eq(400) + expect(await gmt.balanceOf(wallet.address)).eq(500) + }) +}) diff --git a/test/peripherals/OrderBookReader.js b/test/peripherals/OrderBookReader.js new file mode 100644 index 00000000..a8a89d07 --- /dev/null +++ b/test/peripherals/OrderBookReader.js @@ -0,0 +1,164 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../shared/utilities") +const { initVault } = require("../core/Vault/helpers") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") + +use(solidity) + +const PRICE_PRECISION = ethers.BigNumber.from(10).pow(30); + +describe("OrderBookReader", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let orderBook; + let reader; + let dai; + let bnb; + let vault; + let usdg; + let router; + let vaultPriceFeed; + + beforeEach(async () => { + dai = await deployContract("Token", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(50000)) + + bnb = await deployContract("Token", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed); + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + + orderBook = await deployContract("OrderBook", []) + await router.addPlugin(orderBook.address); + await router.connect(user0).approvePlugin(orderBook.address); + await orderBook.initialize( + router.address, + vault.address, + bnb.address, + usdg.address, + 400000, + expandDecimals(5, 30) // minPurchseTokenAmountUsd + ); + reader = await deployContract("OrderBookReader", []) + + await dai.mint(user0.address, expandDecimals(10000000, 18)) + await dai.connect(user0).approve(router.address, expandDecimals(1000000, 18)) + + await btc.mint(user0.address, expandDecimals(100, 8)) + await btc.connect(user0).approve(router.address, expandDecimals(100, 8)) + }) + + function createSwapOrder(toToken = bnb.address) { + const executionFee = 500000; + + return orderBook.connect(user0).createSwapOrder( + [dai.address, toToken], + expandDecimals(1000, 18), + expandDecimals(990, 18), + expandDecimals(1, 30), + true, + executionFee, + false, + true, + {value: executionFee} + ); + } + + function createIncreaseOrder(sizeDelta) { + const executionFee = 500000; + + return orderBook.connect(user0).createIncreaseOrder( + [btc.address], + expandDecimals(1, 8), + btc.address, + 0, + sizeDelta, + btc.address, // collateralToken + true, // isLong + toUsd(53000), // triggerPrice + false, // triggerAboveThreshold + executionFee, + false, // shouldWrap + { value: executionFee } + ); + } + + function createDecreaseOrder(sizeDelta = toUsd(100000)) { + const executionFee = 500000; + return orderBook.connect(user0).createDecreaseOrder( + btc.address, // indexToken + sizeDelta, // sizeDelta + btc.address, // collateralToken + toUsd(35000), // collateralDelta + true, // isLong + toUsd(53000), // triggerPrice + true, // triggetAboveThreshold + { value: executionFee } + ); + } + + function unflattenOrders([uintProps, addressProps], uintLength, addressLength) { + const count = uintProps.length / uintLength; + + const ret = []; + for (let i = 0; i < count; i++) { + const order = addressProps + .slice(addressLength * i, addressLength * (i + 1)) + .concat( + uintProps.slice(uintLength * i, uintLength * (i + 1)) + ); + ret.push(order); + } + return ret; + } + + it("getIncreaseOrders", async () => { + await createIncreaseOrder(toUsd(100000)); + await createIncreaseOrder(toUsd(200000)); + + const [order1, order2] = unflattenOrders(await reader.getIncreaseOrders(orderBook.address, user0.address, [0, 1]), 5, 3); + + expect(order1[2]).to.be.equal(btc.address) + expect(order1[4]).to.be.equal(toUsd(100000)) + + expect(order2[2]).to.be.equal(btc.address) + expect(order2[4]).to.be.equal(toUsd(200000)) + }); + + it("getDecreaseOrders", async () => { + await createDecreaseOrder(toUsd(100000)); + await createDecreaseOrder(toUsd(200000)); + + const [order1, order2] = unflattenOrders(await reader.getDecreaseOrders(orderBook.address, user0.address, [0, 1]), 5, 2); + + expect(order1[1]).to.be.equal(btc.address) + expect(order1[3]).to.be.equal(toUsd(100000)) + + expect(order2[1]).to.be.equal(btc.address) + expect(order2[3]).to.be.equal(toUsd(200000)) + }); + + it("getSwapOrders", async () => { + await createSwapOrder(bnb.address); + await createSwapOrder(btc.address); + + const [order1, order2] = unflattenOrders(await reader.getSwapOrders(orderBook.address, user0.address, [0, 1]), 4, 3); + + expect(order1[0]).to.be.equal(dai.address); + expect(order1[1]).to.be.equal(bnb.address); + + expect(order2[0]).to.be.equal(dai.address); + expect(order2[1]).to.be.equal(btc.address); + }) +}); \ No newline at end of file diff --git a/test/peripherals/Reader.js b/test/peripherals/Reader.js new file mode 100644 index 00000000..ce400408 --- /dev/null +++ b/test/peripherals/Reader.js @@ -0,0 +1,69 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("../core/Vault/helpers") + +use(solidity) + +describe("Reader", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let vault + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + let reader + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + reader = await deployContract("Reader", []) + + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + }) + + it("getVaultTokenInfo", async () => { + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + const results = await reader.getVaultTokenInfo(vault.address, bnb.address, expandDecimals(1, 30), [btc.address, dai.address]) + expect(await results.length).eq(20) + }) +}) diff --git a/test/peripherals/Timelock.js b/test/peripherals/Timelock.js new file mode 100644 index 00000000..186ff5a2 --- /dev/null +++ b/test/peripherals/Timelock.js @@ -0,0 +1,734 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") +const { initVault } = require("../core/Vault/helpers") + +use(solidity) + +describe("Timelock", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3, tokenManager] = provider.getWallets() + let vault + let vaultPriceFeed + let usdg + let router + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let dai + let daiPriceFeed + let distributor0 + let yieldTracker0 + let timelock + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [usdg.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await bnb.mint(distributor0.address, 5000) + await usdg.setYieldTrackers([yieldTracker0.address]) + + await vault.setPriceFeed(user3.address) + + timelock = await deployContract("Timelock", [5 * 24 * 60 * 60, tokenManager.address, 1000]) + await vault.setGov(timelock.address) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + await vaultPriceFeed.setGov(timelock.address) + await router.setGov(timelock.address) + }) + + it("inits", async () => { + expect(await usdg.gov()).eq(wallet.address) + expect(await usdg.vaults(vault.address)).eq(true) + expect(await usdg.vaults(user0.address)).eq(false) + + expect(await vault.gov()).eq(timelock.address) + expect(await vault.isInitialized()).eq(true) + expect(await vault.router()).eq(router.address) + expect(await vault.usdg()).eq(usdg.address) + expect(await vault.liquidationFeeUsd()).eq(toUsd(5)) + expect(await vault.fundingRateFactor()).eq(600) + + expect(await timelock.admin()).eq(wallet.address) + expect(await timelock.buffer()).eq(5 * 24 * 60 * 60) + expect(await timelock.tokenManager()).eq(tokenManager.address) + expect(await timelock.maxTokenSupply()).eq(1000) + + await expect(deployContract("Timelock", [5 * 24 * 60 * 60 + 1, tokenManager.address, 1000])) + .to.be.revertedWith("Timelock: invalid _buffer") + }) + + it("setTokenConfig", async () => { + await timelock.connect(wallet).signalSetPriceFeed(vault.address, vaultPriceFeed.address) + await increaseTime(provider, 5 * 24 * 60 * 60 + 10) + await mineBlock(provider) + await timelock.connect(wallet).setPriceFeed(vault.address, vaultPriceFeed.address) + + await bnbPriceFeed.setLatestAnswer(500) + + await expect(timelock.connect(user0).setTokenConfig( + vault.address, + bnb.address, + 100, + 200, + 1000 + )).to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).setTokenConfig( + vault.address, + bnb.address, + 100, + 200, + 1000 + )).to.be.revertedWith("Timelock: token not yet whitelisted") + + await timelock.connect(wallet).signalVaultSetTokenConfig( + vault.address, + bnb.address, // _token + 12, // _tokenDecimals + 7000, // _tokenWeight + 300, // _minProfitBps + 5000, // _maxUsdgAmount + false, // _isStable + true // isShortable + ) + + await increaseTime(provider, 5 * 24 * 60 *60) + await mineBlock(provider) + + await timelock.connect(wallet).vaultSetTokenConfig( + vault.address, + bnb.address, // _token + 12, // _tokenDecimals + 7000, // _tokenWeight + 300, // _minProfitBps + 5000, // _maxUsdgAmount + false, // _isStable + true // isShortable + ) + + expect(await vault.whitelistedTokenCount()).eq(1) + expect(await vault.totalTokenWeights()).eq(7000) + expect(await vault.whitelistedTokens(bnb.address)).eq(true) + expect(await vault.tokenDecimals(bnb.address)).eq(12) + expect(await vault.tokenWeights(bnb.address)).eq(7000) + expect(await vault.minProfitBasisPoints(bnb.address)).eq(300) + expect(await vault.maxUsdgAmounts(bnb.address)).eq(5000) + expect(await vault.stableTokens(bnb.address)).eq(false) + expect(await vault.shortableTokens(bnb.address)).eq(true) + + await timelock.connect(wallet).setTokenConfig( + vault.address, + bnb.address, + 100, // _tokenWeight + 200, // _minProfitBps + 1000 // _maxUsdgAmount + ) + + expect(await vault.whitelistedTokenCount()).eq(1) + expect(await vault.totalTokenWeights()).eq(100) + expect(await vault.whitelistedTokens(bnb.address)).eq(true) + expect(await vault.tokenDecimals(bnb.address)).eq(12) + expect(await vault.tokenWeights(bnb.address)).eq(100) + expect(await vault.minProfitBasisPoints(bnb.address)).eq(200) + expect(await vault.maxUsdgAmounts(bnb.address)).eq(1000) + expect(await vault.stableTokens(bnb.address)).eq(false) + expect(await vault.shortableTokens(bnb.address)).eq(true) + }) + + it("setBuffer", async () => { + const timelock0 = await deployContract("Timelock", [3 * 24 * 60 * 60, tokenManager.address, 1000]) + await expect(timelock0.connect(user0).setBuffer(3 * 24 * 60 * 60 - 10)) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock0.connect(wallet).setBuffer(5 * 24 * 60 * 60 + 10)) + .to.be.revertedWith("Timelock: invalid _buffer") + + await expect(timelock0.connect(wallet).setBuffer(3 * 24 * 60 * 60 - 10)) + .to.be.revertedWith("Timelock: buffer cannot be decreased") + + expect(await timelock0.buffer()).eq(3 * 24 * 60 * 60) + await timelock0.connect(wallet).setBuffer(3 * 24 * 60 * 60 + 10) + expect(await timelock0.buffer()).eq(3 * 24 * 60 * 60 + 10) + }) + + it("mint", async () => { + const gmx = await deployContract("GMX", []) + await expect(timelock.connect(user0).mint(gmx.address, 900)) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).mint(gmx.address, 900)) + .to.be.revertedWith("BaseToken: forbidden") + + await gmx.setGov(timelock.address) + + expect(await gmx.isMinter(timelock.address)).eq(false) + expect(await gmx.balanceOf(tokenManager.address)).eq(0) + + await timelock.connect(wallet).mint(gmx.address, 900) + + expect(await gmx.isMinter(timelock.address)).eq(true) + expect(await gmx.balanceOf(tokenManager.address)).eq(900) + + await expect(timelock.connect(wallet).mint(gmx.address, 101)) + .to.be.revertedWith("Timelock: maxTokenSupply exceeded") + }) + + it("setIsAmmEnabled", async () => { + await expect(timelock.connect(user0).setIsAmmEnabled(vaultPriceFeed.address, false)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await vaultPriceFeed.isAmmEnabled()).eq(true) + await timelock.connect(wallet).setIsAmmEnabled(vaultPriceFeed.address, false) + expect(await vaultPriceFeed.isAmmEnabled()).eq(false) + }) + + it("setMaxStrictPriceDeviation", async () => { + await expect(timelock.connect(user0).setMaxStrictPriceDeviation(vaultPriceFeed.address, 100)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await vaultPriceFeed.maxStrictPriceDeviation()).eq(0) + await timelock.connect(wallet).setMaxStrictPriceDeviation(vaultPriceFeed.address, 100) + expect(await vaultPriceFeed.maxStrictPriceDeviation()).eq(100) + }) + + it("setPriceSampleSpace", async () => { + await expect(timelock.connect(user0).setPriceSampleSpace(vaultPriceFeed.address, 0)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await vaultPriceFeed.priceSampleSpace()).eq(3) + await timelock.connect(wallet).setPriceSampleSpace(vaultPriceFeed.address, 1) + expect(await vaultPriceFeed.priceSampleSpace()).eq(1) + }) + + it("setIsSwapEnabled", async () => { + await expect(timelock.connect(user0).setIsSwapEnabled(vault.address, false)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await vault.isSwapEnabled()).eq(true) + await timelock.connect(wallet).setIsSwapEnabled(vault.address, false) + expect(await vault.isSwapEnabled()).eq(false) + }) + + it("setIsLeverageEnabled", async () => { + await expect(timelock.connect(user0).setIsLeverageEnabled(vault.address, false)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await vault.isLeverageEnabled()).eq(true) + await timelock.connect(wallet).setIsLeverageEnabled(vault.address, false) + expect(await vault.isLeverageEnabled()).eq(false) + }) + + it("setBufferAmount", async () => { + await expect(timelock.connect(user0).setBufferAmount(vault.address, bnb.address, 100)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await vault.bufferAmounts(bnb.address)).eq(0) + await timelock.connect(wallet).setBufferAmount(vault.address, bnb.address, 100) + expect(await vault.bufferAmounts(bnb.address)).eq(100) + }) + + it("setMaxGasPrice", async () => { + await expect(timelock.connect(user0).setMaxGasPrice(vault.address, 7000000000)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await vault.maxGasPrice()).eq(0) + await timelock.connect(wallet).setMaxGasPrice(vault.address, 7000000000) + expect(await vault.maxGasPrice()).eq(7000000000) + }) + + it("transferIn", async () => { + await bnb.mint(user1.address, 1000) + await expect(timelock.connect(user0).transferIn(user1.address, bnb.address, 1000)) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).transferIn(user1.address, bnb.address, 1000)) + .to.be.revertedWith("ERC20: transfer amount exceeds allowance") + + await bnb.connect(user1).approve(timelock.address, 1000) + + expect(await bnb.balanceOf(user1.address)).eq(1000) + expect(await bnb.balanceOf(timelock.address)).eq(0) + await timelock.connect(wallet).transferIn(user1.address, bnb.address, 1000) + expect(await bnb.balanceOf(user1.address)).eq(0) + expect(await bnb.balanceOf(timelock.address)).eq(1000) + }) + + it("approve", async () => { + await expect(timelock.connect(user0).approve(dai.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).approve(dai.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(user0).signalApprove(dai.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: forbidden") + + await timelock.connect(wallet).signalApprove(dai.address, user1.address, expandDecimals(100, 18)) + + await expect(timelock.connect(wallet).approve(dai.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 4 * 24 * 60 * 60) + await mineBlock(provider) + + await expect(timelock.connect(wallet).approve(dai.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 1 * 24 * 60 * 60 + 10) + await mineBlock(provider) + + await expect(timelock.connect(wallet).approve(bnb.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(wallet).approve(dai.address, user2.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(wallet).approve(dai.address, user1.address, expandDecimals(101, 18))) + .to.be.revertedWith("Timelock: action not signalled") + + await dai.mint(timelock.address, expandDecimals(150, 18)) + + expect(await dai.balanceOf(timelock.address)).eq(expandDecimals(150, 18)) + expect(await dai.balanceOf(user1.address)).eq(0) + + await expect(dai.connect(user1).transferFrom(timelock.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("ERC20: transfer amount exceeds allowance") + + await timelock.connect(wallet).approve(dai.address, user1.address, expandDecimals(100, 18)) + await expect(dai.connect(user2).transferFrom(timelock.address, user2.address, expandDecimals(100, 18))) + .to.be.revertedWith("ERC20: transfer amount exceeds allowance") + await dai.connect(user1).transferFrom(timelock.address, user1.address, expandDecimals(100, 18)) + + expect(await dai.balanceOf(timelock.address)).eq(expandDecimals(50, 18)) + expect(await dai.balanceOf(user1.address)).eq(expandDecimals(100, 18)) + + await expect(dai.connect(user1).transferFrom(timelock.address, user1.address, expandDecimals(1, 18))) + .to.be.revertedWith("ERC20: transfer amount exceeds allowance") + + await expect(timelock.connect(wallet).approve(dai.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: action not signalled") + + await timelock.connect(wallet).signalApprove(dai.address, user1.address, expandDecimals(100, 18)) + + await expect(timelock.connect(wallet).approve(dai.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: action time not yet passed") + + const action0 = ethers.utils.solidityKeccak256(["string", "address", "address", "uint256"], ["approve", bnb.address, user1.address, expandDecimals(100, 18)]) + const action1 = ethers.utils.solidityKeccak256(["string", "address", "address", "uint256"], ["approve", dai.address, user1.address, expandDecimals(100, 18)]) + + await expect(timelock.connect(user0).cancelAction(action0)) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).cancelAction(action0)) + .to.be.revertedWith("Timelock: invalid _action") + + await timelock.connect(wallet).cancelAction(action1) + + await expect(timelock.connect(wallet).approve(dai.address, user1.address, expandDecimals(100, 18))) + .to.be.revertedWith("Timelock: action not signalled") + }) + + it("setGov", async () => { + await expect(timelock.connect(user0).setGov(vault.address, user1.address)) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).setGov(vault.address, user1.address)) + .to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(user0).signalSetGov(vault.address, user1.address)) + .to.be.revertedWith("Timelock: forbidden") + + await timelock.connect(wallet).signalSetGov(vault.address, user1.address) + + await expect(timelock.connect(wallet).setGov(vault.address, user1.address)) + .to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 4 * 24 * 60 * 60) + await mineBlock(provider) + + await expect(timelock.connect(wallet).setGov(vault.address, user1.address)) + .to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 1 * 24 * 60 * 60 + 10) + await mineBlock(provider) + + await expect(timelock.connect(wallet).setGov(user2.address, user1.address)) + .to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(wallet).setGov(vault.address, user2.address)) + .to.be.revertedWith("Timelock: action not signalled") + + expect(await vault.gov()).eq(timelock.address) + await timelock.connect(wallet).setGov(vault.address, user1.address) + expect(await vault.gov()).eq(user1.address) + + await timelock.connect(wallet).signalSetGov(vault.address, user2.address) + + await expect(timelock.connect(wallet).setGov(vault.address, user2.address)) + .to.be.revertedWith("Timelock: action time not yet passed") + + const action0 = ethers.utils.solidityKeccak256(["string", "address", "address"], ["setGov", user1.address, user2.address]) + const action1 = ethers.utils.solidityKeccak256(["string", "address", "address"], ["setGov", vault.address, user2.address]) + + await expect(timelock.connect(wallet).cancelAction(action0)) + .to.be.revertedWith("Timelock: invalid _action") + + await timelock.connect(wallet).cancelAction(action1) + + await expect(timelock.connect(wallet).setGov(vault.address, user2.address)) + .to.be.revertedWith("Timelock: action not signalled") + }) + + it("setPriceFeed", async () => { + await expect(timelock.connect(user0).setPriceFeed(vault.address, user1.address)) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).setPriceFeed(vault.address, user1.address)) + .to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(user0).signalSetPriceFeed(vault.address, user1.address)) + .to.be.revertedWith("Timelock: forbidden") + + await timelock.connect(wallet).signalSetPriceFeed(vault.address, user1.address) + + await expect(timelock.connect(wallet).setPriceFeed(vault.address, user1.address)) + .to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 4 * 24 * 60 * 60) + await mineBlock(provider) + + await expect(timelock.connect(wallet).setPriceFeed(vault.address, user1.address)) + .to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 1 * 24 * 60 * 60 + 10) + await mineBlock(provider) + + await expect(timelock.connect(wallet).setPriceFeed(user2.address, user1.address)) + .to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(wallet).setPriceFeed(vault.address, user2.address)) + .to.be.revertedWith("Timelock: action not signalled") + + expect(await vault.priceFeed()).eq(user3.address) + await timelock.connect(wallet).setPriceFeed(vault.address, user1.address) + expect(await vault.priceFeed()).eq(user1.address) + + await timelock.connect(wallet).signalSetPriceFeed(vault.address, user2.address) + + await expect(timelock.connect(wallet).setPriceFeed(vault.address, user2.address)) + .to.be.revertedWith("Timelock: action time not yet passed") + + const action0 = ethers.utils.solidityKeccak256(["string", "address", "address"], ["setPriceFeed", user1.address, user2.address]) + const action1 = ethers.utils.solidityKeccak256(["string", "address", "address"], ["setPriceFeed", vault.address, user2.address]) + + await expect(timelock.connect(wallet).cancelAction(action0)) + .to.be.revertedWith("Timelock: invalid _action") + + await timelock.connect(wallet).cancelAction(action1) + + await expect(timelock.connect(wallet).setPriceFeed(vault.address, user2.address)) + .to.be.revertedWith("Timelock: action not signalled") + }) + + it("vaultSetTokenConfig", async () => { + await timelock.connect(wallet).signalSetPriceFeed(vault.address, vaultPriceFeed.address) + await increaseTime(provider, 5 * 24 * 60 * 60 + 10) + await mineBlock(provider) + await timelock.connect(wallet).setPriceFeed(vault.address, vaultPriceFeed.address) + + await daiPriceFeed.setLatestAnswer(1) + + await expect(timelock.connect(user0).vaultSetTokenConfig( + vault.address, + dai.address, // _token + 12, // _tokenDecimals + 7000, // _tokenWeight + 120, // _minProfitBps + 5000, // _maxUsdgAmount + true, // _isStable + false // isShortable + )).to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).vaultSetTokenConfig( + vault.address, + dai.address, // _token + 12, // _tokenDecimals + 7000, // _tokenWeight + 120, // _minProfitBps + 5000, // _maxUsdgAmount + true, // _isStable + false // isShortable + )).to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(user0).signalVaultSetTokenConfig( + vault.address, + dai.address, // _token + 12, // _tokenDecimals + 7000, // _tokenWeight + 120, // _minProfitBps + 5000, // _maxUsdgAmount + true, // _isStable + false // isShortable + )).to.be.revertedWith("Timelock: forbidden") + + await timelock.connect(wallet).signalVaultSetTokenConfig( + vault.address, + dai.address, // _token + 12, // _tokenDecimals + 7000, // _tokenWeight + 120, // _minProfitBps + 5000, // _maxUsdgAmount + true, // _isStable + false // isShortable + ) + + await expect(timelock.connect(wallet).vaultSetTokenConfig( + vault.address, + dai.address, // _token + 12, // _tokenDecimals + 7000, // _tokenWeight + 120, // _minProfitBps + 5000, // _maxUsdgAmount + true, // _isStable + false // isShortable + )).to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 4 * 24 * 60 * 60) + await mineBlock(provider) + + await expect(timelock.connect(wallet).vaultSetTokenConfig( + vault.address, + dai.address, // _token + 12, // _tokenDecimals + 7000, // _tokenWeight + 120, // _minProfitBps + 5000, // _maxUsdgAmount + true, // _isStable + false // isShortable + )).to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 1 * 24 * 60 * 60 + 10) + await mineBlock(provider) + + await expect(timelock.connect(wallet).vaultSetTokenConfig( + vault.address, + dai.address, // _token + 15, // _tokenDecimals + 7000, // _tokenWeight + 120, // _minProfitBps + 5000, // _maxUsdgAmount + true, // _isStable + false // isShortable + )).to.be.revertedWith("Timelock: action not signalled") + + expect(await vault.totalTokenWeights()).eq(0) + expect(await vault.whitelistedTokens(dai.address)).eq(false) + expect(await vault.tokenDecimals(dai.address)).eq(0) + expect(await vault.tokenWeights(dai.address)).eq(0) + expect(await vault.minProfitBasisPoints(dai.address)).eq(0) + expect(await vault.maxUsdgAmounts(dai.address)).eq(0) + expect(await vault.stableTokens(dai.address)).eq(false) + expect(await vault.shortableTokens(dai.address)).eq(false) + + await timelock.connect(wallet).vaultSetTokenConfig( + vault.address, + dai.address, // _token + 12, // _tokenDecimals + 7000, // _tokenWeight + 120, // _minProfitBps + 5000, // _maxUsdgAmount + true, // _isStable + false // isShortable + ) + + expect(await vault.totalTokenWeights()).eq(7000) + expect(await vault.whitelistedTokens(dai.address)).eq(true) + expect(await vault.tokenDecimals(dai.address)).eq(12) + expect(await vault.tokenWeights(dai.address)).eq(7000) + expect(await vault.minProfitBasisPoints(dai.address)).eq(120) + expect(await vault.maxUsdgAmounts(dai.address)).eq(5000) + expect(await vault.stableTokens(dai.address)).eq(true) + expect(await vault.shortableTokens(dai.address)).eq(false) + }) + + it("addPlugin", async () => { + await expect(timelock.connect(user0).addPlugin(router.address, user1.address)) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).addPlugin(router.address, user1.address)) + .to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(user0).signalAddPlugin(router.address, user1.address)) + .to.be.revertedWith("Timelock: forbidden") + + await timelock.connect(wallet).signalAddPlugin(router.address, user1.address) + + await expect(timelock.connect(wallet).addPlugin(router.address, user1.address)) + .to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 4 * 24 * 60 * 60) + await mineBlock(provider) + + await expect(timelock.connect(wallet).addPlugin(router.address, user1.address)) + .to.be.revertedWith("Timelock: action time not yet passed") + + await increaseTime(provider, 1 * 24 * 60 * 60 + 10) + await mineBlock(provider) + + await expect(timelock.connect(wallet).addPlugin(user2.address, user1.address)) + .to.be.revertedWith("Timelock: action not signalled") + + await expect(timelock.connect(wallet).addPlugin(router.address, user2.address)) + .to.be.revertedWith("Timelock: action not signalled") + + expect(await router.plugins(user1.address)).eq(false) + await timelock.connect(wallet).addPlugin(router.address, user1.address) + expect(await router.plugins(user1.address)).eq(true) + + await timelock.connect(wallet).signalAddPlugin(router.address, user2.address) + + await expect(timelock.connect(wallet).addPlugin(router.address, user2.address)) + .to.be.revertedWith("Timelock: action time not yet passed") + + const action0 = ethers.utils.solidityKeccak256(["string", "address", "address"], ["addPlugin", user1.address, user2.address]) + const action1 = ethers.utils.solidityKeccak256(["string", "address", "address"], ["addPlugin", router.address, user2.address]) + + await expect(timelock.connect(wallet).cancelAction(action0)) + .to.be.revertedWith("Timelock: invalid _action") + + await timelock.connect(wallet).cancelAction(action1) + + await expect(timelock.connect(wallet).addPlugin(router.address, user2.address)) + .to.be.revertedWith("Timelock: action not signalled") + }) + + it("addExcludedToken", async () => { + const gmx = await deployContract("GMX", []) + await expect(timelock.connect(user0).addExcludedToken(gmx.address)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await timelock.excludedTokens(gmx.address)).eq(false) + await timelock.connect(wallet).addExcludedToken(gmx.address) + expect(await timelock.excludedTokens(gmx.address)).eq(true) + }) + + it("setInPrivateTransferMode", async () => { + const gmx = await deployContract("GMX", []) + await gmx.setMinter(wallet.address, true) + await gmx.mint(user0.address, 100) + await expect(timelock.connect(user0).setInPrivateTransferMode(gmx.address, true)) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).setInPrivateTransferMode(gmx.address, true)) + .to.be.revertedWith("BaseToken: forbidden") + + await gmx.setGov(timelock.address) + + expect(await gmx.inPrivateTransferMode()).eq(false) + await timelock.connect(wallet).setInPrivateTransferMode(gmx.address, true) + expect(await gmx.inPrivateTransferMode()).eq(true) + + await timelock.connect(wallet).setInPrivateTransferMode(gmx.address, false) + expect(await gmx.inPrivateTransferMode()).eq(false) + + await timelock.connect(wallet).setInPrivateTransferMode(gmx.address, true) + expect(await gmx.inPrivateTransferMode()).eq(true) + + await expect(gmx.connect(user0).transfer(user1.address, 100)) + .to.be.revertedWith("BaseToken: msg.sender not whitelisted") + + await timelock.addExcludedToken(gmx.address) + await expect(timelock.connect(wallet).setInPrivateTransferMode(gmx.address, true)) + .to.be.revertedWith("Timelock: invalid _inPrivateTransferMode") + + await timelock.connect(wallet).setInPrivateTransferMode(gmx.address, false) + expect(await gmx.inPrivateTransferMode()).eq(false) + + await gmx.connect(user0).transfer(user1.address, 100) + }) + + it("testBridge", async () => { + const gmx = await deployContract("GMX", []) + const wgmx = await deployContract("GMX", []) + const bridge = await deployContract("Bridge", [gmx.address, wgmx.address]) + + await gmx.setMinter(wallet.address, true) + await gmx.mint(wallet.address, 100) + + await wgmx.setMinter(wallet.address, true) + await wgmx.mint(bridge.address, 100) + + await expect(timelock.connect(user0).setInPrivateTransferMode(gmx.address, true)) + .to.be.revertedWith("Timelock: forbidden") + + await expect(timelock.connect(wallet).setInPrivateTransferMode(gmx.address, true)) + .to.be.revertedWith("BaseToken: forbidden") + + await gmx.setGov(timelock.address) + + expect(await gmx.inPrivateTransferMode()).eq(false) + await timelock.connect(wallet).setInPrivateTransferMode(gmx.address, true) + expect(await gmx.inPrivateTransferMode()).eq(true) + + await expect(gmx.connect(user0).transfer(user1.address, 100)) + .to.be.revertedWith("BaseToken: msg.sender not whitelisted") + + await expect(timelock.connect(user0).testBridge(bridge.address, gmx.address, 100, user1.address)) + .to.be.revertedWith("Timelock: forbidden") + + await gmx.connect(wallet).approve(timelock.address, 100) + + expect(await gmx.balanceOf(wallet.address)).eq(100) + expect(await gmx.balanceOf(bridge.address)).eq(0) + expect(await wgmx.balanceOf(user1.address)).eq(0) + expect(await wgmx.balanceOf(bridge.address)).eq(100) + + await timelock.testBridge(bridge.address, gmx.address, 100, user1.address) + + expect(await gmx.balanceOf(wallet.address)).eq(0) + expect(await gmx.balanceOf(bridge.address)).eq(100) + expect(await wgmx.balanceOf(user1.address)).eq(100) + expect(await wgmx.balanceOf(bridge.address)).eq(0) + + await timelock.addExcludedToken(gmx.address) + await expect(timelock.connect(wallet).testBridge(bridge.address, gmx.address, 100, user1.address)) + .to.be.revertedWith("Timelock: _token is excluded") + }) + + it("setAdmin", async () => { + await expect(timelock.setAdmin(user1.address)) + .to.be.revertedWith("Timelock: forbidden") + + expect(await timelock.admin()).eq(wallet.address) + await timelock.connect(tokenManager).setAdmin(user1.address) + expect(await timelock.admin()).eq(user1.address) + }) +}) diff --git a/test/shared/chainlink.js b/test/shared/chainlink.js new file mode 100644 index 00000000..63e52978 --- /dev/null +++ b/test/shared/chainlink.js @@ -0,0 +1,5 @@ +function toChainlinkPrice(value) { + return parseInt(value * Math.pow(10, 8)) +} + +module.exports = { toChainlinkPrice } diff --git a/test/shared/fixtures.js b/test/shared/fixtures.js new file mode 100644 index 00000000..c8ec46f2 --- /dev/null +++ b/test/shared/fixtures.js @@ -0,0 +1,16 @@ +const { expandDecimals } = require("./utilities") + +async function deployContract(name, args) { + const contractFactory = await ethers.getContractFactory(name) + return await contractFactory.deploy(...args) +} + +async function contractAt(name, address) { + const contractFactory = await ethers.getContractFactory(name) + return await contractFactory.attach(address) +} + +module.exports = { + deployContract, + contractAt +} diff --git a/test/shared/units.js b/test/shared/units.js new file mode 100644 index 00000000..688a8099 --- /dev/null +++ b/test/shared/units.js @@ -0,0 +1,11 @@ +function toUsd(value) { + const normalizedValue = parseInt(value * Math.pow(10, 10)) + return ethers.BigNumber.from(normalizedValue).mul(ethers.BigNumber.from(10).pow(20)) +} + +function toNormalizedPrice(value) { + const normalizedValue = parseInt(value * Math.pow(10, 10)) + return ethers.BigNumber.from(normalizedValue).mul(ethers.BigNumber.from(10).pow(20)) +} + +module.exports = { toUsd, toNormalizedPrice } diff --git a/test/shared/utilities.js b/test/shared/utilities.js new file mode 100644 index 00000000..871a82ec --- /dev/null +++ b/test/shared/utilities.js @@ -0,0 +1,108 @@ +const BN = require('bn.js') + +const maxUint256 = ethers.constants.MaxUint256 + +function newWallet() { + return ethers.Wallet.createRandom() +} + +function bigNumberify(n) { + return ethers.BigNumber.from(n) +} + +function expandDecimals(n, decimals) { + return bigNumberify(n).mul(bigNumberify(10).pow(decimals)) +} + +async function send(provider, method, params = []) { + await provider.send(method, params) +} + +async function mineBlock(provider) { + await send(provider, "evm_mine") +} + +async function increaseTime(provider, seconds) { + await send(provider, "evm_increaseTime", [seconds]) +} + +async function gasUsed(provider, tx) { + return (await provider.getTransactionReceipt(tx.hash)).gasUsed +} + +async function getNetworkFee(provider, tx) { + const gas = await gasUsed(provider, tx) + return gas.mul(tx.gasPrice) +} + +async function reportGasUsed(provider, tx, label) { + const { gasUsed } = await provider.getTransactionReceipt(tx.hash) + console.info(label, gasUsed.toString()) +} + +async function getBlockTime(provider) { + const blockNumber = await provider.getBlockNumber() + const block = await provider.getBlock(blockNumber) + return block.timestamp +} + +async function getTxnBalances(provider, user, txn, callback) { + const balance0 = await provider.getBalance(user.address) + const tx = await txn() + const fee = await getNetworkFee(provider, tx) + const balance1 = await provider.getBalance(user.address) + callback(balance0, balance1, fee) +} + +function print(label, value, decimals) { + if (decimals === 0) { + console.log(label, value.toString()) + return + } + const valueStr = ethers.utils.formatUnits(value, decimals) + console.log(label, valueStr) +} + +function getPriceBitArray(prices) { + let priceBitArray = [] + let shouldExit = false + + for (let i = 0; i < parseInt((prices.length - 1) / 8) + 1; i++) { + let priceBits = new BN('0') + for (let j = 0; j < 8; j++) { + let index = i * 8 + j + if (index >= prices.length) { + shouldExit = true + break + } + + const price = new BN(prices[index]) + if (price.gt(new BN("2147483648"))) { // 2^31 + throw new Error(`price exceeds bit limit ${price.toString()}`) + } + priceBits = priceBits.or(price.shln(j * 32)) + } + + priceBitArray.push(priceBits.toString()) + + if (shouldExit) { break } + } + + return priceBitArray +} + +module.exports = { + newWallet, + maxUint256, + bigNumberify, + expandDecimals, + mineBlock, + increaseTime, + gasUsed, + getNetworkFee, + reportGasUsed, + getBlockTime, + getTxnBalances, + print, + getPriceBitArray +} diff --git a/test/staking/BonusDistributor.js b/test/staking/BonusDistributor.js new file mode 100644 index 00000000..3eb9fc1e --- /dev/null +++ b/test/staking/BonusDistributor.js @@ -0,0 +1,93 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed, print } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") + +use(solidity) + +describe("BonusDistributor", function () { + const provider = waffle.provider + const [wallet, rewardRouter, user0, user1, user2, user3] = provider.getWallets() + let gmx + let esGmx + let bnGmx + let stakedGmxTracker + let stakedGmxDistributor + let bonusGmxTracker + let bonusGmxDistributor + + beforeEach(async () => { + gmx = await deployContract("GMX", []); + esGmx = await deployContract("EsGMX", []); + bnGmx = await deployContract("MintableBaseToken", ["Bonus GMX", "bnGMX", 0]); + + stakedGmxTracker = await deployContract("RewardTracker", ["Staked GMX", "stGMX"]) + stakedGmxDistributor = await deployContract("RewardDistributor", [esGmx.address, stakedGmxTracker.address]) + await stakedGmxDistributor.updateLastDistributionTime() + + bonusGmxTracker = await deployContract("RewardTracker", ["Staked + Bonus GMX", "sbGMX"]) + bonusGmxDistributor = await deployContract("BonusDistributor", [bnGmx.address, bonusGmxTracker.address]) + await bonusGmxDistributor.updateLastDistributionTime() + + await stakedGmxTracker.initialize([gmx.address, esGmx.address], stakedGmxDistributor.address) + await bonusGmxTracker.initialize([stakedGmxTracker.address], bonusGmxDistributor.address) + + await stakedGmxTracker.setInPrivateTransferMode(true) + await stakedGmxTracker.setInPrivateStakingMode(true) + await bonusGmxTracker.setInPrivateTransferMode(true) + await bonusGmxTracker.setInPrivateStakingMode(true) + + await stakedGmxTracker.setHandler(rewardRouter.address, true) + await stakedGmxTracker.setHandler(bonusGmxTracker.address, true) + await bonusGmxTracker.setHandler(rewardRouter.address, true) + await bonusGmxDistributor.setBonusMultiplier(10000) + }) + + it("distributes bonus", async () => { + await esGmx.setMinter(wallet.address, true) + await esGmx.mint(stakedGmxDistributor.address, expandDecimals(50000, 18)) + await bnGmx.setMinter(wallet.address, true) + await bnGmx.mint(bonusGmxDistributor.address, expandDecimals(1500, 18)) + await stakedGmxDistributor.setTokensPerInterval("20667989410000000") // 0.02066798941 esGmx per second + await gmx.setMinter(wallet.address, true) + await gmx.mint(user0.address, expandDecimals(1000, 18)) + + await gmx.connect(user0).approve(stakedGmxTracker.address, expandDecimals(1001, 18)) + await expect(stakedGmxTracker.connect(rewardRouter).stakeForAccount(user0.address, user0.address, gmx.address, expandDecimals(1001, 18))) + .to.be.revertedWith("BaseToken: transfer amount exceeds balance") + await stakedGmxTracker.connect(rewardRouter).stakeForAccount(user0.address, user0.address, gmx.address, expandDecimals(1000, 18)) + await expect(bonusGmxTracker.connect(rewardRouter).stakeForAccount(user0.address, user0.address, stakedGmxTracker.address, expandDecimals(1001, 18))) + .to.be.revertedWith("RewardTracker: transfer amount exceeds balance") + await bonusGmxTracker.connect(rewardRouter).stakeForAccount(user0.address, user0.address, stakedGmxTracker.address, expandDecimals(1000, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await stakedGmxTracker.claimable(user0.address)).gt(expandDecimals(1785, 18)) // 50000 / 28 => ~1785 + expect(await stakedGmxTracker.claimable(user0.address)).lt(expandDecimals(1786, 18)) + expect(await bonusGmxTracker.claimable(user0.address)).gt("2730000000000000000") // 2.73, 1000 / 365 => ~2.74 + expect(await bonusGmxTracker.claimable(user0.address)).lt("2750000000000000000") // 2.75 + + await esGmx.mint(user1.address, expandDecimals(500, 18)) + await esGmx.connect(user1).approve(stakedGmxTracker.address, expandDecimals(500, 18)) + await stakedGmxTracker.connect(rewardRouter).stakeForAccount(user1.address, user1.address, esGmx.address, expandDecimals(500, 18)) + await bonusGmxTracker.connect(rewardRouter).stakeForAccount(user1.address, user1.address, stakedGmxTracker.address, expandDecimals(500, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await stakedGmxTracker.claimable(user0.address)).gt(expandDecimals(1785 + 1190, 18)) + expect(await stakedGmxTracker.claimable(user0.address)).lt(expandDecimals(1786 + 1191, 18)) + + expect(await stakedGmxTracker.claimable(user1.address)).gt(expandDecimals(595, 18)) + expect(await stakedGmxTracker.claimable(user1.address)).lt(expandDecimals(596, 18)) + + expect(await bonusGmxTracker.claimable(user0.address)).gt("5470000000000000000") // 5.47, 1000 / 365 * 2 => ~5.48 + expect(await bonusGmxTracker.claimable(user0.address)).lt("5490000000000000000") // 5.49 + + expect(await bonusGmxTracker.claimable(user1.address)).gt("1360000000000000000") // 1.36, 500 / 365 => ~1.37 + expect(await bonusGmxTracker.claimable(user1.address)).lt("1380000000000000000") // 1.38 + }) +}) diff --git a/test/staking/RewardRouter.js b/test/staking/RewardRouter.js new file mode 100644 index 00000000..71305202 --- /dev/null +++ b/test/staking/RewardRouter.js @@ -0,0 +1,597 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed, print, newWallet } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") +const { initVault, getBnbConfig, getBtcConfig, getDaiConfig } = require("../core/Vault/helpers") + +use(solidity) + +describe("RewardRouter", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + + let vault + let glpManager + let glp + let usdg + let router + let vaultPriceFeed + let bnb + let bnbPriceFeed + let btc + let btcPriceFeed + let eth + let ethPriceFeed + let dai + let daiPriceFeed + let busd + let busdPriceFeed + + let gmx + let esGmx + let bnGmx + + let stakedGmxTracker + let stakedGmxDistributor + let bonusGmxTracker + let bonusGmxDistributor + let feeGmxTracker + let feeGmxDistributor + + let feeGlpTracker + let feeGlpDistributor + let stakedGlpTracker + let stakedGlpDistributor + + let rewardRouter + + beforeEach(async () => { + bnb = await deployContract("Token", []) + bnbPriceFeed = await deployContract("PriceFeed", []) + + btc = await deployContract("Token", []) + btcPriceFeed = await deployContract("PriceFeed", []) + + eth = await deployContract("Token", []) + ethPriceFeed = await deployContract("PriceFeed", []) + + dai = await deployContract("Token", []) + daiPriceFeed = await deployContract("PriceFeed", []) + + busd = await deployContract("Token", []) + busdPriceFeed = await deployContract("PriceFeed", []) + + vault = await deployContract("Vault", []) + usdg = await deployContract("USDG", [vault.address]) + router = await deployContract("Router", [vault.address, usdg.address, bnb.address]) + vaultPriceFeed = await deployContract("VaultPriceFeed", []) + glp = await deployContract("GLP", []) + + await initVault(vault, router, usdg, vaultPriceFeed) + glpManager = await deployContract("GlpManager", [vault.address, usdg.address, glp.address, 24 * 60 * 60]) + + await vaultPriceFeed.setTokenConfig(bnb.address, bnbPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(btc.address, btcPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(eth.address, ethPriceFeed.address, 8, false) + await vaultPriceFeed.setTokenConfig(dai.address, daiPriceFeed.address, 8, false) + + await daiPriceFeed.setLatestAnswer(toChainlinkPrice(1)) + await vault.setTokenConfig(...getDaiConfig(dai, daiPriceFeed)) + + await btcPriceFeed.setLatestAnswer(toChainlinkPrice(60000)) + await vault.setTokenConfig(...getBtcConfig(btc, btcPriceFeed)) + + await bnbPriceFeed.setLatestAnswer(toChainlinkPrice(300)) + await vault.setTokenConfig(...getBnbConfig(bnb, bnbPriceFeed)) + + await glp.setInPrivateTransferMode(true) + await glp.setMinter(glpManager.address, true) + await glpManager.setInPrivateMode(true) + + gmx = await deployContract("GMX", []); + esGmx = await deployContract("EsGMX", []); + bnGmx = await deployContract("MintableBaseToken", ["Bonus GMX", "bnGMX", 0]); + + // GMX + stakedGmxTracker = await deployContract("RewardTracker", ["Staked GMX", "sGMX"]) + stakedGmxDistributor = await deployContract("RewardDistributor", [esGmx.address, stakedGmxTracker.address]) + await stakedGmxTracker.initialize([gmx.address, esGmx.address], stakedGmxDistributor.address) + await stakedGmxDistributor.updateLastDistributionTime() + + bonusGmxTracker = await deployContract("RewardTracker", ["Staked + Bonus GMX", "sbGMX"]) + bonusGmxDistributor = await deployContract("BonusDistributor", [bnGmx.address, bonusGmxTracker.address]) + await bonusGmxTracker.initialize([stakedGmxTracker.address], bonusGmxDistributor.address) + await bonusGmxDistributor.updateLastDistributionTime() + + feeGmxTracker = await deployContract("RewardTracker", ["Staked + Bonus + Fee GMX", "sbfGMX"]) + feeGmxDistributor = await deployContract("RewardDistributor", [eth.address, feeGmxTracker.address]) + await feeGmxTracker.initialize([bonusGmxTracker.address, bnGmx.address], feeGmxDistributor.address) + await feeGmxDistributor.updateLastDistributionTime() + + // GLP + feeGlpTracker = await deployContract("RewardTracker", ["Fee GLP", "fGLP"]) + feeGlpDistributor = await deployContract("RewardDistributor", [eth.address, feeGlpTracker.address]) + await feeGlpTracker.initialize([glp.address], feeGlpDistributor.address) + await feeGlpDistributor.updateLastDistributionTime() + + stakedGlpTracker = await deployContract("RewardTracker", ["Fee + Staked GLP", "fsGLP"]) + stakedGlpDistributor = await deployContract("RewardDistributor", [esGmx.address, stakedGlpTracker.address]) + await stakedGlpTracker.initialize([feeGlpTracker.address], stakedGlpDistributor.address) + await stakedGlpDistributor.updateLastDistributionTime() + + await stakedGmxTracker.setInPrivateTransferMode(true) + await stakedGmxTracker.setInPrivateStakingMode(true) + await bonusGmxTracker.setInPrivateTransferMode(true) + await bonusGmxTracker.setInPrivateStakingMode(true) + await bonusGmxTracker.setInPrivateClaimingMode(true) + await feeGmxTracker.setInPrivateTransferMode(true) + await feeGmxTracker.setInPrivateStakingMode(true) + + await feeGlpTracker.setInPrivateTransferMode(true) + await feeGlpTracker.setInPrivateStakingMode(true) + await stakedGlpTracker.setInPrivateTransferMode(true) + await stakedGlpTracker.setInPrivateStakingMode(true) + + rewardRouter = await deployContract("RewardRouter", []) + await rewardRouter.initialize( + bnb.address, + gmx.address, + esGmx.address, + bnGmx.address, + glp.address, + stakedGmxTracker.address, + bonusGmxTracker.address, + feeGmxTracker.address, + feeGlpTracker.address, + stakedGlpTracker.address, + glpManager.address + ) + + // allow rewardRouter to stake in stakedGmxTracker + await stakedGmxTracker.setHandler(rewardRouter.address, true) + // allow bonusGmxTracker to stake stakedGmxTracker + await stakedGmxTracker.setHandler(bonusGmxTracker.address, true) + // allow rewardRouter to stake in bonusGmxTracker + await bonusGmxTracker.setHandler(rewardRouter.address, true) + // allow bonusGmxTracker to stake feeGmxTracker + await bonusGmxTracker.setHandler(feeGmxTracker.address, true) + await bonusGmxDistributor.setBonusMultiplier(10000) + // allow rewardRouter to stake in feeGmxTracker + await feeGmxTracker.setHandler(rewardRouter.address, true) + // allow feeGmxTracker to stake bnGmx + await bnGmx.setHandler(feeGmxTracker.address, true) + // allow rewardRouter to burn bnGmx + await bnGmx.setMinter(rewardRouter.address, true) + + // allow rewardRouter to mint in glpManager + await glpManager.setHandler(rewardRouter.address, true) + // allow rewardRouter to stake in feeGlpTracker + await feeGlpTracker.setHandler(rewardRouter.address, true) + // allow stakedGlpTracker to stake feeGlpTracker + await feeGlpTracker.setHandler(stakedGlpTracker.address, true) + // allow rewardRouter to sake in stakedGlpTracker + await stakedGlpTracker.setHandler(rewardRouter.address, true) + // allow feeGlpTracker to stake glp + await glp.setHandler(feeGlpTracker.address, true) + + // mint esGmx for distributors + await esGmx.setMinter(wallet.address, true) + await esGmx.mint(stakedGmxDistributor.address, expandDecimals(50000, 18)) + await stakedGmxDistributor.setTokensPerInterval("20667989410000000") // 0.02066798941 esGmx per second + await esGmx.mint(stakedGlpDistributor.address, expandDecimals(50000, 18)) + await stakedGlpDistributor.setTokensPerInterval("20667989410000000") // 0.02066798941 esGmx per second + + await esGmx.setInPrivateTransferMode(true) + await esGmx.setHandler(stakedGmxDistributor.address, true) + await esGmx.setHandler(stakedGlpDistributor.address, true) + await esGmx.setHandler(stakedGmxTracker.address, true) + await esGmx.setHandler(stakedGlpTracker.address, true) + await esGmx.setHandler(rewardRouter.address, true) + + // mint bnGmx for distributor + await bnGmx.setMinter(wallet.address, true) + await bnGmx.mint(bonusGmxDistributor.address, expandDecimals(1500, 18)) + }) + + it("inits", async () => { + expect(await rewardRouter.isInitialized()).eq(true) + + expect(await rewardRouter.weth()).eq(bnb.address) + expect(await rewardRouter.gmx()).eq(gmx.address) + expect(await rewardRouter.esGmx()).eq(esGmx.address) + expect(await rewardRouter.bnGmx()).eq(bnGmx.address) + + expect(await rewardRouter.glp()).eq(glp.address) + + expect(await rewardRouter.stakedGmxTracker()).eq(stakedGmxTracker.address) + expect(await rewardRouter.bonusGmxTracker()).eq(bonusGmxTracker.address) + expect(await rewardRouter.feeGmxTracker()).eq(feeGmxTracker.address) + + expect(await rewardRouter.feeGlpTracker()).eq(feeGlpTracker.address) + expect(await rewardRouter.stakedGlpTracker()).eq(stakedGlpTracker.address) + + expect(await rewardRouter.glpManager()).eq(glpManager.address) + + await expect(rewardRouter.initialize( + bnb.address, + gmx.address, + esGmx.address, + bnGmx.address, + glp.address, + stakedGmxTracker.address, + bonusGmxTracker.address, + feeGmxTracker.address, + feeGlpTracker.address, + stakedGlpTracker.address, + glpManager.address + )).to.be.revertedWith("RewardRouter: already initialized") + }) + + it("stakeGmxForAccount, stakeGmx, stakeEsGmx, unstakeGmx, unstakeEsGmx, claimEsGmx, claimFees, compound, batchCompoundForAccounts", async () => { + await eth.mint(feeGmxDistributor.address, expandDecimals(100, 18)) + await feeGmxDistributor.setTokensPerInterval("41335970000000") // 0.00004133597 ETH per second + + await gmx.setMinter(wallet.address, true) + await gmx.mint(user0.address, expandDecimals(1500, 18)) + expect(await gmx.balanceOf(user0.address)).eq(expandDecimals(1500, 18)) + + await gmx.connect(user0).approve(stakedGmxTracker.address, expandDecimals(1000, 18)) + await expect(rewardRouter.connect(user0).stakeGmxForAccount(user1.address, expandDecimals(1000, 18))) + .to.be.revertedWith("Governable: forbidden") + + await rewardRouter.setGov(user0.address) + await rewardRouter.connect(user0).stakeGmxForAccount(user1.address, expandDecimals(800, 18)) + expect(await gmx.balanceOf(user0.address)).eq(expandDecimals(700, 18)) + + await gmx.mint(user1.address, expandDecimals(200, 18)) + expect(await gmx.balanceOf(user1.address)).eq(expandDecimals(200, 18)) + await gmx.connect(user1).approve(stakedGmxTracker.address, expandDecimals(200, 18)) + await rewardRouter.connect(user1).stakeGmx(expandDecimals(200, 18)) + expect(await gmx.balanceOf(user1.address)).eq(0) + + expect(await stakedGmxTracker.stakedAmounts(user0.address)).eq(0) + expect(await stakedGmxTracker.depositBalances(user0.address, gmx.address)).eq(0) + expect(await stakedGmxTracker.stakedAmounts(user1.address)).eq(expandDecimals(1000, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, gmx.address)).eq(expandDecimals(1000, 18)) + + expect(await bonusGmxTracker.stakedAmounts(user0.address)).eq(0) + expect(await bonusGmxTracker.depositBalances(user0.address, stakedGmxTracker.address)).eq(0) + expect(await bonusGmxTracker.stakedAmounts(user1.address)).eq(expandDecimals(1000, 18)) + expect(await bonusGmxTracker.depositBalances(user1.address, stakedGmxTracker.address)).eq(expandDecimals(1000, 18)) + + expect(await feeGmxTracker.stakedAmounts(user0.address)).eq(0) + expect(await feeGmxTracker.depositBalances(user0.address, bonusGmxTracker.address)).eq(0) + expect(await feeGmxTracker.stakedAmounts(user1.address)).eq(expandDecimals(1000, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bonusGmxTracker.address)).eq(expandDecimals(1000, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await stakedGmxTracker.claimable(user0.address)).eq(0) + expect(await stakedGmxTracker.claimable(user1.address)).gt(expandDecimals(1785, 18)) // 50000 / 28 => ~1785 + expect(await stakedGmxTracker.claimable(user1.address)).lt(expandDecimals(1786, 18)) + + expect(await bonusGmxTracker.claimable(user0.address)).eq(0) + expect(await bonusGmxTracker.claimable(user1.address)).gt("2730000000000000000") // 2.73, 1000 / 365 => ~2.74 + expect(await bonusGmxTracker.claimable(user1.address)).lt("2750000000000000000") // 2.75 + + expect(await feeGmxTracker.claimable(user0.address)).eq(0) + expect(await feeGmxTracker.claimable(user1.address)).gt("3560000000000000000") // 3.56, 100 / 28 => ~3.57 + expect(await feeGmxTracker.claimable(user1.address)).lt("3580000000000000000") // 3.58 + + await esGmx.setMinter(wallet.address, true) + await esGmx.mint(user2.address, expandDecimals(500, 18)) + await rewardRouter.connect(user2).stakeEsGmx(expandDecimals(500, 18)) + + expect(await stakedGmxTracker.stakedAmounts(user0.address)).eq(0) + expect(await stakedGmxTracker.depositBalances(user0.address, gmx.address)).eq(0) + expect(await stakedGmxTracker.stakedAmounts(user1.address)).eq(expandDecimals(1000, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, gmx.address)).eq(expandDecimals(1000, 18)) + expect(await stakedGmxTracker.stakedAmounts(user2.address)).eq(expandDecimals(500, 18)) + expect(await stakedGmxTracker.depositBalances(user2.address, esGmx.address)).eq(expandDecimals(500, 18)) + + expect(await bonusGmxTracker.stakedAmounts(user0.address)).eq(0) + expect(await bonusGmxTracker.depositBalances(user0.address, stakedGmxTracker.address)).eq(0) + expect(await bonusGmxTracker.stakedAmounts(user1.address)).eq(expandDecimals(1000, 18)) + expect(await bonusGmxTracker.depositBalances(user1.address, stakedGmxTracker.address)).eq(expandDecimals(1000, 18)) + expect(await bonusGmxTracker.stakedAmounts(user2.address)).eq(expandDecimals(500, 18)) + expect(await bonusGmxTracker.depositBalances(user2.address, stakedGmxTracker.address)).eq(expandDecimals(500, 18)) + + expect(await feeGmxTracker.stakedAmounts(user0.address)).eq(0) + expect(await feeGmxTracker.depositBalances(user0.address, bonusGmxTracker.address)).eq(0) + expect(await feeGmxTracker.stakedAmounts(user1.address)).eq(expandDecimals(1000, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bonusGmxTracker.address)).eq(expandDecimals(1000, 18)) + expect(await feeGmxTracker.stakedAmounts(user2.address)).eq(expandDecimals(500, 18)) + expect(await feeGmxTracker.depositBalances(user2.address, bonusGmxTracker.address)).eq(expandDecimals(500, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await stakedGmxTracker.claimable(user0.address)).eq(0) + expect(await stakedGmxTracker.claimable(user1.address)).gt(expandDecimals(1785 + 1190, 18)) + expect(await stakedGmxTracker.claimable(user1.address)).lt(expandDecimals(1786 + 1191, 18)) + expect(await stakedGmxTracker.claimable(user2.address)).gt(expandDecimals(595, 18)) + expect(await stakedGmxTracker.claimable(user2.address)).lt(expandDecimals(596, 18)) + + expect(await bonusGmxTracker.claimable(user0.address)).eq(0) + expect(await bonusGmxTracker.claimable(user1.address)).gt("5470000000000000000") // 5.47, 1000 / 365 * 2 => ~5.48 + expect(await bonusGmxTracker.claimable(user1.address)).lt("5490000000000000000") + expect(await bonusGmxTracker.claimable(user2.address)).gt("1360000000000000000") // 1.36, 500 / 365 => ~1.37 + expect(await bonusGmxTracker.claimable(user2.address)).lt("1380000000000000000") + + expect(await feeGmxTracker.claimable(user0.address)).eq(0) + expect(await feeGmxTracker.claimable(user1.address)).gt("5940000000000000000") // 5.94, 3.57 + 100 / 28 / 3 * 2 => ~5.95 + expect(await feeGmxTracker.claimable(user1.address)).lt("5960000000000000000") + expect(await feeGmxTracker.claimable(user2.address)).gt("1180000000000000000") // 1.18, 100 / 28 / 3 => ~1.19 + expect(await feeGmxTracker.claimable(user2.address)).lt("1200000000000000000") + + expect(await esGmx.balanceOf(user1.address)).eq(0) + await rewardRouter.connect(user1).claimEsGmx() + expect(await esGmx.balanceOf(user1.address)).gt(expandDecimals(1785 + 1190, 18)) + expect(await esGmx.balanceOf(user1.address)).lt(expandDecimals(1786 + 1191, 18)) + + expect(await eth.balanceOf(user1.address)).eq(0) + await rewardRouter.connect(user1).claimFees() + expect(await eth.balanceOf(user1.address)).gt("5940000000000000000") + expect(await eth.balanceOf(user1.address)).lt("5960000000000000000") + + expect(await esGmx.balanceOf(user2.address)).eq(0) + await rewardRouter.connect(user2).claimEsGmx() + expect(await esGmx.balanceOf(user2.address)).gt(expandDecimals(595, 18)) + expect(await esGmx.balanceOf(user2.address)).lt(expandDecimals(596, 18)) + + expect(await eth.balanceOf(user2.address)).eq(0) + await rewardRouter.connect(user2).claimFees() + expect(await eth.balanceOf(user2.address)).gt("1180000000000000000") + expect(await eth.balanceOf(user2.address)).lt("1200000000000000000") + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + const tx0 = await rewardRouter.connect(user1).compound() + await reportGasUsed(provider, tx0, "compound gas used") + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + const tx1 = await rewardRouter.connect(user0).batchCompoundForAccounts([user1.address, user2.address]) + await reportGasUsed(provider, tx1, "batchCompoundForAccounts gas used") + + expect(await stakedGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(3643, 18)) + expect(await stakedGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(3645, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, gmx.address)).eq(expandDecimals(1000, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, esGmx.address)).gt(expandDecimals(2643, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, esGmx.address)).lt(expandDecimals(2645, 18)) + + expect(await bonusGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(3643, 18)) + expect(await bonusGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(3645, 18)) + + expect(await feeGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(3657, 18)) + expect(await feeGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(3659, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bonusGmxTracker.address)).gt(expandDecimals(3643, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bonusGmxTracker.address)).lt(expandDecimals(3645, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bnGmx.address)).gt("14100000000000000000") // 14.1 + expect(await feeGmxTracker.depositBalances(user1.address, bnGmx.address)).lt("14300000000000000000") // 14.3 + + expect(await gmx.balanceOf(user1.address)).eq(0) + await rewardRouter.connect(user1).unstakeGmx(expandDecimals(300, 18)) + expect(await gmx.balanceOf(user1.address)).eq(expandDecimals(300, 18)) + + expect(await stakedGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(3343, 18)) + expect(await stakedGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(3345, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, gmx.address)).eq(expandDecimals(700, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, esGmx.address)).gt(expandDecimals(2643, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, esGmx.address)).lt(expandDecimals(2645, 18)) + + expect(await bonusGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(3343, 18)) + expect(await bonusGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(3345, 18)) + + expect(await feeGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(3357, 18)) + expect(await feeGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(3359, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bonusGmxTracker.address)).gt(expandDecimals(3343, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bonusGmxTracker.address)).lt(expandDecimals(3345, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bnGmx.address)).gt("13000000000000000000") // 13 + expect(await feeGmxTracker.depositBalances(user1.address, bnGmx.address)).lt("13100000000000000000") // 13.1 + + const esGmxBalance1 = await esGmx.balanceOf(user1.address) + const esGmxUnstakeBalance1 = await stakedGmxTracker.depositBalances(user1.address, esGmx.address) + await rewardRouter.connect(user1).unstakeEsGmx(esGmxUnstakeBalance1) + expect(await esGmx.balanceOf(user1.address)).eq(esGmxBalance1.add(esGmxUnstakeBalance1)) + + expect(await stakedGmxTracker.stakedAmounts(user1.address)).eq(expandDecimals(700, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, gmx.address)).eq(expandDecimals(700, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, esGmx.address)).eq(0) + + expect(await bonusGmxTracker.stakedAmounts(user1.address)).eq(expandDecimals(700, 18)) + + expect(await feeGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(702, 18)) + expect(await feeGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(703, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bonusGmxTracker.address)).eq(expandDecimals(700, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bnGmx.address)).gt("2720000000000000000") // 2.72 + expect(await feeGmxTracker.depositBalances(user1.address, bnGmx.address)).lt("2740000000000000000") // 2.74 + + await expect(rewardRouter.connect(user1).unstakeEsGmx(expandDecimals(1, 18))) + .to.be.revertedWith("RewardTracker: _amount exceeds depositBalance") + }) + + it("mintAndStakeGlp, unstakeAndRedeemGlp, compound, batchCompoundForAccounts", async () => { + await eth.mint(feeGlpDistributor.address, expandDecimals(100, 18)) + await feeGlpDistributor.setTokensPerInterval("41335970000000") // 0.00004133597 ETH per second + + await bnb.mint(user1.address, expandDecimals(1, 18)) + await bnb.connect(user1).approve(glpManager.address, expandDecimals(1, 18)) + const tx0 = await rewardRouter.connect(user1).mintAndStakeGlp( + bnb.address, + expandDecimals(1, 18), + expandDecimals(299, 18), + expandDecimals(299, 18) + ) + await reportGasUsed(provider, tx0, "mintAndStakeGlp gas used") + + expect(await feeGlpTracker.stakedAmounts(user1.address)).eq(expandDecimals(2991, 17)) + expect(await feeGlpTracker.depositBalances(user1.address, glp.address)).eq(expandDecimals(2991, 17)) + + expect(await stakedGlpTracker.stakedAmounts(user1.address)).eq(expandDecimals(2991, 17)) + expect(await stakedGlpTracker.depositBalances(user1.address, feeGlpTracker.address)).eq(expandDecimals(2991, 17)) + + await bnb.mint(user1.address, expandDecimals(2, 18)) + await bnb.connect(user1).approve(glpManager.address, expandDecimals(2, 18)) + await rewardRouter.connect(user1).mintAndStakeGlp( + bnb.address, + expandDecimals(2, 18), + expandDecimals(299, 18), + expandDecimals(299, 18) + ) + + await increaseTime(provider, 24 * 60 * 60 + 1) + await mineBlock(provider) + + expect(await feeGlpTracker.claimable(user1.address)).gt("3560000000000000000") // 3.56, 100 / 28 => ~3.57 + expect(await feeGlpTracker.claimable(user1.address)).lt("3580000000000000000") // 3.58 + + expect(await stakedGlpTracker.claimable(user1.address)).gt(expandDecimals(1785, 18)) // 50000 / 28 => ~1785 + expect(await stakedGlpTracker.claimable(user1.address)).lt(expandDecimals(1786, 18)) + + await bnb.mint(user2.address, expandDecimals(1, 18)) + await bnb.connect(user2).approve(glpManager.address, expandDecimals(1, 18)) + await rewardRouter.connect(user2).mintAndStakeGlp( + bnb.address, + expandDecimals(1, 18), + expandDecimals(299, 18), + expandDecimals(299, 18) + ) + + await expect(rewardRouter.connect(user2).unstakeAndRedeemGlp( + bnb.address, + expandDecimals(299, 18), + "990000000000000000", // 0.99 + user2.address + )).to.be.revertedWith("GlpManager: cooldown duration not yet passed") + + expect(await feeGlpTracker.stakedAmounts(user1.address)).eq("897300000000000000000") // 897.3 + expect(await stakedGlpTracker.stakedAmounts(user1.address)).eq("897300000000000000000") + expect(await bnb.balanceOf(user1.address)).eq(0) + + const tx1 = await rewardRouter.connect(user1).unstakeAndRedeemGlp( + bnb.address, + expandDecimals(299, 18), + "990000000000000000", // 0.99 + user1.address + ) + await reportGasUsed(provider, tx1, "unstakeAndRedeemGlp gas used") + + expect(await feeGlpTracker.stakedAmounts(user1.address)).eq("598300000000000000000") // 598.3 + expect(await stakedGlpTracker.stakedAmounts(user1.address)).eq("598300000000000000000") + expect(await bnb.balanceOf(user1.address)).eq("993676666666666666") // ~0.99 + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await feeGlpTracker.claimable(user1.address)).gt("5940000000000000000") // 5.94, 3.57 + 100 / 28 / 3 * 2 => ~5.95 + expect(await feeGlpTracker.claimable(user1.address)).lt("5960000000000000000") + expect(await feeGlpTracker.claimable(user2.address)).gt("1180000000000000000") // 1.18, 100 / 28 / 3 => ~1.19 + expect(await feeGlpTracker.claimable(user2.address)).lt("1200000000000000000") + + expect(await stakedGlpTracker.claimable(user1.address)).gt(expandDecimals(1785 + 1190, 18)) + expect(await stakedGlpTracker.claimable(user1.address)).lt(expandDecimals(1786 + 1191, 18)) + expect(await stakedGlpTracker.claimable(user2.address)).gt(expandDecimals(595, 18)) + expect(await stakedGlpTracker.claimable(user2.address)).lt(expandDecimals(596, 18)) + + expect(await esGmx.balanceOf(user1.address)).eq(0) + await rewardRouter.connect(user1).claimEsGmx() + expect(await esGmx.balanceOf(user1.address)).gt(expandDecimals(1785 + 1190, 18)) + expect(await esGmx.balanceOf(user1.address)).lt(expandDecimals(1786 + 1191, 18)) + + expect(await eth.balanceOf(user1.address)).eq(0) + await rewardRouter.connect(user1).claimFees() + expect(await eth.balanceOf(user1.address)).gt("5940000000000000000") + expect(await eth.balanceOf(user1.address)).lt("5960000000000000000") + + expect(await esGmx.balanceOf(user2.address)).eq(0) + await rewardRouter.connect(user2).claimEsGmx() + expect(await esGmx.balanceOf(user2.address)).gt(expandDecimals(595, 18)) + expect(await esGmx.balanceOf(user2.address)).lt(expandDecimals(596, 18)) + + expect(await eth.balanceOf(user2.address)).eq(0) + await rewardRouter.connect(user2).claimFees() + expect(await eth.balanceOf(user2.address)).gt("1180000000000000000") + expect(await eth.balanceOf(user2.address)).lt("1200000000000000000") + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + const tx2 = await rewardRouter.connect(user1).compound() + await reportGasUsed(provider, tx2, "compound gas used") + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + const tx3 = await rewardRouter.batchCompoundForAccounts([user1.address, user2.address]) + await reportGasUsed(provider, tx1, "batchCompoundForAccounts gas used") + + expect(await stakedGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(4165, 18)) + expect(await stakedGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(4167, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, gmx.address)).eq(0) + expect(await stakedGmxTracker.depositBalances(user1.address, esGmx.address)).gt(expandDecimals(4165, 18)) + expect(await stakedGmxTracker.depositBalances(user1.address, esGmx.address)).lt(expandDecimals(4167, 18)) + + expect(await bonusGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(4165, 18)) + expect(await bonusGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(4167, 18)) + + expect(await feeGmxTracker.stakedAmounts(user1.address)).gt(expandDecimals(4179, 18)) + expect(await feeGmxTracker.stakedAmounts(user1.address)).lt(expandDecimals(4180, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bonusGmxTracker.address)).gt(expandDecimals(4165, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bonusGmxTracker.address)).lt(expandDecimals(4167, 18)) + expect(await feeGmxTracker.depositBalances(user1.address, bnGmx.address)).gt("12900000000000000000") // 12.9 + expect(await feeGmxTracker.depositBalances(user1.address, bnGmx.address)).lt("13100000000000000000") // 13.1 + + expect(await feeGlpTracker.stakedAmounts(user1.address)).eq("598300000000000000000") // 598.3 + expect(await stakedGlpTracker.stakedAmounts(user1.address)).eq("598300000000000000000") + expect(await bnb.balanceOf(user1.address)).eq("993676666666666666") // ~0.99 + }) + + it("mintAndStakeGlpETH, unstakeAndRedeemGlpETH", async () => { + const receiver0 = newWallet() + await expect(rewardRouter.connect(user0).mintAndStakeGlpETH(expandDecimals(300, 18), expandDecimals(300, 18), { value: 0 })) + .to.be.revertedWith("RewardRouter: invalid msg.value") + + await expect(rewardRouter.connect(user0).mintAndStakeGlpETH(expandDecimals(300, 18), expandDecimals(300, 18), { value: expandDecimals(1, 18) })) + .to.be.revertedWith("GlpManager: insufficient USDG output") + + await expect(rewardRouter.connect(user0).mintAndStakeGlpETH(expandDecimals(299, 18), expandDecimals(300, 18), { value: expandDecimals(1, 18) })) + .to.be.revertedWith("GlpManager: insufficient GLP output") + + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await bnb.balanceOf(vault.address)).eq(0) + expect(await bnb.totalSupply()).eq(0) + expect(await provider.getBalance(bnb.address)).eq(0) + expect(await stakedGlpTracker.balanceOf(user0.address)).eq(0) + + await rewardRouter.connect(user0).mintAndStakeGlpETH(expandDecimals(299, 18), expandDecimals(299, 18), { value: expandDecimals(1, 18) }) + + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await bnb.balanceOf(vault.address)).eq(expandDecimals(1, 18)) + expect(await provider.getBalance(bnb.address)).eq(expandDecimals(1, 18)) + expect(await bnb.totalSupply()).eq(expandDecimals(1, 18)) + expect(await stakedGlpTracker.balanceOf(user0.address)).eq("299100000000000000000") // 299.1 + + await expect(rewardRouter.connect(user0).unstakeAndRedeemGlpETH(expandDecimals(300, 18), expandDecimals(1, 18), receiver0.address)) + .to.be.revertedWith("RewardTracker: _amount exceeds stakedAmount") + + await expect(rewardRouter.connect(user0).unstakeAndRedeemGlpETH("299100000000000000000", expandDecimals(1, 18), receiver0.address)) + .to.be.revertedWith("GlpManager: cooldown duration not yet passed") + + await increaseTime(provider, 24 * 60 * 60 + 10) + + await expect(rewardRouter.connect(user0).unstakeAndRedeemGlpETH("299100000000000000000", expandDecimals(1, 18), receiver0.address)) + .to.be.revertedWith("GlpManager: insufficient output") + + await rewardRouter.connect(user0).unstakeAndRedeemGlpETH("299100000000000000000", "990000000000000000", receiver0.address) + expect(await provider.getBalance(receiver0.address)).eq("994009000000000000") // 0.994009 + expect(await bnb.balanceOf(vault.address)).eq("5991000000000000") // 0.005991 + expect(await provider.getBalance(bnb.address)).eq("5991000000000000") + expect(await bnb.totalSupply()).eq("5991000000000000") + }) +}) diff --git a/test/staking/RewardTracker.js b/test/staking/RewardTracker.js new file mode 100644 index 00000000..e5643fb7 --- /dev/null +++ b/test/staking/RewardTracker.js @@ -0,0 +1,353 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") + +use(solidity) + +describe("RewardTracker", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let rewardTracker + let gmx + let esGmx + let rewardDistributor + + beforeEach(async () => { + rewardTracker = await deployContract("RewardTracker", ["RT_NAME", "RT_SYMBOL"]) + gmx = await deployContract("GMX", []); + esGmx = await deployContract("EsGMX", []); + rewardDistributor = await deployContract("RewardDistributor", [esGmx.address, rewardTracker.address]) + await rewardDistributor.updateLastDistributionTime() + + await rewardTracker.initialize([gmx.address, esGmx.address], rewardDistributor.address) + }) + + it("inits", async () => { + expect(await rewardTracker.isInitialized()).eq(true) + expect(await rewardTracker.isDepositToken(wallet.address)).eq(false) + expect(await rewardTracker.isDepositToken(gmx.address)).eq(true) + expect(await rewardTracker.isDepositToken(esGmx.address)).eq(true) + expect(await rewardTracker.distributor()).eq(rewardDistributor.address) + expect(await rewardTracker.distributor()).eq(rewardDistributor.address) + expect(await rewardTracker.rewardToken()).eq(esGmx.address) + + await expect(rewardTracker.initialize([gmx.address, esGmx.address], rewardDistributor.address)) + .to.be.revertedWith("RewardTracker: already initialized") + }) + + it("setDepositToken", async () => { + await expect(rewardTracker.connect(user0).setDepositToken(user1.address, true)) + .to.be.revertedWith("Governable: forbidden") + + await rewardTracker.setGov(user0.address) + + expect(await rewardTracker.isDepositToken(user1.address)).eq(false) + await rewardTracker.connect(user0).setDepositToken(user1.address, true) + expect(await rewardTracker.isDepositToken(user1.address)).eq(true) + await rewardTracker.connect(user0).setDepositToken(user1.address, false) + expect(await rewardTracker.isDepositToken(user1.address)).eq(false) + }) + + it("setInPrivateTransferMode", async () => { + await expect(rewardTracker.connect(user0).setInPrivateTransferMode(true)) + .to.be.revertedWith("Governable: forbidden") + + await rewardTracker.setGov(user0.address) + + expect(await rewardTracker.inPrivateTransferMode()).eq(false) + await rewardTracker.connect(user0).setInPrivateTransferMode(true) + expect(await rewardTracker.inPrivateTransferMode()).eq(true) + }) + + it("setInPrivateStakingMode", async () => { + await expect(rewardTracker.connect(user0).setInPrivateStakingMode(true)) + .to.be.revertedWith("Governable: forbidden") + + await rewardTracker.setGov(user0.address) + + expect(await rewardTracker.inPrivateStakingMode()).eq(false) + await rewardTracker.connect(user0).setInPrivateStakingMode(true) + expect(await rewardTracker.inPrivateStakingMode()).eq(true) + }) + + it("setHandler", async () => { + await expect(rewardTracker.connect(user0).setHandler(user1.address, true)) + .to.be.revertedWith("Governable: forbidden") + + await rewardTracker.setGov(user0.address) + + expect(await rewardTracker.isHandler(user1.address)).eq(false) + await rewardTracker.connect(user0).setHandler(user1.address, true) + expect(await rewardTracker.isHandler(user1.address)).eq(true) + }) + + it("withdrawToken", async () => { + await gmx.setMinter(wallet.address, true) + await gmx.mint(rewardTracker.address, 2000) + await expect(rewardTracker.connect(user0).withdrawToken(gmx.address, user1.address, 2000)) + .to.be.revertedWith("Governable: forbidden") + + await rewardTracker.setGov(user0.address) + + expect(await gmx.balanceOf(user1.address)).eq(0) + await rewardTracker.connect(user0).withdrawToken(gmx.address, user1.address, 2000) + expect(await gmx.balanceOf(user1.address)).eq(2000) + }) + + it("stake, unstake, claim", async () => { + await esGmx.setMinter(wallet.address, true) + await esGmx.mint(rewardDistributor.address, expandDecimals(50000, 18)) + await rewardDistributor.setTokensPerInterval("20667989410000000") // 0.02066798941 esGmx per second + await gmx.setMinter(wallet.address, true) + await gmx.mint(user0.address, expandDecimals(1000, 18)) + + await rewardTracker.setInPrivateStakingMode(true) + await expect(rewardTracker.connect(user0).stake(gmx.address, expandDecimals(1000, 18))) + .to.be.revertedWith("RewardTracker: action not enabled") + + await rewardTracker.setInPrivateStakingMode(false) + + await expect(rewardTracker.connect(user0).stake(user1.address, 0)) + .to.be.revertedWith("RewardTracker: invalid _amount") + + await expect(rewardTracker.connect(user0).stake(user1.address, expandDecimals(1000, 18))) + .to.be.revertedWith("RewardTracker: invalid _depositToken") + + await expect(rewardTracker.connect(user0).stake(gmx.address, expandDecimals(1000, 18))) + .to.be.revertedWith("BaseToken: transfer amount exceeds allowance") + + await gmx.connect(user0).approve(rewardTracker.address, expandDecimals(1000, 18)) + await rewardTracker.connect(user0).stake(gmx.address, expandDecimals(1000, 18)) + expect(await rewardTracker.stakedAmounts(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.depositBalances(user0.address, gmx.address)).eq(expandDecimals(1000, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await rewardTracker.claimable(user0.address)).gt(expandDecimals(1785, 18)) // 50000 / 28 => ~1785 + expect(await rewardTracker.claimable(user0.address)).lt(expandDecimals(1786, 18)) + + await esGmx.mint(user1.address, expandDecimals(500, 18)) + await esGmx.connect(user1).approve(rewardTracker.address, expandDecimals(500, 18)) + await rewardTracker.connect(user1).stake(esGmx.address, expandDecimals(500, 18)) + expect(await rewardTracker.stakedAmounts(user1.address)).eq(expandDecimals(500, 18)) + expect(await rewardTracker.stakedAmounts(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.depositBalances(user0.address, gmx.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.depositBalances(user0.address, esGmx.address)).eq(0) + expect(await rewardTracker.depositBalances(user1.address, gmx.address)).eq(0) + expect(await rewardTracker.depositBalances(user1.address, esGmx.address)).eq(expandDecimals(500, 18)) + expect(await rewardTracker.totalDepositSupply(gmx.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.totalDepositSupply(esGmx.address)).eq(expandDecimals(500, 18)) + + expect(await rewardTracker.averageStakedAmounts(user0.address)).eq(0) + expect(await rewardTracker.cumulativeRewards(user0.address)).eq(0) + expect(await rewardTracker.averageStakedAmounts(user1.address)).eq(0) + expect(await rewardTracker.cumulativeRewards(user1.address)).eq(0) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await rewardTracker.claimable(user0.address)).gt(expandDecimals(1785 + 1190, 18)) + expect(await rewardTracker.claimable(user0.address)).lt(expandDecimals(1786 + 1191, 18)) + + expect(await rewardTracker.claimable(user1.address)).gt(expandDecimals(595, 18)) + expect(await rewardTracker.claimable(user1.address)).lt(expandDecimals(596, 18)) + + await expect(rewardTracker.connect(user0).unstake(esGmx.address, expandDecimals(1001, 18))) + .to.be.revertedWith("RewardTracker: _amount exceeds stakedAmount"); + + await expect(rewardTracker.connect(user0).unstake(esGmx.address, expandDecimals(1000, 18))) + .to.be.revertedWith("RewardTracker: _amount exceeds depositBalance"); + + await expect(rewardTracker.connect(user0).unstake(gmx.address, expandDecimals(1001, 18))) + .to.be.revertedWith("RewardTracker: _amount exceeds stakedAmount"); + + expect(await gmx.balanceOf(user0.address)).eq(0) + await rewardTracker.connect(user0).unstake(gmx.address, expandDecimals(1000, 18)) + expect(await gmx.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.totalDepositSupply(gmx.address)).eq(0) + expect(await rewardTracker.totalDepositSupply(esGmx.address)).eq(expandDecimals(500, 18)) + + expect(await rewardTracker.averageStakedAmounts(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.cumulativeRewards(user0.address)).gt(expandDecimals(1785+ 1190, 18)) + expect(await rewardTracker.cumulativeRewards(user0.address)).lt(expandDecimals(1786+ 1191, 18)) + expect(await rewardTracker.averageStakedAmounts(user1.address)).eq(0) + expect(await rewardTracker.cumulativeRewards(user1.address)).eq(0) + + await expect(rewardTracker.connect(user0).unstake(gmx.address, 1)) + .to.be.revertedWith("RewardTracker: _amount exceeds stakedAmount"); + + expect(await esGmx.balanceOf(user0.address)).eq(0) + await rewardTracker.connect(user0).claim(user2.address) + expect(await esGmx.balanceOf(user2.address)).gt(expandDecimals(1785 + 1190, 18)) + expect(await esGmx.balanceOf(user2.address)).lt(expandDecimals(1786 + 1191, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await rewardTracker.claimable(user0.address)).eq(0) + + expect(await rewardTracker.claimable(user1.address)).gt(expandDecimals(595 + 1785, 18)) + expect(await rewardTracker.claimable(user1.address)).lt(expandDecimals(596 + 1786, 18)) + + await gmx.mint(user1.address, expandDecimals(300, 18)) + await gmx.connect(user1).approve(rewardTracker.address, expandDecimals(300, 18)) + await rewardTracker.connect(user1).stake(gmx.address, expandDecimals(300, 18)) + expect(await rewardTracker.totalDepositSupply(gmx.address)).eq(expandDecimals(300, 18)) + expect(await rewardTracker.totalDepositSupply(esGmx.address)).eq(expandDecimals(500, 18)) + + expect(await rewardTracker.averageStakedAmounts(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.cumulativeRewards(user0.address)).gt(expandDecimals(1785+ 1190, 18)) + expect(await rewardTracker.cumulativeRewards(user0.address)).lt(expandDecimals(1786+ 1191, 18)) + expect(await rewardTracker.averageStakedAmounts(user1.address)).eq(expandDecimals(500, 18)) + expect(await rewardTracker.cumulativeRewards(user1.address)).gt(expandDecimals(595 + 1785, 18)) + expect(await rewardTracker.cumulativeRewards(user1.address)).lt(expandDecimals(596 + 1786, 18)) + + await expect(rewardTracker.connect(user1).unstake(gmx.address, expandDecimals(301, 18))) + .to.be.revertedWith("RewardTracker: _amount exceeds depositBalance"); + + await expect(rewardTracker.connect(user1).unstake(esGmx.address, expandDecimals(501, 18))) + .to.be.revertedWith("RewardTracker: _amount exceeds depositBalance"); + + await increaseTime(provider, 2 * 24 * 60 * 60) + await mineBlock(provider) + + await rewardTracker.connect(user0).claim(user2.address) + await rewardTracker.connect(user1).claim(user3.address) + + expect(await rewardTracker.averageStakedAmounts(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.cumulativeRewards(user0.address)).gt(expandDecimals(1785 + 1190, 18)) + expect(await rewardTracker.cumulativeRewards(user0.address)).lt(expandDecimals(1786 + 1191, 18)) + expect(await rewardTracker.averageStakedAmounts(user1.address)).gt(expandDecimals(679, 18)) + expect(await rewardTracker.averageStakedAmounts(user1.address)).lt(expandDecimals(681, 18)) + expect(await rewardTracker.cumulativeRewards(user1.address)).gt(expandDecimals(595 + 1785 + 1785 * 2, 18)) + expect(await rewardTracker.cumulativeRewards(user1.address)).lt(expandDecimals(596 + 1786 + 1786 * 2, 18)) + + await increaseTime(provider, 2 * 24 * 60 * 60) + await mineBlock(provider) + + await rewardTracker.connect(user0).claim(user2.address) + await rewardTracker.connect(user1).claim(user3.address) + + expect(await rewardTracker.averageStakedAmounts(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.cumulativeRewards(user0.address)).gt(expandDecimals(1785 + 1190, 18)) + expect(await rewardTracker.cumulativeRewards(user0.address)).lt(expandDecimals(1786 + 1191, 18)) + expect(await rewardTracker.averageStakedAmounts(user1.address)).gt(expandDecimals(724, 18)) + expect(await rewardTracker.averageStakedAmounts(user1.address)).lt(expandDecimals(726, 18)) + expect(await rewardTracker.cumulativeRewards(user1.address)).gt(expandDecimals(595 + 1785 + 1785 * 4, 18)) + expect(await rewardTracker.cumulativeRewards(user1.address)).lt(expandDecimals(596 + 1786 + 1786 * 4, 18)) + + expect(await esGmx.balanceOf(user2.address)).eq(await rewardTracker.cumulativeRewards(user0.address)) + expect(await esGmx.balanceOf(user3.address)).eq(await rewardTracker.cumulativeRewards(user1.address)) + + expect(await gmx.balanceOf(user1.address)).eq(0) + expect(await esGmx.balanceOf(user1.address)).eq(0) + await rewardTracker.connect(user1).unstake(gmx.address, expandDecimals(300, 18)) + expect(await gmx.balanceOf(user1.address)).eq(expandDecimals(300, 18)) + expect(await esGmx.balanceOf(user1.address)).eq(0) + await rewardTracker.connect(user1).unstake(esGmx.address, expandDecimals(500, 18)) + expect(await gmx.balanceOf(user1.address)).eq(expandDecimals(300, 18)) + expect(await esGmx.balanceOf(user1.address)).eq(expandDecimals(500, 18)) + expect(await rewardTracker.totalDepositSupply(gmx.address)).eq(0) + expect(await rewardTracker.totalDepositSupply(esGmx.address)).eq(0) + + await rewardTracker.connect(user0).claim(user2.address) + await rewardTracker.connect(user1).claim(user3.address) + + const distributed = expandDecimals(50000, 18).sub(await esGmx.balanceOf(rewardDistributor.address)) + const cumulativeReward0 = await rewardTracker.cumulativeRewards(user0.address) + const cumulativeReward1 = await rewardTracker.cumulativeRewards(user1.address) + const totalCumulativeReward = cumulativeReward0.add(cumulativeReward1) + + expect(distributed).gt(totalCumulativeReward.sub(expandDecimals(1, 18))) + expect(distributed).lt(totalCumulativeReward.add(expandDecimals(1, 18))) + }) + + it("stakeForAccount, unstakeForAccount, claimForAccount", async () => { + await esGmx.setMinter(wallet.address, true) + await esGmx.mint(rewardDistributor.address, expandDecimals(50000, 18)) + await rewardDistributor.setTokensPerInterval("20667989410000000") // 0.02066798941 esGmx per second + await gmx.setMinter(wallet.address, true) + await gmx.mint(wallet.address, expandDecimals(1000, 18)) + + await rewardTracker.setInPrivateStakingMode(true) + await expect(rewardTracker.connect(user0).stake(gmx.address, expandDecimals(1000, 18))) + .to.be.revertedWith("RewardTracker: action not enabled") + + await expect(rewardTracker.connect(user2).stakeForAccount(wallet.address, user0.address, gmx.address, expandDecimals(1000, 18))) + .to.be.revertedWith("RewardTracker: forbidden") + + await rewardTracker.setHandler(user2.address, true) + await expect(rewardTracker.connect(user2).stakeForAccount(wallet.address, user0.address, gmx.address, expandDecimals(1000, 18))) + .to.be.revertedWith("BaseToken: transfer amount exceeds allowance") + + await gmx.connect(wallet).approve(rewardTracker.address, expandDecimals(1000, 18)) + + await rewardTracker.connect(user2).stakeForAccount(wallet.address, user0.address, gmx.address, expandDecimals(1000, 18)) + expect(await rewardTracker.stakedAmounts(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.depositBalances(user0.address, gmx.address)).eq(expandDecimals(1000, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await rewardTracker.claimable(user0.address)).gt(expandDecimals(1785, 18)) // 50000 / 28 => ~1785 + expect(await rewardTracker.claimable(user0.address)).lt(expandDecimals(1786, 18)) + + await rewardTracker.setHandler(user2.address, false) + await expect(rewardTracker.connect(user2).unstakeForAccount(user0.address, esGmx.address, expandDecimals(1000, 18), user1.address)) + .to.be.revertedWith("RewardTracker: forbidden") + + await rewardTracker.setHandler(user2.address, true) + + await expect(rewardTracker.connect(user2).unstakeForAccount(user0.address, esGmx.address, expandDecimals(1000, 18), user1.address)) + .to.be.revertedWith("RewardTracker: _amount exceeds depositBalance") + + await expect(rewardTracker.connect(user2).unstakeForAccount(user0.address, gmx.address, expandDecimals(1001, 18), user1.address)) + .to.be.revertedWith("RewardTracker: _amount exceeds stakedAmount") + + expect(await gmx.balanceOf(user0.address)).eq(0) + expect(await rewardTracker.stakedAmounts(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.depositBalances(user0.address, gmx.address)).eq(expandDecimals(1000, 18)) + + expect(await rewardTracker.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + await rewardTracker.connect(user0).transfer(user1.address, expandDecimals(50, 18)) + expect(await rewardTracker.balanceOf(user0.address)).eq(expandDecimals(950, 18)) + expect(await rewardTracker.balanceOf(user1.address)).eq(expandDecimals(50, 18)) + + await rewardTracker.setInPrivateTransferMode(true) + await expect(rewardTracker.connect(user0).transfer(user1.address, expandDecimals(50, 18))) + .to.be.revertedWith("RewardTracker: forbidden") + + await rewardTracker.setHandler(user2.address, false) + await expect(rewardTracker.connect(user2).transferFrom(user1.address, user0.address, expandDecimals(50, 18))) + .to.be.revertedWith("RewardTracker: transfer amount exceeds allowance") + + await rewardTracker.setHandler(user2.address, true) + await rewardTracker.connect(user2).transferFrom(user1.address, user0.address, expandDecimals(50, 18)) + expect(await rewardTracker.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + expect(await rewardTracker.balanceOf(user1.address)).eq(0) + + await rewardTracker.connect(user2).unstakeForAccount(user0.address, gmx.address, expandDecimals(100, 18), user1.address) + + expect(await gmx.balanceOf(user1.address)).eq(expandDecimals(100, 18)) + expect(await rewardTracker.stakedAmounts(user0.address)).eq(expandDecimals(900, 18)) + expect(await rewardTracker.depositBalances(user0.address, gmx.address)).eq(expandDecimals(900, 18)) + + await expect(rewardTracker.connect(user3).claimForAccount(user0.address, user3.address)) + .to.be.revertedWith("RewardTracker: forbidden") + + expect(await rewardTracker.claimable(user0.address)).gt(expandDecimals(1785, 18)) + expect(await rewardTracker.claimable(user0.address)).lt(expandDecimals(1787, 18)) + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await esGmx.balanceOf(user3.address)).eq(0) + + await rewardTracker.connect(user2).claimForAccount(user0.address, user3.address) + + expect(await rewardTracker.claimable(user0.address)).eq(0) + expect(await esGmx.balanceOf(user3.address)).gt(expandDecimals(1785, 18)) + expect(await esGmx.balanceOf(user3.address)).lt(expandDecimals(1787, 18)) + }) +}) diff --git a/test/staking/Vester.js b/test/staking/Vester.js new file mode 100644 index 00000000..d8068037 --- /dev/null +++ b/test/staking/Vester.js @@ -0,0 +1,820 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed, print } = require("../shared/utilities") +const { toChainlinkPrice } = require("../shared/chainlink") +const { toUsd, toNormalizedPrice } = require("../shared/units") + +use(solidity) + +const secondsPerYear = 365 * 24 * 60 * 60 +const { AddressZero } = ethers.constants + +describe("Vester", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3, user4] = provider.getWallets() + let gmx + let esGmx + let bnGmx + let eth + + beforeEach(async () => { + gmx = await deployContract("GMX", []); + esGmx = await deployContract("EsGMX", []); + bnGmx = await deployContract("MintableBaseToken", ["Bonus GMX", "bnGMX", 0]); + eth = await deployContract("Token", []) + + await esGmx.setMinter(wallet.address, true) + await gmx.setMinter(wallet.address, true) + }) + + it("inits", async () => { + const vester = await deployContract("Vester", [ + "Vested GMX", + "veGMX", + secondsPerYear, + esGmx.address, + AddressZero, + gmx.address, + AddressZero + ]) + + expect(await vester.name()).eq("Vested GMX") + expect(await vester.symbol()).eq("veGMX") + expect(await vester.vestingDuration()).eq(secondsPerYear) + expect(await vester.esToken()).eq(esGmx.address) + expect(await vester.pairToken()).eq(AddressZero) + expect(await vester.claimableToken()).eq(gmx.address) + expect(await vester.rewardTracker()).eq(AddressZero) + expect(await vester.hasPairToken()).eq(false) + expect(await vester.hasRewardTracker()).eq(false) + expect(await vester.hasMaxVestableAmount()).eq(false) + }) + + it("deposit, claim, withdraw", async () => { + const vester = await deployContract("Vester", [ + "Vested GMX", + "veGMX", + secondsPerYear, + esGmx.address, + AddressZero, + gmx.address, + AddressZero + ]) + + await expect(vester.connect(user0).deposit(0)) + .to.be.revertedWith("Vester: invalid _amount") + + await expect(vester.connect(user0).deposit(expandDecimals(1000, 18))) + .to.be.revertedWith("BaseToken: transfer amount exceeds allowance") + + await esGmx.connect(user0).approve(vester.address, expandDecimals(1000, 18)) + + await expect(vester.connect(user0).deposit(expandDecimals(1000, 18))) + .to.be.revertedWith("BaseToken: transfer amount exceeds balance") + + expect(await vester.balanceOf(user0.address)).eq(0) + expect(await vester.getTotalVested(user0.address)).eq(0) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(0) + + await esGmx.mint(user0.address, expandDecimals(1000, 18)) + await vester.connect(user0).deposit(expandDecimals(1000, 18)) + + let blockTime = await getBlockTime(provider) + + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await gmx.balanceOf(user0.address)).eq(0) + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).gt("2730000000000000000") // 1000 / 365 => ~2.739 + expect(await vester.claimable(user0.address)).lt("2750000000000000000") + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await expect(vester.connect(user0).claim(user0.address)) + .to.be.revertedWith("BaseToken: transfer amount exceeds balance") + + await gmx.mint(vester.address, expandDecimals(2000, 18)) + + await vester.connect(user0).claim(user0.address) + blockTime = await getBlockTime(provider) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await gmx.balanceOf(user0.address)).gt("2730000000000000000") + expect(await gmx.balanceOf(user0.address)).lt("2750000000000000000") + + let gmxAmount = await gmx.balanceOf(user0.address) + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18).sub(gmxAmount)) + + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(gmxAmount) + expect(await vester.claimedAmounts(user0.address)).eq(gmxAmount) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await increaseTime(provider, 48 * 60 * 60) + await mineBlock(provider) + + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(gmxAmount) + expect(await vester.claimedAmounts(user0.address)).eq(gmxAmount) + expect(await vester.claimable(user0.address)).gt("5478000000000000000") // 1000 / 365 * 2 => ~5.479 + expect(await vester.claimable(user0.address)).lt("5480000000000000000") + + await increaseTime(provider, (parseInt(365 / 2 - 1)) * 24 * 60 * 60) + await mineBlock(provider) + + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(gmxAmount) + expect(await vester.claimedAmounts(user0.address)).eq(gmxAmount) + expect(await vester.claimable(user0.address)).gt(expandDecimals(500, 18)) // 1000 / 2 => 500 + expect(await vester.claimable(user0.address)).lt(expandDecimals(502, 18)) + + await vester.connect(user0).claim(user0.address) + blockTime = await getBlockTime(provider) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await gmx.balanceOf(user0.address)).gt(expandDecimals(503, 18)) + expect(await gmx.balanceOf(user0.address)).lt(expandDecimals(505, 18)) + + gmxAmount = await gmx.balanceOf(user0.address) + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18).sub(gmxAmount)) + + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(gmxAmount) + expect(await vester.claimedAmounts(user0.address)).eq(gmxAmount) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + // vesting rate should be the same even after claiming + expect(await vester.claimable(user0.address)).gt("2730000000000000000") // 1000 / 365 => ~2.739 + expect(await vester.claimable(user0.address)).lt("2750000000000000000") + + await esGmx.mint(user0.address, expandDecimals(500, 18)) + await esGmx.connect(user0).approve(vester.address, expandDecimals(500, 18)) + await vester.connect(user0).deposit(expandDecimals(500, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await vester.claimable(user0.address)).gt("6840000000000000000") // 1000 / 365 + 1500 / 365 => 6.849 + expect(await vester.claimable(user0.address)).lt("6860000000000000000") + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await gmx.balanceOf(user0.address)).eq(gmxAmount) + expect(await esGmx.balanceOf(user1.address)).eq(0) + expect(await gmx.balanceOf(user1.address)).eq(0) + + await vester.connect(user0).withdraw(user1.address) + + const gmxAmount1 = await gmx.balanceOf(user1.address) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await gmx.balanceOf(user0.address)).eq(gmxAmount) + expect(await esGmx.balanceOf(user1.address)).eq(expandDecimals(1500, 18).sub(gmxAmount).sub(gmxAmount1)) + expect(await gmx.balanceOf(user1.address)).gt("6840000000000000000") + expect(await gmx.balanceOf(user1.address)).lt("6860000000000000000") + + expect(await vester.balanceOf(user0.address)).eq(0) + expect(await vester.getTotalVested(user0.address)).eq(0) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(0) + + await esGmx.connect(user0).approve(vester.address, expandDecimals(1000, 18)) + await esGmx.mint(user0.address, expandDecimals(1000, 18)) + await vester.connect(user0).deposit(expandDecimals(1000, 18)) + blockTime = await getBlockTime(provider) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).gt("2730000000000000000") // 1000 / 365 => ~2.739 + expect(await vester.claimable(user0.address)).lt("2750000000000000000") + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await vester.connect(user0).claim(user0.address) + }) + + it("depositForAccount, claimForAccount", async () => { + const vester = await deployContract("Vester", [ + "Vested GMX", + "veGMX", + secondsPerYear, + esGmx.address, + AddressZero, + gmx.address, + AddressZero + ]) + + await esGmx.connect(user0).approve(vester.address, expandDecimals(1000, 18)) + + expect(await vester.balanceOf(user0.address)).eq(0) + expect(await vester.getTotalVested(user0.address)).eq(0) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(0) + + await esGmx.mint(user0.address, expandDecimals(1000, 18)) + + await expect(vester.connect(user2).depositForAccount(user0.address, expandDecimals(1000, 18))) + .to.be.revertedWith("Vester: forbidden") + + await vester.setHandler(user2.address, true) + await vester.connect(user2).depositForAccount(user0.address, expandDecimals(1000, 18)) + + let blockTime = await getBlockTime(provider) + + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await gmx.balanceOf(user0.address)).eq(0) + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).gt("2730000000000000000") // 1000 / 365 => ~2.739 + expect(await vester.claimable(user0.address)).lt("2750000000000000000") + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await expect(vester.connect(user0).claim(user0.address)) + .to.be.revertedWith("BaseToken: transfer amount exceeds balance") + + await gmx.mint(vester.address, expandDecimals(2000, 18)) + + await expect(vester.connect(user3).claimForAccount(user0.address, user4.address)) + .to.be.revertedWith("Vester: forbidden") + + await vester.setHandler(user3.address, true) + + await vester.connect(user3).claimForAccount(user0.address, user4.address) + blockTime = await getBlockTime(provider) + + expect(await esGmx.balanceOf(user4.address)).eq(0) + expect(await gmx.balanceOf(user4.address)).gt("2730000000000000000") + expect(await gmx.balanceOf(user4.address)).lt("2750000000000000000") + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await gmx.balanceOf(user0.address)).eq(0) + expect(await vester.balanceOf(user0.address)).gt(expandDecimals(996, 18)) + expect(await vester.balanceOf(user0.address)).lt(expandDecimals(998, 18)) + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).gt("2730000000000000000") + expect(await vester.cumulativeClaimAmounts(user0.address)).lt("2750000000000000000") + expect(await vester.claimedAmounts(user0.address)).gt("2730000000000000000") + expect(await vester.claimedAmounts(user0.address)).lt("2750000000000000000") + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + }) + + it("handles multiple deposits", async () => { + const vester = await deployContract("Vester", [ + "Vested GMX", + "veGMX", + secondsPerYear, + esGmx.address, + AddressZero, + gmx.address, + AddressZero + ]) + + await esGmx.connect(user0).approve(vester.address, expandDecimals(1000, 18)) + + expect(await vester.balanceOf(user0.address)).eq(0) + expect(await vester.getTotalVested(user0.address)).eq(0) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(0) + + await esGmx.mint(user0.address, expandDecimals(1000, 18)) + await vester.connect(user0).deposit(expandDecimals(1000, 18)) + + let blockTime = await getBlockTime(provider) + + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await gmx.balanceOf(user0.address)).eq(0) + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1000, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).gt("2730000000000000000") // 1000 / 365 => ~2.739 + expect(await vester.claimable(user0.address)).lt("2750000000000000000") + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await expect(vester.connect(user0).claim(user0.address)) + .to.be.revertedWith("BaseToken: transfer amount exceeds balance") + + await gmx.mint(vester.address, expandDecimals(2000, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await vester.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + + await esGmx.mint(user0.address, expandDecimals(500, 18)) + await esGmx.connect(user0).approve(vester.address, expandDecimals(500, 18)) + await vester.connect(user0).deposit(expandDecimals(500, 18)) + blockTime = await getBlockTime(provider) + + expect(await vester.balanceOf(user0.address)).gt(expandDecimals(1494, 18)) + expect(await vester.balanceOf(user0.address)).lt(expandDecimals(1496, 18)) + expect(await vester.getTotalVested(user0.address)).eq(expandDecimals(1500, 18)) + expect(await vester.cumulativeClaimAmounts(user0.address)).gt("5470000000000000000") // 5.47, 1000 / 365 * 2 => ~5.48 + expect(await vester.cumulativeClaimAmounts(user0.address)).lt("5490000000000000000") // 5.49 + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).gt("5470000000000000000") + expect(await vester.claimable(user0.address)).lt("5490000000000000000") + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(blockTime) + + await vester.connect(user0).withdraw(user1.address) + + expect(await esGmx.balanceOf(user1.address)).gt(expandDecimals(1494, 18)) + expect(await esGmx.balanceOf(user1.address)).lt(expandDecimals(1496, 18)) + expect(await gmx.balanceOf(user1.address)).gt("5470000000000000000") + expect(await gmx.balanceOf(user1.address)).lt("5490000000000000000") + expect(await vester.balanceOf(user0.address)).eq(0) + expect(await vester.getTotalVested(user0.address)).eq(0) + expect(await vester.cumulativeClaimAmounts(user0.address)).eq(0) // 5.47, 1000 / 365 * 2 => ~5.48 + expect(await vester.claimedAmounts(user0.address)).eq(0) + expect(await vester.claimable(user0.address)).eq(0) + expect(await vester.pairAmounts(user0.address)).eq(0) + expect(await vester.lastVestingTimes(user0.address)).eq(0) + }) + + it("handles pairing", async () => { + stakedGmxTracker = await deployContract("RewardTracker", ["Staked GMX", "sGMX"]) + stakedGmxDistributor = await deployContract("RewardDistributor", [esGmx.address, stakedGmxTracker.address]) + await stakedGmxTracker.initialize([gmx.address, esGmx.address], stakedGmxDistributor.address) + await stakedGmxDistributor.updateLastDistributionTime() + + bonusGmxTracker = await deployContract("RewardTracker", ["Staked + Bonus GMX", "sbGMX"]) + bonusGmxDistributor = await deployContract("BonusDistributor", [bnGmx.address, bonusGmxTracker.address]) + await bonusGmxTracker.initialize([stakedGmxTracker.address], bonusGmxDistributor.address) + await bonusGmxDistributor.updateLastDistributionTime() + + feeGmxTracker = await deployContract("RewardTracker", ["Staked + Bonus + Fee GMX", "sbfGMX"]) + feeGmxDistributor = await deployContract("RewardDistributor", [eth.address, feeGmxTracker.address]) + await feeGmxTracker.initialize([bonusGmxTracker.address, bnGmx.address], feeGmxDistributor.address) + await feeGmxDistributor.updateLastDistributionTime() + + await stakedGmxTracker.setInPrivateTransferMode(true) + await stakedGmxTracker.setInPrivateStakingMode(true) + await bonusGmxTracker.setInPrivateTransferMode(true) + await bonusGmxTracker.setInPrivateStakingMode(true) + await bonusGmxTracker.setInPrivateClaimingMode(true) + await feeGmxTracker.setInPrivateTransferMode(true) + await feeGmxTracker.setInPrivateStakingMode(true) + + await esGmx.setMinter(wallet.address, true) + await esGmx.mint(stakedGmxDistributor.address, expandDecimals(50000 * 12, 18)) + await stakedGmxDistributor.setTokensPerInterval("20667989410000000") // 0.02066798941 esGmx per second + + const rewardRouter = await deployContract("RewardRouter", []) + await rewardRouter.initialize( + eth.address, + gmx.address, + esGmx.address, + bnGmx.address, + AddressZero, + stakedGmxTracker.address, + bonusGmxTracker.address, + feeGmxTracker.address, + AddressZero, + AddressZero, + AddressZero + ) + + // allow rewardRouter to stake in stakedGmxTracker + await stakedGmxTracker.setHandler(rewardRouter.address, true) + // allow bonusGmxTracker to stake stakedGmxTracker + await stakedGmxTracker.setHandler(bonusGmxTracker.address, true) + // allow rewardRouter to stake in bonusGmxTracker + await bonusGmxTracker.setHandler(rewardRouter.address, true) + // allow bonusGmxTracker to stake feeGmxTracker + await bonusGmxTracker.setHandler(feeGmxTracker.address, true) + await bonusGmxDistributor.setBonusMultiplier(10000) + // allow rewardRouter to stake in feeGmxTracker + await feeGmxTracker.setHandler(rewardRouter.address, true) + // allow stakedGmxTracker to stake esGmx + await esGmx.setHandler(stakedGmxTracker.address, true) + // allow feeGmxTracker to stake bnGmx + await bnGmx.setHandler(feeGmxTracker.address, true) + // allow rewardRouter to burn bnGmx + await bnGmx.setMinter(rewardRouter.address, true) + + const vester = await deployContract("Vester", [ + "Vested GMX", + "veGMX", + secondsPerYear, + esGmx.address, + feeGmxTracker.address, + gmx.address, + stakedGmxTracker.address + ]) + + expect(await vester.name()).eq("Vested GMX") + expect(await vester.symbol()).eq("veGMX") + expect(await vester.vestingDuration()).eq(secondsPerYear) + expect(await vester.esToken()).eq(esGmx.address) + expect(await vester.pairToken()).eq(feeGmxTracker.address) + expect(await vester.claimableToken()).eq(gmx.address) + expect(await vester.rewardTracker()).eq(stakedGmxTracker.address) + expect(await vester.hasPairToken()).eq(true) + expect(await vester.hasRewardTracker()).eq(true) + expect(await vester.hasMaxVestableAmount()).eq(true) + + // allow vester to transfer feeGmxTracker tokens + await feeGmxTracker.setHandler(vester.address, true) + // allow vester to transfer esGmx tokens + await esGmx.setHandler(vester.address, true) + + await gmx.mint(vester.address, expandDecimals(2000, 18)) + + await gmx.mint(user0.address, expandDecimals(1000, 18)) + await gmx.mint(user1.address, expandDecimals(500, 18)) + await gmx.connect(user0).approve(stakedGmxTracker.address, expandDecimals(1000, 18)) + await gmx.connect(user1).approve(stakedGmxTracker.address, expandDecimals(500, 18)) + + await rewardRouter.connect(user0).stakeGmx(expandDecimals(1000, 18)) + await rewardRouter.connect(user1).stakeGmx(expandDecimals(500, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await stakedGmxTracker.claimable(user0.address)).gt(expandDecimals(1190, 18)) + expect(await stakedGmxTracker.claimable(user0.address)).lt(expandDecimals(1191, 18)) + expect(await stakedGmxTracker.claimable(user1.address)).gt(expandDecimals(594, 18)) + expect(await stakedGmxTracker.claimable(user1.address)).lt(expandDecimals(596, 18)) + + expect(await vester.getMaxVestableAmount(user0.address)).eq(0) + expect(await vester.getMaxVestableAmount(user1.address)).eq(0) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await esGmx.balanceOf(user1.address)).eq(0) + expect(await esGmx.balanceOf(user2.address)).eq(0) + expect(await esGmx.balanceOf(user3.address)).eq(0) + + await stakedGmxTracker.connect(user0).claim(user2.address) + await stakedGmxTracker.connect(user1).claim(user3.address) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await esGmx.balanceOf(user1.address)).eq(0) + expect(await esGmx.balanceOf(user2.address)).gt(expandDecimals(1190, 18)) + expect(await esGmx.balanceOf(user2.address)).lt(expandDecimals(1191, 18)) + expect(await esGmx.balanceOf(user3.address)).gt(expandDecimals(594, 18)) + expect(await esGmx.balanceOf(user3.address)).lt(expandDecimals(596, 18)) + + expect(await vester.getMaxVestableAmount(user0.address)).gt(expandDecimals(1190, 18)) + expect(await vester.getMaxVestableAmount(user0.address)).lt(expandDecimals(1191, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).gt(expandDecimals(594, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).lt(expandDecimals(596, 18)) + expect(await vester.getMaxVestableAmount(user2.address)).eq(0) + expect(await vester.getMaxVestableAmount(user3.address)).eq(0) + + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).gt("830000000000000000") // 0.83, 1000 / 1190 => ~0.84 + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).lt("850000000000000000") // 0.85 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).gt("830000000000000000") // 0.83, 500 / 595 => ~0.84 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).lt("850000000000000000") // 0.85 + expect(await vester.getPairAmount(user2.address, expandDecimals(1, 18))).eq(0) + expect(await vester.getPairAmount(user3.address, expandDecimals(1, 18))).eq(0) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + await stakedGmxTracker.connect(user0).claim(user2.address) + await stakedGmxTracker.connect(user1).claim(user3.address) + + expect(await vester.getMaxVestableAmount(user0.address)).gt(expandDecimals(2380, 18)) + expect(await vester.getMaxVestableAmount(user0.address)).lt(expandDecimals(2382, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).gt(expandDecimals(1189, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).lt(expandDecimals(1191, 18)) + + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).gt("410000000000000000") // 0.41, 1000 / 2380 => ~0.42 + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).lt("430000000000000000") // 0.43 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).gt("410000000000000000") // 0.41, 1000 / 2380 => ~0.42 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).lt("430000000000000000") // 0.43 + + await esGmx.mint(user0.address, expandDecimals(2385, 18)) + await expect(vester.connect(user0).deposit(expandDecimals(2385, 18))) + .to.be.revertedWith("RewardTracker: transfer amount exceeds balance") + + await gmx.mint(user0.address, expandDecimals(500, 18)) + await gmx.connect(user0).approve(stakedGmxTracker.address, expandDecimals(500, 18)) + await rewardRouter.connect(user0).stakeGmx(expandDecimals(500, 18)) + + await expect(vester.connect(user0).deposit(expandDecimals(2385, 18))) + .to.be.revertedWith("Vester: max vestable amount exceeded") + + await gmx.mint(user2.address, expandDecimals(1, 18)) + await expect(vester.connect(user2).deposit(expandDecimals(1, 18))) + .to.be.revertedWith("Vester: max vestable amount exceeded") + + expect(await esGmx.balanceOf(user0.address)).eq(expandDecimals(2385, 18)) + expect(await esGmx.balanceOf(vester.address)).eq(0) + expect(await feeGmxTracker.balanceOf(user0.address)).eq(expandDecimals(1500, 18)) + expect(await feeGmxTracker.balanceOf(vester.address)).eq(0) + + await vester.connect(user0).deposit(expandDecimals(2380, 18)) + + expect(await esGmx.balanceOf(user0.address)).eq(expandDecimals(5, 18)) + expect(await esGmx.balanceOf(vester.address)).eq(expandDecimals(2380, 18)) + expect(await feeGmxTracker.balanceOf(user0.address)).gt(expandDecimals(499, 18)) + expect(await feeGmxTracker.balanceOf(user0.address)).lt(expandDecimals(501, 18)) + expect(await feeGmxTracker.balanceOf(vester.address)).gt(expandDecimals(999, 18)) + expect(await feeGmxTracker.balanceOf(vester.address)).lt(expandDecimals(1001, 18)) + + await rewardRouter.connect(user1).unstakeGmx(expandDecimals(499, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + await stakedGmxTracker.connect(user0).claim(user2.address) + await stakedGmxTracker.connect(user1).claim(user3.address) + + expect(await vester.getMaxVestableAmount(user0.address)).gt(expandDecimals(4164, 18)) + expect(await vester.getMaxVestableAmount(user0.address)).lt(expandDecimals(4166, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).gt(expandDecimals(1190, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).lt(expandDecimals(1192, 18)) + + // (1000 * 2380 / 4164) + (1500 * 1784 / 4164) => 1214.21709894 + // 1214.21709894 / 4164 => ~0.29 + + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).gt("280000000000000000") // 0.28 + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).lt("300000000000000000") // 0.30 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).gt("410000000000000000") // 0.41, 1000 / 2380 => ~0.42 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).lt("430000000000000000") // 0.43 + + await increaseTime(provider, 30 * 24 * 60 * 60) + await mineBlock(provider) + + expect(await feeGmxTracker.balanceOf(user4.address)).eq(0) + expect(await gmx.balanceOf(user4.address)).eq(0) + expect(await esGmx.balanceOf(user4.address)).eq(0) + + await vester.connect(user0).withdraw(user4.address) + + expect(await feeGmxTracker.balanceOf(user4.address)).gt(expandDecimals(999, 18)) + expect(await feeGmxTracker.balanceOf(user4.address)).lt(expandDecimals(1001, 18)) + expect(await gmx.balanceOf(user4.address)).gt(expandDecimals(201, 18)) // 2380 / 12 = ~198 + expect(await gmx.balanceOf(user4.address)).lt(expandDecimals(203, 18)) + expect(await esGmx.balanceOf(user4.address)).gt(expandDecimals(2176, 18)) // 2380 - 202 = 2178 + expect(await esGmx.balanceOf(user4.address)).lt(expandDecimals(2178, 18)) + }) + + it("handles existing pair tokens", async () => { + stakedGmxTracker = await deployContract("RewardTracker", ["Staked GMX", "sGMX"]) + stakedGmxDistributor = await deployContract("RewardDistributor", [esGmx.address, stakedGmxTracker.address]) + await stakedGmxTracker.initialize([gmx.address, esGmx.address], stakedGmxDistributor.address) + await stakedGmxDistributor.updateLastDistributionTime() + + bonusGmxTracker = await deployContract("RewardTracker", ["Staked + Bonus GMX", "sbGMX"]) + bonusGmxDistributor = await deployContract("BonusDistributor", [bnGmx.address, bonusGmxTracker.address]) + await bonusGmxTracker.initialize([stakedGmxTracker.address], bonusGmxDistributor.address) + await bonusGmxDistributor.updateLastDistributionTime() + + feeGmxTracker = await deployContract("RewardTracker", ["Staked + Bonus + Fee GMX", "sbfGMX"]) + feeGmxDistributor = await deployContract("RewardDistributor", [eth.address, feeGmxTracker.address]) + await feeGmxTracker.initialize([bonusGmxTracker.address, bnGmx.address], feeGmxDistributor.address) + await feeGmxDistributor.updateLastDistributionTime() + + await stakedGmxTracker.setInPrivateTransferMode(true) + await stakedGmxTracker.setInPrivateStakingMode(true) + await bonusGmxTracker.setInPrivateTransferMode(true) + await bonusGmxTracker.setInPrivateStakingMode(true) + await bonusGmxTracker.setInPrivateClaimingMode(true) + await feeGmxTracker.setInPrivateTransferMode(true) + await feeGmxTracker.setInPrivateStakingMode(true) + + await esGmx.setMinter(wallet.address, true) + await esGmx.mint(stakedGmxDistributor.address, expandDecimals(50000 * 12, 18)) + await stakedGmxDistributor.setTokensPerInterval("20667989410000000") // 0.02066798941 esGmx per second + + const rewardRouter = await deployContract("RewardRouter", []) + await rewardRouter.initialize( + eth.address, + gmx.address, + esGmx.address, + bnGmx.address, + AddressZero, + stakedGmxTracker.address, + bonusGmxTracker.address, + feeGmxTracker.address, + AddressZero, + AddressZero, + AddressZero + ) + + // allow rewardRouter to stake in stakedGmxTracker + await stakedGmxTracker.setHandler(rewardRouter.address, true) + // allow bonusGmxTracker to stake stakedGmxTracker + await stakedGmxTracker.setHandler(bonusGmxTracker.address, true) + // allow rewardRouter to stake in bonusGmxTracker + await bonusGmxTracker.setHandler(rewardRouter.address, true) + // allow bonusGmxTracker to stake feeGmxTracker + await bonusGmxTracker.setHandler(feeGmxTracker.address, true) + await bonusGmxDistributor.setBonusMultiplier(10000) + // allow rewardRouter to stake in feeGmxTracker + await feeGmxTracker.setHandler(rewardRouter.address, true) + // allow stakedGmxTracker to stake esGmx + await esGmx.setHandler(stakedGmxTracker.address, true) + // allow feeGmxTracker to stake bnGmx + await bnGmx.setHandler(feeGmxTracker.address, true) + // allow rewardRouter to burn bnGmx + await bnGmx.setMinter(rewardRouter.address, true) + + const vester = await deployContract("Vester", [ + "Vested GMX", + "veGMX", + secondsPerYear, + esGmx.address, + feeGmxTracker.address, + gmx.address, + stakedGmxTracker.address + ]) + + expect(await vester.name()).eq("Vested GMX") + expect(await vester.symbol()).eq("veGMX") + expect(await vester.vestingDuration()).eq(secondsPerYear) + expect(await vester.esToken()).eq(esGmx.address) + expect(await vester.pairToken()).eq(feeGmxTracker.address) + expect(await vester.claimableToken()).eq(gmx.address) + expect(await vester.rewardTracker()).eq(stakedGmxTracker.address) + expect(await vester.hasPairToken()).eq(true) + expect(await vester.hasRewardTracker()).eq(true) + expect(await vester.hasMaxVestableAmount()).eq(true) + + // allow vester to transfer feeGmxTracker tokens + await feeGmxTracker.setHandler(vester.address, true) + // allow vester to transfer esGmx tokens + await esGmx.setHandler(vester.address, true) + + await gmx.mint(vester.address, expandDecimals(2000, 18)) + + await gmx.mint(user0.address, expandDecimals(1000, 18)) + await gmx.mint(user1.address, expandDecimals(500, 18)) + await gmx.connect(user0).approve(stakedGmxTracker.address, expandDecimals(1000, 18)) + await gmx.connect(user1).approve(stakedGmxTracker.address, expandDecimals(500, 18)) + + await rewardRouter.connect(user0).stakeGmx(expandDecimals(1000, 18)) + await rewardRouter.connect(user1).stakeGmx(expandDecimals(500, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await stakedGmxTracker.claimable(user0.address)).gt(expandDecimals(1190, 18)) + expect(await stakedGmxTracker.claimable(user0.address)).lt(expandDecimals(1191, 18)) + expect(await stakedGmxTracker.claimable(user1.address)).gt(expandDecimals(594, 18)) + expect(await stakedGmxTracker.claimable(user1.address)).lt(expandDecimals(596, 18)) + + expect(await vester.getMaxVestableAmount(user0.address)).eq(0) + expect(await vester.getMaxVestableAmount(user1.address)).eq(0) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await esGmx.balanceOf(user1.address)).eq(0) + expect(await esGmx.balanceOf(user2.address)).eq(0) + expect(await esGmx.balanceOf(user3.address)).eq(0) + + await stakedGmxTracker.connect(user0).claim(user2.address) + await stakedGmxTracker.connect(user1).claim(user3.address) + + expect(await esGmx.balanceOf(user0.address)).eq(0) + expect(await esGmx.balanceOf(user1.address)).eq(0) + expect(await esGmx.balanceOf(user2.address)).gt(expandDecimals(1190, 18)) + expect(await esGmx.balanceOf(user2.address)).lt(expandDecimals(1191, 18)) + expect(await esGmx.balanceOf(user3.address)).gt(expandDecimals(594, 18)) + expect(await esGmx.balanceOf(user3.address)).lt(expandDecimals(596, 18)) + + expect(await vester.getMaxVestableAmount(user0.address)).gt(expandDecimals(1190, 18)) + expect(await vester.getMaxVestableAmount(user0.address)).lt(expandDecimals(1191, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).gt(expandDecimals(594, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).lt(expandDecimals(596, 18)) + expect(await vester.getMaxVestableAmount(user2.address)).eq(0) + expect(await vester.getMaxVestableAmount(user3.address)).eq(0) + + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).gt("830000000000000000") // 0.83, 1000 / 1190 => ~0.84 + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).lt("850000000000000000") // 0.85 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).gt("830000000000000000") // 0.83, 500 / 595 => ~0.84 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).lt("850000000000000000") // 0.85 + expect(await vester.getPairAmount(user2.address, expandDecimals(1, 18))).eq(0) + expect(await vester.getPairAmount(user3.address, expandDecimals(1, 18))).eq(0) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + await stakedGmxTracker.connect(user0).claim(user2.address) + await stakedGmxTracker.connect(user1).claim(user3.address) + + expect(await esGmx.balanceOf(user2.address)).gt(expandDecimals(2380, 18)) + expect(await esGmx.balanceOf(user2.address)).lt(expandDecimals(2382, 18)) + expect(await esGmx.balanceOf(user3.address)).gt(expandDecimals(1189, 18)) + expect(await esGmx.balanceOf(user3.address)).lt(expandDecimals(1191, 18)) + + expect(await vester.getMaxVestableAmount(user0.address)).gt(expandDecimals(2380, 18)) + expect(await vester.getMaxVestableAmount(user0.address)).lt(expandDecimals(2382, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).gt(expandDecimals(1189, 18)) + expect(await vester.getMaxVestableAmount(user1.address)).lt(expandDecimals(1191, 18)) + + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).gt("410000000000000000") // 0.41, 1000 / 2380 => ~0.42 + expect(await vester.getPairAmount(user0.address, expandDecimals(1, 18))).lt("430000000000000000") // 0.43 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).gt("410000000000000000") // 0.41, 1000 / 2380 => ~0.42 + expect(await vester.getPairAmount(user1.address, expandDecimals(1, 18))).lt("430000000000000000") // 0.43 + + expect(await vester.getPairAmount(user0.address, expandDecimals(2380, 18))).gt(expandDecimals(999, 18)) + expect(await vester.getPairAmount(user0.address, expandDecimals(2380, 18))).lt(expandDecimals(1000, 18)) + expect(await vester.getPairAmount(user1.address, expandDecimals(1189, 18))).gt(expandDecimals(499, 18)) + expect(await vester.getPairAmount(user1.address, expandDecimals(1189, 18))).lt(expandDecimals(500, 18)) + + expect(await feeGmxTracker.balanceOf(user0.address)).eq(expandDecimals(1000, 18)) + await esGmx.mint(user0.address, expandDecimals(2380, 18)) + await vester.connect(user0).deposit(expandDecimals(2380, 18)) + + expect(await feeGmxTracker.balanceOf(user0.address)).gt(0) + expect(await feeGmxTracker.balanceOf(user0.address)).lt(expandDecimals(1, 18)) + + await increaseTime(provider, 24 * 60 * 60) + await mineBlock(provider) + + expect(await stakedGmxTracker.claimable(user0.address)).gt(expandDecimals(1190, 18)) + expect(await stakedGmxTracker.claimable(user0.address)).lt(expandDecimals(1191, 18)) + + expect(await vester.getMaxVestableAmount(user0.address)).gt(expandDecimals(2380, 18)) + expect(await vester.getMaxVestableAmount(user0.address)).lt(expandDecimals(2382, 18)) + + await stakedGmxTracker.connect(user0).claim(user2.address) + + expect(await vester.getMaxVestableAmount(user0.address)).gt(expandDecimals(3571, 18)) + expect(await vester.getMaxVestableAmount(user0.address)).lt(expandDecimals(3572, 18)) + + expect(await vester.getPairAmount(user0.address, expandDecimals(3570, 18))).gt(expandDecimals(999, 18)) + expect(await vester.getPairAmount(user0.address, expandDecimals(3570, 18))).lt(expandDecimals(1000, 18)) + + const feeGmxTrackerBalance = await feeGmxTracker.balanceOf(user0.address) + + await esGmx.mint(user0.address, expandDecimals(1190, 18)) + await vester.connect(user0).deposit(expandDecimals(1190, 18)) + + expect(feeGmxTrackerBalance).eq(await feeGmxTracker.balanceOf(user0.address)) + + await expect(rewardRouter.connect(user0).unstakeGmx(expandDecimals(2, 18))) + .to.be.revertedWith("RewardTracker: burn amount exceeds balance") + + await vester.connect(user0).withdraw(user0.address) + + await rewardRouter.connect(user0).unstakeGmx(expandDecimals(2, 18)) + }) +}) diff --git a/test/tokens/Bridge.js b/test/tokens/Bridge.js new file mode 100644 index 00000000..8c5945ee --- /dev/null +++ b/test/tokens/Bridge.js @@ -0,0 +1,84 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../shared/utilities") + +use(solidity) + +describe("Bridge", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let gmx + let wgmx + let bridge + + beforeEach(async () => { + gmx = await deployContract("GMX", []) + wgmx = await deployContract("GMX", []) + bridge = await deployContract("Bridge", [gmx.address, wgmx.address]) + }) + + it("wrap, unwrap", async () => { + await gmx.setMinter(wallet.address, true) + await gmx.mint(user0.address, 100) + await gmx.connect(user0).approve(bridge.address, 100) + await expect(bridge.connect(user0).wrap(200, user1.address)) + .to.be.revertedWith("BaseToken: transfer amount exceeds allowance") + + await expect(bridge.connect(user0).wrap(100, user1.address)) + .to.be.revertedWith("BaseToken: transfer amount exceeds balance") + + await wgmx.setMinter(wallet.address, true) + await wgmx.mint(bridge.address, 50) + + await expect(bridge.connect(user0).wrap(100, user1.address)) + .to.be.revertedWith("BaseToken: transfer amount exceeds balance") + + await wgmx.mint(bridge.address, 50) + + expect(await gmx.balanceOf(user0.address)).eq(100) + expect(await gmx.balanceOf(bridge.address)).eq(0) + expect(await wgmx.balanceOf(user1.address)).eq(0) + expect(await wgmx.balanceOf(bridge.address)).eq(100) + + await bridge.connect(user0).wrap(100, user1.address) + + expect(await gmx.balanceOf(user0.address)).eq(0) + expect(await gmx.balanceOf(bridge.address)).eq(100) + expect(await wgmx.balanceOf(user1.address)).eq(100) + expect(await wgmx.balanceOf(bridge.address)).eq(0) + + await wgmx.connect(user1).approve(bridge.address, 100) + + expect(await gmx.balanceOf(user2.address)).eq(0) + expect(await gmx.balanceOf(bridge.address)).eq(100) + expect(await wgmx.balanceOf(user1.address)).eq(100) + expect(await wgmx.balanceOf(bridge.address)).eq(0) + + await bridge.connect(user1).unwrap(100, user2.address) + + expect(await gmx.balanceOf(user2.address)).eq(100) + expect(await gmx.balanceOf(bridge.address)).eq(0) + expect(await wgmx.balanceOf(user1.address)).eq(0) + expect(await wgmx.balanceOf(bridge.address)).eq(100) + }) + + it("withdrawToken", async () => { + await gmx.setMinter(wallet.address, true) + await gmx.mint(bridge.address, 100) + + await expect(bridge.connect(user0).withdrawToken(gmx.address, user1.address, 100)) + .to.be.revertedWith("Governable: forbidden") + + await expect(bridge.connect(user0).setGov(user0.address)) + .to.be.revertedWith("Governable: forbidden") + + await bridge.connect(wallet).setGov(user0.address) + + expect(await gmx.balanceOf(user1.address)).eq(0) + expect(await gmx.balanceOf(bridge.address)).eq(100) + await bridge.connect(user0).withdrawToken(gmx.address, user1.address, 100) + expect(await gmx.balanceOf(user1.address)).eq(100) + expect(await gmx.balanceOf(bridge.address)).eq(0) + }) +}) diff --git a/test/tokens/TimeDistributor.js b/test/tokens/TimeDistributor.js new file mode 100644 index 00000000..3f99f9dc --- /dev/null +++ b/test/tokens/TimeDistributor.js @@ -0,0 +1,84 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../shared/utilities") + +use(solidity) + +describe("TimeDistributor", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let token + let distributor + + beforeEach(async () => { + token = await deployContract("Token", []) + await token.mint(wallet.address, 1000) + distributor = await deployContract("TimeDistributor", []) + }) + + it("distribute", async () => { + await token.transfer(distributor.address, 1000) + await expect(distributor.connect(user0).setDistribution([user1.address], [100], [token.address])) + .to.be.revertedWith("TimeDistributor: forbidden") + + await distributor.connect(wallet).setDistribution([user1.address], [100], [token.address]) + expect(await distributor.getDistributionAmount(user1.address)).eq(0) + + await increaseTime(provider, 60 * 60 + 10) + await mineBlock(provider) + + expect(await distributor.getDistributionAmount(user1.address)).eq(100) + + await increaseTime(provider, 2 * 60 * 60 + 10) + await mineBlock(provider) + + expect(await distributor.getDistributionAmount(user1.address)).eq(300) + + expect(await token.balanceOf(user1.address)).eq(0) + await distributor.connect(user1).distribute() + expect(await token.balanceOf(user1.address)).eq(300) + + expect(await distributor.getDistributionAmount(user1.address)).eq(0) + + await increaseTime(provider, 10) + await mineBlock(provider) + + expect(await distributor.getDistributionAmount(user1.address)).eq(0) + + await increaseTime(provider, 60) + await mineBlock(provider) + + await distributor.connect(user1).distribute() + + await increaseTime(provider, 60 * 60 - 60) + await mineBlock(provider) + + expect(await distributor.getDistributionAmount(user1.address)).eq(100) + + await distributor.connect(user1).distribute() + + await expect(distributor.connect(user0).setTokensPerInterval(user1.address, 50)) + .to.be.revertedWith("TimeDistributor: forbidden") + + expect(await distributor.tokensPerInterval(user1.address)).eq(100) + await distributor.connect(wallet).setTokensPerInterval(user1.address, 50) + expect(await distributor.tokensPerInterval(user1.address)).eq(50) + + await expect(distributor.connect(user0).updateLastDistributionTime(user1.address)) + .to.be.revertedWith("TimeDistributor: forbidden") + + await increaseTime(provider, 60) + await mineBlock(provider) + + const lastDistributionTime = await distributor.lastDistributionTime(user1.address) + await distributor.connect(wallet).updateLastDistributionTime(user1.address) + expect(await distributor.lastDistributionTime(user1.address)).eq(lastDistributionTime) + + await increaseTime(provider, 60 * 60 + 1) + await mineBlock(provider) + + await distributor.connect(wallet).updateLastDistributionTime(user1.address) + expect(await distributor.lastDistributionTime(user1.address)).eq(lastDistributionTime.add(60 * 60)) + }) +}) diff --git a/test/tokens/USDG.js b/test/tokens/USDG.js new file mode 100644 index 00000000..c7fb3bb2 --- /dev/null +++ b/test/tokens/USDG.js @@ -0,0 +1,75 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../shared/utilities") + +use(solidity) + +describe("USDG", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let usdg + + beforeEach(async () => { + usdg = await deployContract("USDG", [user1.address]) + }) + + it("addVault", async () => { + await expect(usdg.connect(user0).addVault(user0.address)) + .to.be.revertedWith("YieldToken: forbidden") + + await usdg.setGov(user0.address) + + expect(await usdg.vaults(user0.address)).eq(false) + await usdg.connect(user0).addVault(user0.address) + expect(await usdg.vaults(user0.address)).eq(true) + }) + + it("removeVault", async () => { + await expect(usdg.connect(user0).removeVault(user0.address)) + .to.be.revertedWith("YieldToken: forbidden") + + await usdg.setGov(user0.address) + + expect(await usdg.vaults(user0.address)).eq(false) + await usdg.connect(user0).addVault(user0.address) + expect(await usdg.vaults(user0.address)).eq(true) + await usdg.connect(user0).removeVault(user0.address) + expect(await usdg.vaults(user0.address)).eq(false) + }) + + it("mint", async () => { + expect(await usdg.balanceOf(user1.address)).eq(0) + await usdg.connect(user1).mint(user1.address, 1000) + expect(await usdg.balanceOf(user1.address)).eq(1000) + expect(await usdg.totalSupply()).eq(1000) + + await expect(usdg.connect(user0).mint(user1.address, 1000)) + .to.be.revertedWith("USDG: forbidden") + + await usdg.addVault(user0.address) + + expect(await usdg.balanceOf(user1.address)).eq(1000) + await usdg.connect(user0).mint(user1.address, 500) + expect(await usdg.balanceOf(user1.address)).eq(1500) + expect(await usdg.totalSupply()).eq(1500) + }) + + it("burn", async () => { + expect(await usdg.balanceOf(user1.address)).eq(0) + await usdg.connect(user1).mint(user1.address, 1000) + expect(await usdg.balanceOf(user1.address)).eq(1000) + await usdg.connect(user1).burn(user1.address, 300) + expect(await usdg.balanceOf(user1.address)).eq(700) + expect(await usdg.totalSupply()).eq(700) + + await expect(usdg.connect(user0).burn(user1.address, 100)) + .to.be.revertedWith("USDG: forbidden") + + await usdg.addVault(user0.address) + + await usdg.connect(user0).burn(user1.address, 100) + expect(await usdg.balanceOf(user1.address)).eq(600) + expect(await usdg.totalSupply()).eq(600) + }) +}) diff --git a/test/tokens/YieldFarm.js b/test/tokens/YieldFarm.js new file mode 100644 index 00000000..1ef4b415 --- /dev/null +++ b/test/tokens/YieldFarm.js @@ -0,0 +1,56 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../shared/utilities") + +use(solidity) + +describe("YieldFarm", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let farm + let token + + beforeEach(async () => { + token = await deployContract("Token", []) + await token.mint(wallet.address, 1000) + farm = await deployContract("YieldFarm", ["Yield Farm", "FARM", token.address]) + }) + + it("stake", async () => { + await expect(farm.stake(1000)) + .to.be.revertedWith("ERC20: transfer amount exceeds allowance") + + await token.connect(wallet).approve(farm.address, 2000) + await expect(farm.stake(2000)) + .to.be.revertedWith("ERC20: transfer amount exceeds balance") + + expect(await farm.balanceOf(wallet.address)).eq(0) + expect(await token.balanceOf(wallet.address)).eq(1000) + expect(await token.balanceOf(farm.address)).eq(0) + await farm.stake(1000) + expect(await farm.balanceOf(wallet.address)).eq(1000) + expect(await token.balanceOf(wallet.address)).eq(0) + expect(await token.balanceOf(farm.address)).eq(1000) + }) + + it("unstake", async () => { + await token.connect(wallet).approve(farm.address, 2000) + expect(await farm.balanceOf(wallet.address)).eq(0) + expect(await token.balanceOf(wallet.address)).eq(1000) + expect(await token.balanceOf(farm.address)).eq(0) + await farm.stake(1000) + expect(await farm.balanceOf(wallet.address)).eq(1000) + expect(await token.balanceOf(wallet.address)).eq(0) + expect(await token.balanceOf(farm.address)).eq(1000) + + await expect(farm.unstake(1001)) + .to.be.revertedWith("YieldToken: burn amount exceeds balance") + + await farm.unstake(1000) + + expect(await farm.balanceOf(wallet.address)).eq(0) + expect(await token.balanceOf(wallet.address)).eq(1000) + expect(await token.balanceOf(farm.address)).eq(0) + }) +}) diff --git a/test/tokens/YieldToken.js b/test/tokens/YieldToken.js new file mode 100644 index 00000000..becd66e2 --- /dev/null +++ b/test/tokens/YieldToken.js @@ -0,0 +1,146 @@ +const { expect, use } = require("chai") +const { solidity } = require("ethereum-waffle") +const { deployContract } = require("../shared/fixtures") +const { expandDecimals, getBlockTime, increaseTime, mineBlock, reportGasUsed } = require("../shared/utilities") + +use(solidity) + +describe("YieldToken", function () { + const provider = waffle.provider + const [wallet, user0, user1, user2, user3] = provider.getWallets() + let bnb + let btc + let yieldToken + let distributor0 + let yieldTracker0 + + beforeEach(async () => { + bnb = await deployContract("Token", []) + btc = await deployContract("Token", []) + + yieldToken = await deployContract("YieldToken", ["Token", "TKN", 1000]) + + distributor0 = await deployContract("TimeDistributor", []) + yieldTracker0 = await deployContract("YieldTracker", [yieldToken.address]) + + distributor1 = await deployContract("TimeDistributor", []) + yieldTracker1 = await deployContract("YieldTracker", [yieldToken.address]) + + await yieldTracker0.setDistributor(distributor0.address) + await distributor0.setDistribution([yieldTracker0.address], [1000], [bnb.address]) + + await yieldTracker1.setDistributor(distributor1.address) + await distributor1.setDistribution([yieldTracker1.address], [2000], [btc.address]) + }) + + it("claim", async () => { + await bnb.mint(distributor0.address, 5000) + await btc.mint(distributor1.address, 5000) + + const tx0 = await yieldToken.transfer(user0.address, 200) + await reportGasUsed(provider, tx0, "tranfer0 gas used") + + await increaseTime(provider, 60 * 60 + 10) + await mineBlock(provider) + + await yieldToken.setYieldTrackers([yieldTracker0.address]) + await yieldToken.connect(wallet).claim(user1.address) + expect(await bnb.balanceOf(user1.address)).eq(800) + expect(await bnb.balanceOf(yieldTracker0.address)).eq(200) + expect(await btc.balanceOf(user1.address)).eq(0) + expect(await btc.balanceOf(yieldTracker1.address)).eq(0) + + const tx1 = await yieldToken.transfer(user0.address, 200) + await reportGasUsed(provider, tx1, "tranfer1 gas used") + + await yieldToken.setYieldTrackers([yieldTracker0.address, yieldTracker1.address]) + + const tx2 = await yieldToken.transfer(user0.address, 200) + await reportGasUsed(provider, tx2, "tranfer2 gas used") + + expect(await btc.balanceOf(yieldTracker1.address)).eq(2000) + + expect(await bnb.balanceOf(user2.address)).eq(0) + expect(await btc.balanceOf(user2.address)).eq(0) + + await yieldToken.connect(user0).claim(user2.address) + + expect(await bnb.balanceOf(user2.address)).eq(200) + expect(await btc.balanceOf(user2.address)).eq(800) + + expect(await bnb.balanceOf(user3.address)).eq(0) + expect(await btc.balanceOf(user3.address)).eq(0) + + await yieldToken.connect(wallet).claim(user3.address) + + expect(await bnb.balanceOf(user3.address)).eq(0) + expect(await btc.balanceOf(user3.address)).eq(1200) + + const tx3 = await yieldToken.transfer(user0.address, 200) + await reportGasUsed(provider, tx3, "tranfer3 gas used") + }) + + it("nonStakingAccounts", async () => { + await bnb.mint(distributor0.address, 5000) + await btc.mint(distributor1.address, 5000) + await yieldToken.setYieldTrackers([yieldTracker0.address, yieldTracker1.address]) + + await yieldToken.transfer(user0.address, 100) + await yieldToken.transfer(user1.address, 300) + + await increaseTime(provider, 60 * 60 + 10) + await mineBlock(provider) + + expect(await bnb.balanceOf(wallet.address)).eq(0) + expect(await btc.balanceOf(wallet.address)).eq(0) + await yieldToken.connect(wallet).claim(wallet.address) + expect(await bnb.balanceOf(wallet.address)).eq(600) + expect(await btc.balanceOf(wallet.address)).eq(1200) + + expect(await bnb.balanceOf(user0.address)).eq(0) + expect(await btc.balanceOf(user0.address)).eq(0) + await yieldToken.connect(user0).claim(user0.address) + expect(await bnb.balanceOf(user0.address)).eq(100) + expect(await btc.balanceOf(user0.address)).eq(200) + + expect(await bnb.balanceOf(user1.address)).eq(0) + expect(await btc.balanceOf(user1.address)).eq(0) + await yieldToken.connect(user1).claim(user1.address) + expect(await bnb.balanceOf(user1.address)).eq(300) + expect(await btc.balanceOf(user1.address)).eq(600) + + expect(await yieldToken.balanceOf(wallet.address)).eq(600) + expect(await yieldToken.stakedBalance(wallet.address)).eq(600) + expect(await yieldToken.totalStaked()).eq(1000) + await yieldToken.addNonStakingAccount(wallet.address) + expect(await yieldToken.balanceOf(wallet.address)).eq(600) + expect(await yieldToken.stakedBalance(wallet.address)).eq(0) + expect(await yieldToken.totalStaked()).eq(400) + + await yieldToken.transfer(user0.address, 100) + expect(await yieldToken.totalStaked()).eq(500) + expect(await yieldToken.balanceOf(user0.address)).eq(200) + expect(await yieldToken.balanceOf(user1.address)).eq(300) + + await increaseTime(provider, 60 * 60 + 10) + await mineBlock(provider) + + expect(await bnb.balanceOf(wallet.address)).eq(600) + expect(await btc.balanceOf(wallet.address)).eq(1200) + await yieldToken.connect(wallet).claim(wallet.address) + expect(await bnb.balanceOf(wallet.address)).eq(600) + expect(await btc.balanceOf(wallet.address)).eq(1200) + + expect(await bnb.balanceOf(user0.address)).eq(100) + expect(await btc.balanceOf(user0.address)).eq(200) + await yieldToken.connect(user0).claim(user0.address) + expect(await bnb.balanceOf(user0.address)).eq(100 + 400) + expect(await btc.balanceOf(user0.address)).eq(200 + 800) + + expect(await bnb.balanceOf(user1.address)).eq(300) + expect(await btc.balanceOf(user1.address)).eq(600) + await yieldToken.connect(user1).claim(user1.address) + expect(await bnb.balanceOf(user1.address)).eq(300 + 600) + expect(await btc.balanceOf(user1.address)).eq(600 + 1200) + }) +})