├── .gitignore ├── LICENSE ├── README.md ├── contracts ├── Oracle.sol ├── Pool.sol ├── PoolCAP.sol ├── Rewards.sol ├── Router.sol ├── Trading.sol ├── Treasury.sol ├── interfaces │ ├── IERC20.sol │ ├── IOracle.sol │ ├── IPool.sol │ ├── IRewards.sol │ ├── IRouter.sol │ ├── ITrading.sol │ └── ITreasury.sol ├── libraries │ ├── Address.sol │ └── SafeERC20.sol └── mocks │ └── MockToken.sol ├── hardhat.config.js ├── package-lock.json ├── package.json └── scripts ├── deploy-avax.js ├── deploy-local.js ├── deploy-new-pools.js ├── deploy.js ├── gas.js └── tester.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | 4 | #Hardhat files 5 | cache 6 | artifacts 7 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Business Source License 1.1 2 | 3 | License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. 4 | "Business Source License" is a trademark of MariaDB Corporation Ab. 5 | 6 | ----------------------------------------------------------------------------- 7 | 8 | Parameters 9 | 10 | Licensor: Cap 11 | 12 | Licensed Work: Cap Protocol 13 | The Licensed Work is (c) 2021 Cap 14 | 15 | Change Date: The earlier of 2025-12-01 16 | 17 | Change License: GNU General Public License v2.0 or later 18 | 19 | ----------------------------------------------------------------------------- 20 | 21 | Terms 22 | 23 | The Licensor hereby grants you the right to copy, modify, create derivative 24 | works, redistribute, and make non-production use of the Licensed Work. The 25 | Licensor may make an Additional Use Grant, above, permitting limited 26 | production use. 27 | 28 | Effective on the Change Date, or the fourth anniversary of the first publicly 29 | available distribution of a specific version of the Licensed Work under this 30 | License, whichever comes first, the Licensor hereby grants you rights under 31 | the terms of the Change License, and the rights granted in the paragraph 32 | above terminate. 33 | 34 | If your use of the Licensed Work does not comply with the requirements 35 | currently in effect as described in this License, you must purchase a 36 | commercial license from the Licensor, its affiliated entities, or authorized 37 | resellers, or you must refrain from using the Licensed Work. 38 | 39 | All copies of the original and modified Licensed Work, and derivative works 40 | of the Licensed Work, are subject to this License. This License applies 41 | separately for each version of the Licensed Work and the Change Date may vary 42 | for each version of the Licensed Work released by Licensor. 43 | 44 | You must conspicuously display this License on each original or modified copy 45 | of the Licensed Work. If you receive the Licensed Work in original or 46 | modified form from a third party, the terms and conditions set forth in this 47 | License apply to your use of that work. 48 | 49 | Any use of the Licensed Work in violation of this License will automatically 50 | terminate your rights under this License for the current and all other 51 | versions of the Licensed Work. 52 | 53 | This License does not grant you any right in any trademark or logo of 54 | Licensor or its affiliates (provided that you may use a trademark or logo of 55 | Licensor as expressly required by this License). 56 | 57 | TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON 58 | AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, 59 | EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF 60 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND 61 | TITLE. 62 | 63 | MariaDB hereby grants you permission to use this License’s text to license 64 | your works, and to refer to it using the trademark "Business Source License", 65 | as long as you comply with the Covenants of Licensor below. 66 | 67 | ----------------------------------------------------------------------------- 68 | 69 | Covenants of Licensor 70 | 71 | In consideration of the right to use this License’s text and the "Business 72 | Source License" name and trademark, Licensor covenants to MariaDB, and to all 73 | other recipients of the licensed work to be provided by Licensor: 74 | 75 | 1. To specify as the Change License the GPL Version 2.0 or any later version, 76 | or a license that is compatible with GPL Version 2.0 or a later version, 77 | where "compatible" means that software provided under the Change License can 78 | be included in a program with software provided under GPL Version 2.0 or a 79 | later version. Licensor may specify additional Change Licenses without 80 | limitation. 81 | 82 | 2. To either: (a) specify an additional grant of rights to use that does not 83 | impose any additional restriction on the right granted in this License, as 84 | the Additional Use Grant; or (b) insert the text "None". 85 | 86 | 3. To specify a Change Date. 87 | 88 | 4. Not to modify this License in any other way. 89 | 90 | ----------------------------------------------------------------------------- 91 | 92 | Notice 93 | 94 | The Business Source License (this document, or the "License") is not an Open 95 | Source license. However, the Licensed Work will eventually be made available 96 | under an Open Source License, as stated in this License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Docs 2 | 3 | Documentation can be found [here](https://docs.cap.finance). 4 | 5 | ## Bug Bounty 6 | 7 | CAP will pay you legally acceptable money for finding bugs and exploits in the protocol. Check out the details of the bug bounty program on [Immunefi](https://immunefi.com/bounty/cap/). 8 | -------------------------------------------------------------------------------- /contracts/Oracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | import "./interfaces/IRouter.sol"; 5 | import "./interfaces/ITreasury.sol"; 6 | import "./interfaces/ITrading.sol"; 7 | 8 | contract Oracle { 9 | 10 | // Contract dependencies 11 | address public owner; 12 | address public router; 13 | address public darkOracle; 14 | address public treasury; 15 | address public trading; 16 | 17 | // Variables 18 | uint256 public requestsPerFunding = 100; 19 | uint256 public costPerRequest = 6 * 10**14; // 0.0006 ETH 20 | uint256 public requestsSinceFunding; 21 | 22 | event SettlementError( 23 | address indexed user, 24 | address currency, 25 | bytes32 productId, 26 | bool isLong, 27 | string reason 28 | ); 29 | 30 | constructor() { 31 | owner = msg.sender; 32 | } 33 | 34 | // Governance methods 35 | 36 | function setOwner(address newOwner) external onlyOwner { 37 | owner = newOwner; 38 | } 39 | 40 | function setRouter(address _router) external onlyOwner { 41 | router = _router; 42 | trading = IRouter(router).trading(); 43 | treasury = IRouter(router).treasury(); 44 | darkOracle = IRouter(router).darkOracle(); 45 | } 46 | 47 | function setParams( 48 | uint256 _requestsPerFunding, 49 | uint256 _costPerRequest 50 | ) external onlyOwner { 51 | requestsPerFunding = _requestsPerFunding; 52 | costPerRequest = _costPerRequest; 53 | } 54 | 55 | // Methods 56 | 57 | function settleOrders( 58 | address[] calldata users, 59 | bytes32[] calldata productIds, 60 | address[] calldata currencies, 61 | bool[] calldata directions, 62 | uint256[] calldata prices 63 | ) external onlyDarkOracle { 64 | 65 | for (uint256 i = 0; i < users.length; i++) { 66 | 67 | address user = users[i]; 68 | address currency = currencies[i]; 69 | bytes32 productId = productIds[i]; 70 | bool isLong = directions[i]; 71 | 72 | try ITrading(trading).settleOrder(user, productId, currency, isLong, prices[i]) { 73 | 74 | } catch Error(string memory reason) { 75 | emit SettlementError( 76 | user, 77 | currency, 78 | productId, 79 | isLong, 80 | reason 81 | ); 82 | } 83 | 84 | } 85 | 86 | _tallyOracleRequests(users.length); 87 | 88 | } 89 | 90 | function liquidatePositions( 91 | address[] calldata users, 92 | bytes32[] calldata productIds, 93 | address[] calldata currencies, 94 | bool[] calldata directions, 95 | uint256[] calldata prices 96 | ) external onlyDarkOracle { 97 | for (uint256 i = 0; i < users.length; i++) { 98 | address user = users[i]; 99 | bytes32 productId = productIds[i]; 100 | address currency = currencies[i]; 101 | bool isLong = directions[i]; 102 | ITrading(trading).liquidatePosition(user, productId, currency, isLong, prices[i]); 103 | } 104 | _tallyOracleRequests(users.length); 105 | } 106 | 107 | function _tallyOracleRequests(uint256 newRequests) internal { 108 | if (newRequests == 0) return; 109 | requestsSinceFunding += newRequests; 110 | if (requestsSinceFunding >= requestsPerFunding) { 111 | requestsSinceFunding = 0; 112 | ITreasury(treasury).fundOracle(darkOracle, costPerRequest * requestsPerFunding); 113 | } 114 | } 115 | 116 | // Modifiers 117 | 118 | modifier onlyOwner() { 119 | require(msg.sender == owner, "!owner"); 120 | _; 121 | } 122 | 123 | modifier onlyDarkOracle() { 124 | require(msg.sender == darkOracle, "!dark-oracle"); 125 | _; 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /contracts/Pool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | import "./libraries/SafeERC20.sol"; 5 | import "./libraries/Address.sol"; 6 | 7 | import "./interfaces/IRouter.sol"; 8 | import "./interfaces/IRewards.sol"; 9 | 10 | contract Pool { 11 | 12 | using SafeERC20 for IERC20; 13 | using Address for address payable; 14 | 15 | // Contracts 16 | address public owner; 17 | address public router; 18 | address public trading; 19 | 20 | uint256 public withdrawFee = 30; // 0.3% 21 | 22 | address public currency; 23 | address public rewards; // contract 24 | 25 | uint256 public utilizationMultiplier = 100; // in bps 26 | 27 | uint256 public maxCap = 1000000 ether; 28 | 29 | mapping(address => uint256) private balances; // account => amount staked 30 | uint256 public totalSupply; 31 | 32 | mapping(address => uint256) lastDeposited; 33 | uint256 public minDepositTime = 1 hours; 34 | 35 | uint256 public openInterest; 36 | 37 | uint256 public constant UNIT = 10**18; 38 | 39 | // Events 40 | event Deposit( 41 | address indexed user, 42 | address indexed currency, 43 | uint256 amount, 44 | uint256 clpAmount 45 | ); 46 | event Withdraw( 47 | address indexed user, 48 | address indexed currency, 49 | uint256 amount, 50 | uint256 clpAmount 51 | ); 52 | 53 | constructor(address _currency) { 54 | owner = msg.sender; 55 | currency = _currency; 56 | } 57 | 58 | // Governance methods 59 | 60 | function setOwner(address newOwner) external onlyOwner { 61 | owner = newOwner; 62 | } 63 | 64 | function setRouter(address _router) external onlyOwner { 65 | router = _router; 66 | trading = IRouter(router).trading(); 67 | rewards = IRouter(router).getPoolRewards(currency); 68 | } 69 | 70 | function setParams( 71 | uint256 _minDepositTime, 72 | uint256 _utilizationMultiplier, 73 | uint256 _maxCap, 74 | uint256 _withdrawFee 75 | ) external onlyOwner { 76 | minDepositTime = _minDepositTime; 77 | utilizationMultiplier = _utilizationMultiplier; 78 | maxCap = _maxCap; 79 | withdrawFee = _withdrawFee; 80 | } 81 | 82 | // Open interest 83 | function updateOpenInterest(uint256 amount, bool isDecrease) external onlyTrading { 84 | if (isDecrease) { 85 | if (openInterest <= amount) { 86 | openInterest = 0; 87 | } else { 88 | openInterest -= amount; 89 | } 90 | } else { 91 | openInterest += amount; 92 | } 93 | } 94 | 95 | // Methods 96 | 97 | function deposit(uint256 amount) external payable { 98 | 99 | uint256 lastBalance = _getCurrentBalance(); 100 | 101 | if (currency == address(0)) { 102 | amount = msg.value; 103 | lastBalance -= amount; 104 | } else { 105 | _transferIn(amount); 106 | } 107 | 108 | require(amount > 0, "!amount"); 109 | require(amount + lastBalance <= maxCap, "!max-cap"); 110 | 111 | uint256 clpAmountToMint = lastBalance == 0 || totalSupply == 0 ? amount : amount * totalSupply / lastBalance; 112 | 113 | lastDeposited[msg.sender] = block.timestamp; 114 | 115 | IRewards(rewards).updateRewards(msg.sender); 116 | 117 | totalSupply += clpAmountToMint; 118 | balances[msg.sender] += clpAmountToMint; 119 | 120 | emit Deposit( 121 | msg.sender, 122 | currency, 123 | amount, 124 | clpAmountToMint 125 | ); 126 | 127 | } 128 | 129 | function withdraw(uint256 currencyAmount) external { 130 | 131 | require(currencyAmount > 0, "!amount"); 132 | require(block.timestamp > lastDeposited[msg.sender] + minDepositTime, "!cooldown"); 133 | 134 | IRewards(rewards).updateRewards(msg.sender); 135 | 136 | // Determine corresponding CLP amount 137 | 138 | uint256 currentBalance = _getCurrentBalance(); 139 | require(currentBalance > 0 && totalSupply > 0, "!empty"); 140 | 141 | uint256 utilization = getUtilization(); 142 | require(utilization < 10**4, "!utilization"); 143 | 144 | // CLP amount 145 | uint256 amount = currencyAmount * totalSupply / currentBalance; 146 | 147 | // Set to max if above max 148 | if (amount >= balances[msg.sender]) { 149 | amount = balances[msg.sender]; 150 | currencyAmount = amount * currentBalance / totalSupply; 151 | } 152 | 153 | uint256 availableBalance = currentBalance * (10**4 - utilization) / 10**4; 154 | uint256 currencyAmountAfterFee = currencyAmount * (10**4 - withdrawFee) / 10**4; 155 | require(currencyAmountAfterFee <= availableBalance, "!available-balance"); 156 | 157 | totalSupply -= amount; 158 | balances[msg.sender] -= amount; 159 | 160 | _transferOut(msg.sender, currencyAmountAfterFee); 161 | 162 | // Send fee to this pool's rewards contract 163 | uint256 feeAmount = currencyAmount - currencyAmountAfterFee; 164 | _transferOut(rewards, feeAmount); 165 | IRewards(rewards).notifyRewardReceived(feeAmount); 166 | 167 | emit Withdraw( 168 | msg.sender, 169 | currency, 170 | currencyAmountAfterFee, 171 | amount 172 | ); 173 | 174 | } 175 | 176 | function creditUserProfit(address destination, uint256 amount) external onlyTrading { 177 | if (amount == 0) return; 178 | uint256 currentBalance = _getCurrentBalance(); 179 | require(amount < currentBalance, "!balance"); 180 | _transferOut(destination, amount); 181 | } 182 | 183 | // To receive ETH 184 | fallback() external payable {} 185 | receive() external payable {} 186 | 187 | // Utils 188 | 189 | function _transferIn(uint256 amount) internal { 190 | // adjust decimals 191 | uint256 decimals = IRouter(router).getDecimals(currency); 192 | amount = amount * (10**decimals) / UNIT; 193 | IERC20(currency).safeTransferFrom(msg.sender, address(this), amount); 194 | } 195 | 196 | function _transferOut(address to, uint256 amount) internal { 197 | if (amount == 0 || to == address(0)) return; 198 | // adjust decimals 199 | uint256 decimals = IRouter(router).getDecimals(currency); 200 | amount = amount * (10**decimals) / UNIT; 201 | if (currency == address(0)) { 202 | payable(to).sendValue(amount); 203 | } else { 204 | IERC20(currency).safeTransfer(to, amount); 205 | } 206 | } 207 | 208 | function _getCurrentBalance() internal view returns(uint256) { 209 | uint256 currentBalance; 210 | if (currency == address(0)) { 211 | currentBalance = address(this).balance; 212 | } else { 213 | currentBalance = IERC20(currency).balanceOf(address(this)); 214 | } 215 | uint256 decimals = IRouter(router).getDecimals(currency); 216 | return currentBalance * UNIT / (10**decimals); 217 | } 218 | 219 | // Getters 220 | 221 | function getUtilization() public view returns(uint256) { 222 | uint256 currentBalance = _getCurrentBalance(); 223 | if (currentBalance == 0) return 0; 224 | return openInterest * utilizationMultiplier / currentBalance; // in bps 225 | } 226 | 227 | function getCurrencyBalance(address account) external view returns(uint256) { 228 | if (totalSupply == 0) return 0; 229 | uint256 currentBalance = _getCurrentBalance(); 230 | return balances[account] * currentBalance / totalSupply; 231 | } 232 | 233 | // In Clp 234 | function getBalance(address account) external view returns(uint256) { 235 | return balances[account]; 236 | } 237 | 238 | // Modifier 239 | 240 | modifier onlyOwner() { 241 | require(msg.sender == owner, "!owner"); 242 | _; 243 | } 244 | 245 | modifier onlyTrading() { 246 | require(msg.sender == trading, "!trading"); 247 | _; 248 | } 249 | 250 | } -------------------------------------------------------------------------------- /contracts/PoolCAP.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | import "./libraries/SafeERC20.sol"; 5 | 6 | import "./interfaces/IRouter.sol"; 7 | import "./interfaces/IRewards.sol"; 8 | 9 | contract PoolCAP { 10 | 11 | using SafeERC20 for IERC20; 12 | 13 | address public owner; 14 | address public router; 15 | 16 | address public cap; // CAP address 17 | 18 | mapping(address => uint256) private balances; // account => amount staked 19 | uint256 public totalSupply; 20 | 21 | // Events 22 | event DepositCAP( 23 | address indexed user, 24 | uint256 amount 25 | ); 26 | event WithdrawCAP( 27 | address indexed user, 28 | uint256 amount 29 | ); 30 | 31 | constructor(address _cap) { 32 | owner = msg.sender; 33 | cap = _cap; 34 | } 35 | 36 | // Governance methods 37 | 38 | function setOwner(address newOwner) external onlyOwner { 39 | owner = newOwner; 40 | } 41 | 42 | function setRouter(address _router) external onlyOwner { 43 | router = _router; 44 | } 45 | 46 | function deposit(uint256 amount) external { 47 | 48 | require(amount > 0, "!amount"); 49 | 50 | _updateRewards(); 51 | 52 | totalSupply += amount; 53 | balances[msg.sender] += amount; 54 | 55 | IERC20(cap).safeTransferFrom(msg.sender, address(this), amount); 56 | 57 | emit DepositCAP( 58 | msg.sender, 59 | amount 60 | ); 61 | 62 | } 63 | 64 | function withdraw(uint256 amount) external { 65 | 66 | require(amount > 0, "!amount"); 67 | 68 | if (amount >= balances[msg.sender]) { 69 | amount = balances[msg.sender]; 70 | } 71 | 72 | _updateRewards(); 73 | 74 | totalSupply -= amount; 75 | balances[msg.sender] -= amount; 76 | 77 | IERC20(cap).safeTransfer(msg.sender, amount); 78 | 79 | emit WithdrawCAP( 80 | msg.sender, 81 | amount 82 | ); 83 | 84 | } 85 | 86 | function getBalance(address account) external view returns(uint256) { 87 | return balances[account]; 88 | } 89 | 90 | function _updateRewards() internal { 91 | uint256 length = IRouter(router).currenciesLength(); 92 | for (uint256 i = 0; i < length; i++) { 93 | address currency = IRouter(router).currencies(i); 94 | address rewardsContract = IRouter(router).getCapRewards(currency); 95 | IRewards(rewardsContract).updateRewards(msg.sender); 96 | } 97 | } 98 | 99 | modifier onlyOwner() { 100 | require(msg.sender == owner, "!owner"); 101 | _; 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /contracts/Rewards.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | import "./libraries/SafeERC20.sol"; 5 | import "./libraries/Address.sol"; 6 | 7 | import "./interfaces/IRouter.sol"; 8 | import "./interfaces/ITrading.sol"; 9 | import "./interfaces/IPool.sol"; 10 | 11 | contract Rewards { 12 | 13 | using SafeERC20 for IERC20; 14 | using Address for address payable; 15 | 16 | address public owner; 17 | address public router; 18 | address public trading; 19 | address public treasury; 20 | 21 | address public pool; // pool contract associated with these rewards 22 | address public currency; // rewards paid in this 23 | 24 | uint256 public cumulativeRewardPerTokenStored; 25 | uint256 public pendingReward; 26 | 27 | mapping(address => uint256) private claimableReward; 28 | mapping(address => uint256) private previousRewardPerToken; 29 | 30 | uint256 public constant UNIT = 10**18; 31 | 32 | event CollectedReward( 33 | address user, 34 | address poolContract, 35 | address currency, 36 | uint256 amount 37 | ); 38 | 39 | constructor(address _pool, address _currency) { 40 | owner = msg.sender; 41 | pool = _pool; 42 | currency = _currency; 43 | } 44 | 45 | // Governance methods 46 | 47 | function setOwner(address newOwner) external onlyOwner { 48 | owner = newOwner; 49 | } 50 | 51 | function setRouter(address _router) external onlyOwner { 52 | router = _router; 53 | trading = IRouter(router).trading(); 54 | treasury = IRouter(router).treasury(); 55 | } 56 | 57 | // Methods 58 | 59 | function notifyRewardReceived(uint256 amount) external onlyTreasuryOrPool { 60 | pendingReward += amount; // 18 decimals 61 | } 62 | 63 | function updateRewards(address account) public { 64 | 65 | if (account == address(0)) return; 66 | 67 | ITrading(trading).distributeFees(currency); 68 | 69 | uint256 supply = IPool(pool).totalSupply(); 70 | 71 | if (supply > 0) { 72 | cumulativeRewardPerTokenStored += pendingReward * UNIT / supply; 73 | pendingReward = 0; 74 | } 75 | 76 | if (cumulativeRewardPerTokenStored == 0) return; // no rewards yet 77 | 78 | uint256 accountBalance = IPool(pool).getBalance(account); // in CLP 79 | 80 | claimableReward[account] += accountBalance * (cumulativeRewardPerTokenStored - previousRewardPerToken[account]) / UNIT; 81 | previousRewardPerToken[account] = cumulativeRewardPerTokenStored; 82 | 83 | } 84 | 85 | function collectReward() external { 86 | 87 | updateRewards(msg.sender); 88 | 89 | uint256 rewardToSend = claimableReward[msg.sender]; 90 | claimableReward[msg.sender] = 0; 91 | 92 | if (rewardToSend > 0) { 93 | 94 | _transferOut(msg.sender, rewardToSend); 95 | 96 | emit CollectedReward( 97 | msg.sender, 98 | pool, 99 | currency, 100 | rewardToSend 101 | ); 102 | 103 | } 104 | 105 | } 106 | 107 | function getClaimableReward() external view returns(uint256) { 108 | 109 | uint256 currentClaimableReward = claimableReward[msg.sender]; 110 | 111 | uint256 supply = IPool(pool).totalSupply(); 112 | if (supply == 0) return currentClaimableReward; 113 | 114 | uint256 share; 115 | if (pool == IRouter(router).capPool()) { 116 | share = IRouter(router).getCapShare(currency); 117 | } else { 118 | share = IRouter(router).getPoolShare(currency); 119 | } 120 | 121 | uint256 _pendingReward = pendingReward + ITrading(trading).getPendingFee(currency) * share / 10**4; 122 | 123 | uint256 _rewardPerTokenStored = cumulativeRewardPerTokenStored + _pendingReward * UNIT / supply; 124 | if (_rewardPerTokenStored == 0) return currentClaimableReward; // no rewards yet 125 | 126 | uint256 accountStakedBalance = IPool(pool).getBalance(msg.sender); 127 | 128 | return currentClaimableReward + accountStakedBalance * (_rewardPerTokenStored - previousRewardPerToken[msg.sender]) / UNIT; 129 | 130 | } 131 | 132 | // To receive ETH 133 | fallback() external payable {} 134 | receive() external payable {} 135 | 136 | // Utils 137 | 138 | function _transferOut(address to, uint256 amount) internal { 139 | if (amount == 0 || to == address(0)) return; 140 | // adjust decimals 141 | uint256 decimals = IRouter(router).getDecimals(currency); 142 | amount = amount * (10**decimals) / UNIT; 143 | if (currency == address(0)) { 144 | payable(to).sendValue(amount); 145 | } else { 146 | IERC20(currency).safeTransfer(to, amount); 147 | } 148 | } 149 | 150 | modifier onlyOwner() { 151 | require(msg.sender == owner, "!owner"); 152 | _; 153 | } 154 | 155 | modifier onlyTreasuryOrPool() { 156 | require(msg.sender == treasury || msg.sender == pool, "!treasury|pool"); 157 | _; 158 | } 159 | 160 | } -------------------------------------------------------------------------------- /contracts/Router.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | import "./libraries/SafeERC20.sol"; 5 | 6 | import "./interfaces/ITreasury.sol"; 7 | import "./interfaces/ITrading.sol"; 8 | import "./interfaces/IRouter.sol"; 9 | 10 | contract Router { 11 | 12 | using SafeERC20 for IERC20; 13 | 14 | // Contract dependencies 15 | address public owner; 16 | address public trading; 17 | address public oracle; 18 | address public capPool; 19 | address public treasury; 20 | address public darkOracle; 21 | 22 | address[] public currencies; 23 | 24 | mapping(address => uint8) decimals; 25 | 26 | mapping(address => address) pools; // currency => contract 27 | 28 | mapping(address => uint256) private poolShare; // currency (eth, usdc, etc.) => bps 29 | mapping(address => uint256) private capShare; // currency => bps 30 | 31 | mapping(address => address) poolRewards; // currency => contract 32 | mapping(address => address) capRewards; // currency => contract 33 | 34 | constructor() { 35 | owner = msg.sender; 36 | } 37 | 38 | function isSupportedCurrency(address currency) external view returns(bool) { 39 | return currency != address(0) && pools[currency] != address(0); 40 | } 41 | 42 | function currenciesLength() external view returns(uint256) { 43 | return currencies.length; 44 | } 45 | 46 | function getPool(address currency) external view returns(address) { 47 | return pools[currency]; 48 | } 49 | 50 | function getPoolShare(address currency) external view returns(uint256) { 51 | return poolShare[currency]; 52 | } 53 | 54 | function getCapShare(address currency) external view returns(uint256) { 55 | return capShare[currency]; 56 | } 57 | 58 | function getPoolRewards(address currency) external view returns(address) { 59 | return poolRewards[currency]; 60 | } 61 | 62 | function getCapRewards(address currency) external view returns(address) { 63 | return capRewards[currency]; 64 | } 65 | 66 | function getDecimals(address currency) external view returns(uint8) { 67 | if (currency == address(0)) return 18; 68 | if (decimals[currency] > 0) return decimals[currency]; 69 | if (IERC20(currency).decimals() > 0) return IERC20(currency).decimals(); 70 | return 18; 71 | } 72 | 73 | // Setters 74 | 75 | function setCurrencies(address[] calldata _currencies) external onlyOwner { 76 | currencies = _currencies; 77 | } 78 | 79 | function setDecimals(address currency, uint8 _decimals) external onlyOwner { 80 | decimals[currency] = _decimals; 81 | } 82 | 83 | function setContracts( 84 | address _treasury, 85 | address _trading, 86 | address _capPool, 87 | address _oracle, 88 | address _darkOracle 89 | ) external onlyOwner { 90 | treasury = _treasury; 91 | trading = _trading; 92 | capPool = _capPool; 93 | oracle = _oracle; 94 | darkOracle = _darkOracle; 95 | } 96 | 97 | function setPool(address currency, address _contract) external onlyOwner { 98 | pools[currency] = _contract; 99 | } 100 | 101 | function setPoolShare(address currency, uint256 share) external onlyOwner { 102 | poolShare[currency] = share; 103 | } 104 | function setCapShare(address currency, uint256 share) external onlyOwner { 105 | capShare[currency] = share; 106 | } 107 | 108 | function setPoolRewards(address currency, address _contract) external onlyOwner { 109 | poolRewards[currency] = _contract; 110 | } 111 | 112 | function setCapRewards(address currency, address _contract) external onlyOwner { 113 | capRewards[currency] = _contract; 114 | } 115 | 116 | function setOwner(address newOwner) external onlyOwner { 117 | owner = newOwner; 118 | } 119 | 120 | // Modifiers 121 | 122 | modifier onlyOwner() { 123 | require(msg.sender == owner, "!owner"); 124 | _; 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /contracts/Trading.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | import "./libraries/SafeERC20.sol"; 5 | import "./libraries/Address.sol"; 6 | 7 | import "./interfaces/IRouter.sol"; 8 | import "./interfaces/ITreasury.sol"; 9 | import "./interfaces/IPool.sol"; 10 | 11 | contract Trading { 12 | 13 | // All amounts in 8 decimals unless otherwise indicated 14 | 15 | using SafeERC20 for IERC20; 16 | using Address for address payable; 17 | 18 | // Structs 19 | 20 | struct Product { 21 | uint64 maxLeverage; // set to 0 to deactivate product 22 | uint64 liquidationThreshold; // in bps. 8000 = 80% 23 | uint64 fee; // In sbps (10^6). 0.5% = 5000. 0.025% = 250 24 | uint64 interest; // For 360 days, in bps. 5.35% = 535 25 | } 26 | 27 | struct Position { 28 | uint64 size; 29 | uint64 margin; 30 | uint64 timestamp; 31 | uint64 price; 32 | } 33 | 34 | struct Order { 35 | bool isClose; 36 | uint64 size; 37 | uint64 margin; 38 | } 39 | 40 | // Contracts 41 | address public owner; 42 | address public router; 43 | address public treasury; 44 | address public oracle; 45 | 46 | uint256 public nextPositionId; // Incremental 47 | uint256 public nextCloseOrderId; // Incremental 48 | 49 | mapping(bytes32 => Product) private products; 50 | mapping(bytes32 => Position) private positions; // key = currency,user,product,direction 51 | mapping(bytes32 => Order) private orders; // position key => Order 52 | 53 | mapping(address => uint256) minMargin; // currency => amount 54 | 55 | mapping(address => uint256) pendingFees; // currency => amount 56 | 57 | uint256 public constant UNIT_DECIMALS = 8; 58 | uint256 public constant UNIT = 10**UNIT_DECIMALS; 59 | 60 | uint256 public constant PRICE_DECIMALS = 8; 61 | 62 | // Events 63 | event NewOrder( 64 | bytes32 indexed key, 65 | address indexed user, 66 | bytes32 indexed productId, 67 | address currency, 68 | bool isLong, 69 | uint256 margin, 70 | uint256 size, 71 | bool isClose 72 | ); 73 | 74 | event PositionUpdated( 75 | bytes32 indexed key, 76 | address indexed user, 77 | bytes32 indexed productId, 78 | address currency, 79 | bool isLong, 80 | uint256 margin, 81 | uint256 size, 82 | uint256 price, 83 | uint256 fee 84 | ); 85 | 86 | event ClosePosition( 87 | bytes32 indexed key, 88 | address indexed user, 89 | bytes32 indexed productId, 90 | address currency, 91 | bool isLong, 92 | uint256 price, 93 | uint256 margin, 94 | uint256 size, 95 | uint256 fee, 96 | int256 pnl, 97 | bool wasLiquidated 98 | ); 99 | 100 | constructor() { 101 | owner = msg.sender; 102 | } 103 | 104 | // Governance methods 105 | 106 | function setOwner(address newOwner) external onlyOwner { 107 | owner = newOwner; 108 | } 109 | 110 | function setRouter(address _router) external onlyOwner { 111 | router = _router; 112 | treasury = IRouter(router).treasury(); 113 | oracle = IRouter(router).oracle(); 114 | } 115 | 116 | function setMinMargin( 117 | address currency, 118 | uint256 _minMargin 119 | ) external onlyOwner { 120 | minMargin[currency] = _minMargin; 121 | } 122 | 123 | function addProduct(bytes32 productId, Product memory _product) external onlyOwner { 124 | 125 | Product memory product = products[productId]; 126 | 127 | require(product.liquidationThreshold == 0, "!product-exists"); 128 | require(_product.liquidationThreshold > 0, "!liqThreshold"); 129 | 130 | products[productId] = Product({ 131 | maxLeverage: _product.maxLeverage, 132 | fee: _product.fee, 133 | interest: _product.interest, 134 | liquidationThreshold: _product.liquidationThreshold 135 | }); 136 | 137 | } 138 | 139 | function updateProduct(bytes32 productId, Product memory _product) external onlyOwner { 140 | 141 | Product storage product = products[productId]; 142 | 143 | require(product.liquidationThreshold > 0, "!product-does-not-exist"); 144 | 145 | product.maxLeverage = _product.maxLeverage; 146 | product.fee = _product.fee; 147 | product.interest = _product.interest; 148 | product.liquidationThreshold = _product.liquidationThreshold; 149 | 150 | } 151 | 152 | // Methods 153 | 154 | function distributeFees(address currency) external { 155 | uint256 pendingFee = pendingFees[currency]; 156 | if (pendingFee > 0) { 157 | pendingFees[currency] = 0; 158 | _transferOut(currency, treasury, pendingFee); 159 | ITreasury(treasury).notifyFeeReceived(currency, pendingFee * 10**(18-UNIT_DECIMALS)); 160 | } 161 | } 162 | 163 | function submitOrder( 164 | bytes32 productId, 165 | address currency, 166 | bool isLong, 167 | uint256 margin, 168 | uint256 size 169 | ) external payable { 170 | 171 | if (currency == address(0)) { // User is sending ETH 172 | margin = msg.value / 10**(18 - UNIT_DECIMALS); 173 | } else { 174 | require(IRouter(router).isSupportedCurrency(currency), "!currency"); 175 | } 176 | 177 | // Check params 178 | require(margin > 0, "!margin"); 179 | require(size > 0, "!size"); 180 | 181 | bytes32 key = _getPositionKey(msg.sender, productId, currency, isLong); 182 | 183 | Order memory order = orders[key]; 184 | require(order.size == 0, "!order"); // existing order 185 | 186 | Product memory product = products[productId]; 187 | uint256 fee = size * product.fee / 10**6; 188 | 189 | if (currency == address(0)) { 190 | require(margin > fee, "!margin= minMargin[currency], "!min-margin"); 197 | 198 | uint256 leverage = UNIT * size / margin; 199 | require(leverage >= UNIT, "!leverage"); 200 | require(leverage <= product.maxLeverage, "!max-leverage"); 201 | 202 | // Update and check pool utlization 203 | _updateOpenInterest(currency, size, false); 204 | address pool = IRouter(router).getPool(currency); 205 | uint256 utilization = IPool(pool).getUtilization(); 206 | require(utilization < 10**4, "!utilization"); 207 | 208 | orders[key] = Order({ 209 | isClose: false, 210 | size: uint64(size), 211 | margin: uint64(margin) 212 | }); 213 | 214 | emit NewOrder( 215 | key, 216 | msg.sender, 217 | productId, 218 | currency, 219 | isLong, 220 | margin, 221 | size, 222 | false 223 | ); 224 | 225 | } 226 | 227 | function submitCloseOrder( 228 | bytes32 productId, 229 | address currency, 230 | bool isLong, 231 | uint256 size 232 | ) external payable { 233 | 234 | require(size > 0, "!size"); 235 | 236 | bytes32 key = _getPositionKey(msg.sender, productId, currency, isLong); 237 | 238 | Order memory order = orders[key]; 239 | require(order.size == 0, "!order"); // existing order 240 | 241 | // Check position 242 | Position storage position = positions[key]; 243 | require(position.margin > 0, "!position"); 244 | 245 | if (size > position.size) { 246 | size = position.size; 247 | } 248 | 249 | Product memory product = products[productId]; 250 | uint256 fee = size * product.fee / 10**6; 251 | 252 | if (currency == address(0)) { 253 | uint256 fee_units = fee * 10**(18-UNIT_DECIMALS); 254 | require(msg.value >= fee_units && msg.value <= fee_units * (10**6 + 1)/10**6, "!fee"); 255 | } else { 256 | _transferIn(currency, fee); 257 | } 258 | 259 | uint256 margin = size * uint256(position.margin) / uint256(position.size); 260 | 261 | orders[key] = Order({ 262 | isClose: true, 263 | size: uint64(size), 264 | margin: uint64(margin) 265 | }); 266 | 267 | emit NewOrder( 268 | key, 269 | msg.sender, 270 | productId, 271 | currency, 272 | isLong, 273 | margin, 274 | size, 275 | true 276 | ); 277 | 278 | } 279 | 280 | function cancelOrder( 281 | bytes32 productId, 282 | address currency, 283 | bool isLong 284 | ) external { 285 | 286 | bytes32 key = _getPositionKey(msg.sender, productId, currency, isLong); 287 | 288 | Order memory order = orders[key]; 289 | require(order.size > 0, "!exists"); 290 | 291 | Product memory product = products[productId]; 292 | uint256 fee = order.size * product.fee / 10**6; 293 | 294 | _updateOpenInterest(currency, order.size, true); 295 | 296 | delete orders[key]; 297 | 298 | // Refund margin + fee 299 | uint256 marginPlusFee = order.margin + fee; 300 | _transferOut(currency, msg.sender, marginPlusFee); 301 | 302 | } 303 | 304 | // Set price for newly submitted order (oracle) 305 | function settleOrder( 306 | address user, 307 | bytes32 productId, 308 | address currency, 309 | bool isLong, 310 | uint256 price 311 | ) external onlyOracle { 312 | 313 | bytes32 key = _getPositionKey(user, productId, currency, isLong); 314 | 315 | Order storage order = orders[key]; 316 | require(order.size > 0, "!exists"); 317 | 318 | // fee 319 | Product memory product = products[productId]; 320 | uint256 fee = order.size * product.fee / 10**6; 321 | pendingFees[currency] += fee; 322 | 323 | if (order.isClose) { 324 | 325 | { 326 | (uint256 margin, uint256 size, int256 pnl) = _settleCloseOrder(user, productId, currency, isLong, price); 327 | 328 | address pool = IRouter(router).getPool(currency); 329 | 330 | if (pnl < 0) { 331 | { 332 | uint256 positivePnl = uint256(-1 * pnl); 333 | _transferOut(currency, pool, positivePnl); 334 | if (positivePnl < margin) { 335 | _transferOut(currency, user, margin - positivePnl); 336 | } 337 | } 338 | } else { 339 | IPool(pool).creditUserProfit(user, uint256(pnl) * 10**(18-UNIT_DECIMALS)); 340 | _transferOut(currency, user, margin); 341 | } 342 | 343 | _updateOpenInterest(currency, size, true); 344 | 345 | emit ClosePosition( 346 | key, 347 | user, 348 | productId, 349 | currency, 350 | isLong, 351 | price, 352 | margin, 353 | size, 354 | fee, 355 | pnl, 356 | false 357 | ); 358 | 359 | } 360 | 361 | } else { 362 | 363 | // Validate price, returns 8 decimals 364 | price = _validatePrice(price); 365 | 366 | Position storage position = positions[key]; 367 | 368 | uint256 averagePrice = (uint256(position.size) * uint256(position.price) + uint256(order.size) * uint256(price)) / (uint256(position.size) + uint256(order.size)); 369 | 370 | if (position.timestamp == 0) { 371 | position.timestamp = uint64(block.timestamp); 372 | } 373 | 374 | position.size += uint64(order.size); 375 | position.margin += uint64(order.margin); 376 | position.price = uint64(averagePrice); 377 | 378 | delete orders[key]; 379 | 380 | emit PositionUpdated( 381 | key, 382 | user, 383 | productId, 384 | currency, 385 | isLong, 386 | position.margin, 387 | position.size, 388 | position.price, 389 | fee 390 | ); 391 | 392 | } 393 | 394 | } 395 | 396 | function _settleCloseOrder( 397 | address user, 398 | bytes32 productId, 399 | address currency, 400 | bool isLong, 401 | uint256 price 402 | ) internal returns(uint256, uint256, int256) { 403 | 404 | bytes32 key = _getPositionKey(user, productId, currency, isLong); 405 | 406 | // Check order and params 407 | Order memory order = orders[key]; 408 | uint256 size = order.size; 409 | uint256 margin = order.margin; 410 | 411 | Position storage position = positions[key]; 412 | require(position.margin > 0, "!position"); 413 | 414 | Product memory product = products[productId]; 415 | 416 | price = _validatePrice(price); 417 | 418 | int256 pnl = _getPnL(isLong, price, position.price, size, product.interest, position.timestamp); 419 | 420 | // Check if it's a liquidation 421 | if (pnl <= -1 * int256(uint256(position.margin) * uint256(product.liquidationThreshold) / 10**4)) { 422 | pnl = -1 * int256(uint256(position.margin)); 423 | margin = position.margin; 424 | size = position.size; 425 | position.margin = 0; 426 | } else { 427 | position.margin -= uint64(margin); 428 | position.size -= uint64(size); 429 | } 430 | 431 | if (position.margin == 0) { 432 | delete positions[key]; 433 | } 434 | 435 | delete orders[key]; 436 | 437 | return (margin, size, pnl); 438 | 439 | } 440 | 441 | // Liquidate positionIds (oracle) 442 | function liquidatePosition( 443 | address user, 444 | bytes32 productId, 445 | address currency, 446 | bool isLong, 447 | uint256 price 448 | ) external onlyOracle { 449 | 450 | bytes32 key = _getPositionKey(user, productId, currency, isLong); 451 | 452 | Position memory position = positions[key]; 453 | 454 | if (position.margin == 0 || position.size == 0) { 455 | return; 456 | } 457 | 458 | Product storage product = products[productId]; 459 | 460 | price = _validatePrice(price); 461 | 462 | int256 pnl = _getPnL(isLong, price, position.price, position.size, product.interest, position.timestamp); 463 | 464 | uint256 threshold = position.margin * product.liquidationThreshold / 10**4; 465 | 466 | if (pnl <= -1 * int256(threshold)) { 467 | 468 | uint256 fee = position.margin - threshold; 469 | address pool = IRouter(router).getPool(currency); 470 | 471 | _transferOut(currency, pool, threshold); 472 | _updateOpenInterest(currency, position.size, true); 473 | pendingFees[currency] += fee; 474 | 475 | emit ClosePosition( 476 | key, 477 | user, 478 | productId, 479 | currency, 480 | isLong, 481 | price, 482 | position.margin, 483 | position.size, 484 | fee, 485 | -1 * int256(uint256(position.margin)), 486 | true 487 | ); 488 | 489 | delete positions[key]; 490 | 491 | } 492 | 493 | } 494 | 495 | function releaseMargin( 496 | address user, 497 | bytes32 productId, 498 | address currency, 499 | bool isLong, 500 | bool includeFee 501 | ) external onlyOwner { 502 | 503 | bytes32 key = _getPositionKey(user, productId, currency, isLong); 504 | 505 | Position storage position = positions[key]; 506 | require(position.margin > 0, "!position"); 507 | 508 | uint256 margin = position.margin; 509 | 510 | emit ClosePosition( 511 | key, 512 | user, 513 | productId, 514 | currency, 515 | isLong, 516 | position.price, 517 | margin, 518 | position.size, 519 | 0, 520 | 0, 521 | false 522 | ); 523 | 524 | delete orders[key]; 525 | 526 | if (includeFee) { 527 | Product memory product = products[productId]; 528 | uint256 fee = position.size * product.fee / 10**6; 529 | margin += fee; 530 | } 531 | 532 | _updateOpenInterest(currency, position.size, true); 533 | 534 | delete positions[key]; 535 | 536 | _transferOut(currency, user, margin); 537 | 538 | } 539 | 540 | // To receive ETH 541 | fallback() external payable {} 542 | receive() external payable {} 543 | 544 | // Internal methods 545 | 546 | function _getPositionKey(address user, bytes32 productId, address currency, bool isLong) internal pure returns (bytes32) { 547 | return keccak256(abi.encodePacked(user, productId, currency, isLong)); 548 | } 549 | 550 | function _updateOpenInterest(address currency, uint256 amount, bool isDecrease) internal { 551 | address pool = IRouter(router).getPool(currency); 552 | IPool(pool).updateOpenInterest(amount * 10**(18 - UNIT_DECIMALS), isDecrease); 553 | } 554 | 555 | function _transferIn(address currency, uint256 amount) internal { 556 | if (amount == 0 || currency == address(0)) return; 557 | // adjust decimals 558 | uint256 decimals = IRouter(router).getDecimals(currency); 559 | amount = amount * (10**decimals) / (10**UNIT_DECIMALS); 560 | IERC20(currency).safeTransferFrom(msg.sender, address(this), amount); 561 | } 562 | 563 | function _transferOut(address currency, address to, uint256 amount) internal { 564 | if (amount == 0 || to == address(0)) return; 565 | // adjust decimals 566 | uint256 decimals = IRouter(router).getDecimals(currency); 567 | amount = amount * (10**decimals) / (10**UNIT_DECIMALS); 568 | if (currency == address(0)) { 569 | payable(to).sendValue(amount); 570 | } else { 571 | IERC20(currency).safeTransfer(to, amount); 572 | } 573 | } 574 | 575 | function _validatePrice( 576 | uint256 price // 8 decimals 577 | ) internal pure returns(uint256) { 578 | require(price > 0, "!price"); 579 | return price * 10**(UNIT_DECIMALS - PRICE_DECIMALS); 580 | } 581 | 582 | function _getPnL( 583 | bool isLong, 584 | uint256 price, 585 | uint256 positionPrice, 586 | uint256 size, 587 | uint256 interest, 588 | uint256 timestamp 589 | ) internal view returns(int256 _pnl) { 590 | 591 | bool pnlIsNegative; 592 | uint256 pnl; 593 | 594 | if (isLong) { 595 | if (price >= positionPrice) { 596 | pnl = size * (price - positionPrice) / positionPrice; 597 | } else { 598 | pnl = size * (positionPrice - price) / positionPrice; 599 | pnlIsNegative = true; 600 | } 601 | } else { 602 | if (price > positionPrice) { 603 | pnl = size * (price - positionPrice) / positionPrice; 604 | pnlIsNegative = true; 605 | } else { 606 | pnl = size * (positionPrice - price) / positionPrice; 607 | } 608 | } 609 | 610 | // Subtract interest from P/L 611 | if (block.timestamp >= timestamp + 15 minutes) { 612 | 613 | uint256 _interest = size * interest * (block.timestamp - timestamp) / (UNIT * 10**4 * 360 days); 614 | 615 | if (pnlIsNegative) { 616 | pnl += _interest; 617 | } else if (pnl < _interest) { 618 | pnl = _interest - pnl; 619 | pnlIsNegative = true; 620 | } else { 621 | pnl -= _interest; 622 | } 623 | 624 | } 625 | 626 | if (pnlIsNegative) { 627 | _pnl = -1 * int256(pnl); 628 | } else { 629 | _pnl = int256(pnl); 630 | } 631 | 632 | return _pnl; 633 | 634 | } 635 | 636 | // Getters 637 | 638 | function getProduct(bytes32 productId) external view returns(Product memory) { 639 | return products[productId]; 640 | } 641 | 642 | function getPosition( 643 | address user, 644 | address currency, 645 | bytes32 productId, 646 | bool isLong 647 | ) external view returns(Position memory position) { 648 | bytes32 key = _getPositionKey(user, productId, currency, isLong); 649 | return positions[key]; 650 | } 651 | 652 | function getOrder( 653 | address user, 654 | address currency, 655 | bytes32 productId, 656 | bool isLong 657 | ) external view returns(Order memory order) { 658 | bytes32 key = _getPositionKey(user, productId, currency, isLong); 659 | return orders[key]; 660 | } 661 | 662 | function getOrders(bytes32[] calldata keys) external view returns(Order[] memory _orders) { 663 | uint256 length = keys.length; 664 | _orders = new Order[](length); 665 | for (uint256 i = 0; i < length; i++) { 666 | _orders[i] = orders[keys[i]]; 667 | } 668 | return _orders; 669 | } 670 | 671 | function getPositions(bytes32[] calldata keys) external view returns(Position[] memory _positions) { 672 | uint256 length = keys.length; 673 | _positions = new Position[](length); 674 | for (uint256 i = 0; i < length; i++) { 675 | _positions[i] = positions[keys[i]]; 676 | } 677 | return _positions; 678 | } 679 | 680 | function getPendingFee(address currency) external view returns(uint256) { 681 | return pendingFees[currency] * 10**(18-UNIT_DECIMALS); 682 | } 683 | 684 | // Modifiers 685 | 686 | modifier onlyOracle() { 687 | require(msg.sender == oracle, "!oracle"); 688 | _; 689 | } 690 | 691 | modifier onlyOwner() { 692 | require(msg.sender == owner, "!owner"); 693 | _; 694 | } 695 | 696 | } -------------------------------------------------------------------------------- /contracts/Treasury.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | import "./libraries/SafeERC20.sol"; 5 | import "./libraries/Address.sol"; 6 | 7 | import "./interfaces/IRouter.sol"; 8 | import "./interfaces/IRewards.sol"; 9 | 10 | // This contract should be relatively upgradeable = no important state 11 | 12 | contract Treasury { 13 | 14 | using SafeERC20 for IERC20; 15 | using Address for address payable; 16 | 17 | // Contract dependencies 18 | address public owner; 19 | address public router; 20 | address public trading; 21 | address public oracle; 22 | 23 | uint256 public constant UNIT = 10**18; 24 | 25 | constructor() { 26 | owner = msg.sender; 27 | } 28 | 29 | // Governance methods 30 | 31 | function setOwner(address newOwner) external onlyOwner { 32 | owner = newOwner; 33 | } 34 | 35 | function setRouter(address _router) external onlyOwner { 36 | router = _router; 37 | oracle = IRouter(router).oracle(); 38 | trading = IRouter(router).trading(); 39 | } 40 | 41 | // Methods 42 | 43 | function notifyFeeReceived( 44 | address currency, 45 | uint256 amount 46 | ) external onlyTrading { 47 | 48 | // Contracts from Router 49 | address poolRewards = IRouter(router).getPoolRewards(currency); 50 | address capRewards = IRouter(router).getCapRewards(currency); 51 | 52 | // Send poolShare to pool-currency rewards contract 53 | uint256 poolReward = IRouter(router).getPoolShare(currency) * amount / 10**4; 54 | _transferOut(currency, poolRewards, poolReward); 55 | IRewards(poolRewards).notifyRewardReceived(poolReward); 56 | 57 | // Send capPoolShare to cap-currency rewards contract 58 | uint256 capReward = IRouter(router).getCapShare(currency) * amount / 10**4; 59 | _transferOut(currency, capRewards, capReward); 60 | IRewards(capRewards).notifyRewardReceived(capReward); 61 | 62 | } 63 | 64 | function fundOracle( 65 | address destination, 66 | uint256 amount 67 | ) external onlyOracle { 68 | uint256 ethBalance = address(this).balance; 69 | if (amount > ethBalance) return; 70 | payable(destination).sendValue(amount); 71 | } 72 | 73 | function sendFunds( 74 | address token, 75 | address destination, 76 | uint256 amount 77 | ) external onlyOwner { 78 | _transferOut(token, destination, amount); 79 | } 80 | 81 | // To receive ETH 82 | fallback() external payable {} 83 | receive() external payable {} 84 | 85 | // Utils 86 | 87 | function _transferOut(address currency, address to, uint256 amount) internal { 88 | if (amount == 0 || to == address(0)) return; 89 | // adjust decimals 90 | uint256 decimals = IRouter(router).getDecimals(currency); 91 | amount = amount * (10**decimals) / UNIT; 92 | if (currency == address(0)) { 93 | payable(to).sendValue(amount); 94 | } else { 95 | IERC20(currency).safeTransfer(to, amount); 96 | } 97 | } 98 | 99 | // Modifiers 100 | 101 | modifier onlyOwner() { 102 | require(msg.sender == owner, "!owner"); 103 | _; 104 | } 105 | 106 | modifier onlyTrading() { 107 | require(msg.sender == trading, "!trading"); 108 | _; 109 | } 110 | 111 | modifier onlyOracle() { 112 | require(msg.sender == oracle, "!oracle"); 113 | _; 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /contracts/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @dev Interface of the ERC20 standard as defined in the EIP. 7 | */ 8 | interface IERC20 { 9 | /** 10 | * @dev Returns token decimals. 11 | */ 12 | function decimals() external view returns (uint8); 13 | 14 | /** 15 | * @dev Returns the amount of tokens in existence. 16 | */ 17 | function totalSupply() external view returns (uint256); 18 | 19 | /** 20 | * @dev Returns the amount of tokens owned by `account`. 21 | */ 22 | function balanceOf(address account) external view returns (uint256); 23 | 24 | /** 25 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 26 | * 27 | * Returns a boolean value indicating whether the operation succeeded. 28 | * 29 | * Emits a {Transfer} event. 30 | */ 31 | function transfer(address recipient, uint256 amount) external returns (bool); 32 | 33 | /** 34 | * @dev Returns the remaining number of tokens that `spender` will be 35 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 36 | * zero by default. 37 | * 38 | * This value changes when {approve} or {transferFrom} are called. 39 | */ 40 | function allowance(address owner, address spender) external view returns (uint256); 41 | 42 | /** 43 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 44 | * 45 | * Returns a boolean value indicating whether the operation succeeded. 46 | * 47 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 48 | * that someone may use both the old and the new allowance by unfortunate 49 | * transaction ordering. One possible solution to mitigate this race 50 | * condition is to first reduce the spender's allowance to 0 and set the 51 | * desired value afterwards: 52 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 53 | * 54 | * Emits an {Approval} event. 55 | */ 56 | function approve(address spender, uint256 amount) external returns (bool); 57 | 58 | /** 59 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 60 | * allowance mechanism. `amount` is then deducted from the caller's 61 | * allowance. 62 | * 63 | * Returns a boolean value indicating whether the operation succeeded. 64 | * 65 | * Emits a {Transfer} event. 66 | */ 67 | function transferFrom( 68 | address sender, 69 | address recipient, 70 | uint256 amount 71 | ) external returns (bool); 72 | 73 | /** 74 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 75 | * another (`to`). 76 | * 77 | * Note that `value` may be zero. 78 | */ 79 | event Transfer(address indexed from, address indexed to, uint256 value); 80 | 81 | /** 82 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 83 | * a call to {approve}. `value` is the new allowance. 84 | */ 85 | event Approval(address indexed owner, address indexed spender, uint256 value); 86 | } 87 | -------------------------------------------------------------------------------- /contracts/interfaces/IOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | interface IOracle {} 5 | -------------------------------------------------------------------------------- /contracts/interfaces/IPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | interface IPool { 5 | function totalSupply() external view returns (uint256); 6 | 7 | function creditUserProfit(address destination, uint256 amount) external; 8 | 9 | function updateOpenInterest(uint256 amount, bool isDecrease) external; 10 | 11 | function getUtilization() external view returns (uint256); 12 | 13 | function getBalance(address account) external view returns (uint256); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /contracts/interfaces/IRewards.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | interface IRewards { 5 | function updateRewards(address account) external; 6 | 7 | function notifyRewardReceived(uint256 amount) external; 8 | } 9 | -------------------------------------------------------------------------------- /contracts/interfaces/IRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | interface IRouter { 5 | function trading() external view returns (address); 6 | 7 | function capPool() external view returns (address); 8 | 9 | function oracle() external view returns (address); 10 | 11 | function treasury() external view returns (address); 12 | 13 | function darkOracle() external view returns (address); 14 | 15 | function isSupportedCurrency(address currency) external view returns (bool); 16 | 17 | function currencies(uint256 index) external view returns (address); 18 | 19 | function currenciesLength() external view returns (uint256); 20 | 21 | function getDecimals(address currency) external view returns(uint8); 22 | 23 | function getPool(address currency) external view returns (address); 24 | 25 | function getPoolShare(address currency) external view returns(uint256); 26 | 27 | function getCapShare(address currency) external view returns(uint256); 28 | 29 | function getPoolRewards(address currency) external view returns (address); 30 | 31 | function getCapRewards(address currency) external view returns (address); 32 | } 33 | -------------------------------------------------------------------------------- /contracts/interfaces/ITrading.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | interface ITrading { 5 | 6 | function distributeFees(address currency) external; 7 | 8 | function settleOrder(address user, bytes32 productId, address currency, bool isLong, uint256 price) external; 9 | 10 | function liquidatePosition(address user, bytes32 productId, address currency, bool isLong, uint256 price) external; 11 | 12 | function getPendingFee(address currency) external view returns(uint256); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/ITreasury.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BUSL-1.1 2 | pragma solidity ^0.8.0; 3 | 4 | interface ITreasury { 5 | function fundOracle(address destination, uint256 amount) external; 6 | 7 | function notifyFeeReceived(address currency, uint256 amount) external; 8 | } 9 | -------------------------------------------------------------------------------- /contracts/libraries/Address.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | /** 6 | * @dev Collection of functions related to the address type 7 | */ 8 | library Address { 9 | /** 10 | * @dev Returns true if `account` is a contract. 11 | * 12 | * [IMPORTANT] 13 | * ==== 14 | * It is unsafe to assume that an address for which this function returns 15 | * false is an externally-owned account (EOA) and not a contract. 16 | * 17 | * Among others, `isContract` will return false for the following 18 | * types of addresses: 19 | * 20 | * - an externally-owned account 21 | * - a contract in construction 22 | * - an address where a contract will be created 23 | * - an address where a contract lived, but was destroyed 24 | * ==== 25 | */ 26 | function isContract(address account) internal view returns (bool) { 27 | // This method relies on extcodesize, which returns 0 for contracts in 28 | // construction, since the code is only stored at the end of the 29 | // constructor execution. 30 | 31 | uint256 size; 32 | assembly { 33 | size := extcodesize(account) 34 | } 35 | return size > 0; 36 | } 37 | 38 | /** 39 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 40 | * `recipient`, forwarding all available gas and reverting on errors. 41 | * 42 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 43 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 44 | * imposed by `transfer`, making them unable to receive funds via 45 | * `transfer`. {sendValue} removes this limitation. 46 | * 47 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 48 | * 49 | * IMPORTANT: because control is transferred to `recipient`, care must be 50 | * taken to not create reentrancy vulnerabilities. Consider using 51 | * {ReentrancyGuard} or the 52 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 53 | */ 54 | function sendValue(address payable recipient, uint256 amount) internal { 55 | require(address(this).balance >= amount, "Address: insufficient balance"); 56 | 57 | (bool success, ) = recipient.call{value: amount}(""); 58 | require(success, "Address: unable to send value, recipient may have reverted"); 59 | } 60 | 61 | /** 62 | * @dev Performs a Solidity function call using a low level `call`. A 63 | * plain `call` is an unsafe replacement for a function call: use this 64 | * function instead. 65 | * 66 | * If `target` reverts with a revert reason, it is bubbled up by this 67 | * function (like regular Solidity function calls). 68 | * 69 | * Returns the raw returned data. To convert to the expected return value, 70 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 71 | * 72 | * Requirements: 73 | * 74 | * - `target` must be a contract. 75 | * - calling `target` with `data` must not revert. 76 | * 77 | * _Available since v3.1._ 78 | */ 79 | function functionCall(address target, bytes memory data) internal returns (bytes memory) { 80 | return functionCall(target, data, "Address: low-level call failed"); 81 | } 82 | 83 | /** 84 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with 85 | * `errorMessage` as a fallback revert reason when `target` reverts. 86 | * 87 | * _Available since v3.1._ 88 | */ 89 | function functionCall( 90 | address target, 91 | bytes memory data, 92 | string memory errorMessage 93 | ) internal returns (bytes memory) { 94 | return functionCallWithValue(target, data, 0, errorMessage); 95 | } 96 | 97 | /** 98 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 99 | * but also transferring `value` wei to `target`. 100 | * 101 | * Requirements: 102 | * 103 | * - the calling contract must have an ETH balance of at least `value`. 104 | * - the called Solidity function must be `payable`. 105 | * 106 | * _Available since v3.1._ 107 | */ 108 | function functionCallWithValue( 109 | address target, 110 | bytes memory data, 111 | uint256 value 112 | ) internal returns (bytes memory) { 113 | return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); 114 | } 115 | 116 | /** 117 | * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but 118 | * with `errorMessage` as a fallback revert reason when `target` reverts. 119 | * 120 | * _Available since v3.1._ 121 | */ 122 | function functionCallWithValue( 123 | address target, 124 | bytes memory data, 125 | uint256 value, 126 | string memory errorMessage 127 | ) internal returns (bytes memory) { 128 | require(address(this).balance >= value, "Address: insufficient balance for call"); 129 | require(isContract(target), "Address: call to non-contract"); 130 | 131 | (bool success, bytes memory returndata) = target.call{value: value}(data); 132 | return verifyCallResult(success, returndata, errorMessage); 133 | } 134 | 135 | /** 136 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 137 | * but performing a static call. 138 | * 139 | * _Available since v3.3._ 140 | */ 141 | function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 142 | return functionStaticCall(target, data, "Address: low-level static call failed"); 143 | } 144 | 145 | /** 146 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 147 | * but performing a static call. 148 | * 149 | * _Available since v3.3._ 150 | */ 151 | function functionStaticCall( 152 | address target, 153 | bytes memory data, 154 | string memory errorMessage 155 | ) internal view returns (bytes memory) { 156 | require(isContract(target), "Address: static call to non-contract"); 157 | 158 | (bool success, bytes memory returndata) = target.staticcall(data); 159 | return verifyCallResult(success, returndata, errorMessage); 160 | } 161 | 162 | /** 163 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 164 | * but performing a delegate call. 165 | * 166 | * _Available since v3.4._ 167 | */ 168 | function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { 169 | return functionDelegateCall(target, data, "Address: low-level delegate call failed"); 170 | } 171 | 172 | /** 173 | * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], 174 | * but performing a delegate call. 175 | * 176 | * _Available since v3.4._ 177 | */ 178 | function functionDelegateCall( 179 | address target, 180 | bytes memory data, 181 | string memory errorMessage 182 | ) internal returns (bytes memory) { 183 | require(isContract(target), "Address: delegate call to non-contract"); 184 | 185 | (bool success, bytes memory returndata) = target.delegatecall(data); 186 | return verifyCallResult(success, returndata, errorMessage); 187 | } 188 | 189 | /** 190 | * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the 191 | * revert reason using the provided one. 192 | * 193 | * _Available since v4.3._ 194 | */ 195 | function verifyCallResult( 196 | bool success, 197 | bytes memory returndata, 198 | string memory errorMessage 199 | ) internal pure returns (bytes memory) { 200 | if (success) { 201 | return returndata; 202 | } else { 203 | // Look for revert reason and bubble it up if present 204 | if (returndata.length > 0) { 205 | // The easiest way to bubble the revert reason is using memory via assembly 206 | 207 | assembly { 208 | let returndata_size := mload(returndata) 209 | revert(add(32, returndata), returndata_size) 210 | } 211 | } else { 212 | revert(errorMessage); 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /contracts/libraries/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "../interfaces/IERC20.sol"; 6 | import "./Address.sol"; 7 | 8 | /** 9 | * @title SafeERC20 10 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 11 | * contract returns false). Tokens that return no value (and instead revert or 12 | * throw on failure) are also supported, non-reverting calls are assumed to be 13 | * successful. 14 | * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, 15 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 16 | */ 17 | library SafeERC20 { 18 | using Address for address; 19 | 20 | function safeTransfer( 21 | IERC20 token, 22 | address to, 23 | uint256 value 24 | ) internal { 25 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 26 | } 27 | 28 | function safeTransferFrom( 29 | IERC20 token, 30 | address from, 31 | address to, 32 | uint256 value 33 | ) internal { 34 | _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 35 | } 36 | 37 | /** 38 | * @dev Deprecated. This function has issues similar to the ones found in 39 | * {IERC20-approve}, and its usage is discouraged. 40 | * 41 | * Whenever possible, use {safeIncreaseAllowance} and 42 | * {safeDecreaseAllowance} instead. 43 | */ 44 | function safeApprove( 45 | IERC20 token, 46 | address spender, 47 | uint256 value 48 | ) internal { 49 | // safeApprove should only be called when setting an initial allowance, 50 | // or when resetting it to zero. To increase and decrease it, use 51 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 52 | require( 53 | (value == 0) || (token.allowance(address(this), spender) == 0), 54 | "SafeERC20: approve from non-zero to non-zero allowance" 55 | ); 56 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 57 | } 58 | 59 | function safeIncreaseAllowance( 60 | IERC20 token, 61 | address spender, 62 | uint256 value 63 | ) internal { 64 | uint256 newAllowance = token.allowance(address(this), spender) + value; 65 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 66 | } 67 | 68 | function safeDecreaseAllowance( 69 | IERC20 token, 70 | address spender, 71 | uint256 value 72 | ) internal { 73 | unchecked { 74 | uint256 oldAllowance = token.allowance(address(this), spender); 75 | require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); 76 | uint256 newAllowance = oldAllowance - value; 77 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 78 | } 79 | } 80 | 81 | /** 82 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 83 | * on the return value: the return value is optional (but if data is returned, it must not be false). 84 | * @param token The token targeted by the call. 85 | * @param data The call data (encoded using abi.encode or one of its variants). 86 | */ 87 | function _callOptionalReturn(IERC20 token, bytes memory data) private { 88 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 89 | // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that 90 | // the target address contains contract code and also asserts for success in the low-level call. 91 | 92 | bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); 93 | if (returndata.length > 0) { 94 | // Return data is optional 95 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /contracts/mocks/MockToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.2; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockToken is ERC20 { 7 | 8 | uint8 _decimals; 9 | 10 | constructor(string memory name, string memory symbol, uint8 __decimals) ERC20(name, symbol) { 11 | _decimals = __decimals; 12 | } 13 | 14 | function decimals() public view virtual override returns (uint8) { 15 | if (_decimals > 0) return _decimals; 16 | return 18; 17 | } 18 | 19 | function mint(uint256 amount) public { 20 | _mint(msg.sender, amount); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-waffle"); 2 | require('dotenv').config() 3 | 4 | /** 5 | * @type import('hardhat/config').HardhatUserConfig 6 | */ 7 | module.exports = { 8 | defaultNetwork: "hardhat", 9 | networks: { 10 | hardhat: { 11 | // forking: { 12 | // url: process.env.FORKING_URL_ARBITRUM 13 | // }, 14 | // mining: { 15 | // auto: true, 16 | // interval: [10000, 20000] 17 | // } 18 | }, 19 | rinkeby: { 20 | url: process.env.RINKEBY_URL, 21 | accounts: [process.env.RINKEBY_PKEY] 22 | }, 23 | mainnet: { 24 | url: process.env.MAINNET_URL 25 | }, 26 | arbitrum_rinkeby: { 27 | url: 'https://rinkeby.arbitrum.io/rpc', 28 | accounts: [process.env.RINKEBY_PKEY] 29 | }, 30 | arbitrum: { 31 | url: 'https://arb1.arbitrum.io/rpc', 32 | accounts: [process.env.ARBITRUM_PKEY] 33 | }, 34 | avalanche: { 35 | url: 'https://api.avax.network/ext/bc/C/rpc', 36 | accounts: [process.env.AVALANCHE_PKEY] 37 | } 38 | }, 39 | solidity: { 40 | compilers: [{ 41 | version: "0.8.7", 42 | settings: { 43 | optimizer: { 44 | enabled: true, 45 | runs: 200 46 | } 47 | } 48 | }] 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protocol", 3 | "version": "1.0.0", 4 | "description": "Readme startes here.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/0xcap/protocol.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/0xcap/protocol/issues" 17 | }, 18 | "homepage": "https://github.com/0xcap/protocol#readme", 19 | "devDependencies": { 20 | "@nomiclabs/hardhat-ethers": "^2.0.2", 21 | "@nomiclabs/hardhat-waffle": "^2.0.1", 22 | "chai": "^4.3.4", 23 | "ethereum-waffle": "^3.4.0", 24 | "ethers": "^5.4.4", 25 | "hardhat": "^2.6.0" 26 | }, 27 | "dependencies": { 28 | "@chainlink/contracts": "^0.2.1", 29 | "@openzeppelin/contracts": "^4.3.2", 30 | "@uniswap/v3-periphery": "^1.2.0", 31 | "dotenv": "^10.0.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /scripts/deploy-avax.js: -------------------------------------------------------------------------------- 1 | // We require the Hardhat Runtime Environment explicitly here. This is optional 2 | // but useful for running the script in a standalone fashion through `node