├── .github └── workflows │ └── main.yml ├── README.md ├── core ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .solhint.json ├── README.md ├── contracts │ ├── ArbAirdrop.sol │ ├── BaseEngine.sol │ ├── BaseWithdrawPool.sol │ ├── Clearinghouse.sol │ ├── ClearinghouseLiq.sol │ ├── ClearinghouseStorage.sol │ ├── Endpoint.sol │ ├── EndpointGated.sol │ ├── MockSanctionsList.sol │ ├── OffchainExchange.sol │ ├── PerpEngine.sol │ ├── PerpEngineLp.sol │ ├── PerpEngineState.sol │ ├── SpotEngine.sol │ ├── SpotEngineLP.sol │ ├── SpotEngineState.sol │ ├── Verifier.sol │ ├── VertexGateway.sol │ ├── WithdrawPool.sol │ ├── WithdrawPoolRipple.sol │ ├── common │ │ ├── Constants.sol │ │ └── Errors.sol │ ├── interfaces │ │ ├── IArbAirdrop.sol │ │ ├── IERC20Base.sol │ │ ├── IEndpoint.sol │ │ ├── IEndpointGated.sol │ │ ├── IFEndpoint.sol │ │ ├── IOffchainExchange.sol │ │ ├── IVerifier.sol │ │ ├── clearinghouse │ │ │ ├── IClearinghouse.sol │ │ │ ├── IClearinghouseEventEmitter.sol │ │ │ └── IClearinghouseLiq.sol │ │ └── engine │ │ │ ├── IPerpEngine.sol │ │ │ ├── IProductEngine.sol │ │ │ └── ISpotEngine.sol │ ├── libraries │ │ ├── ERC20Helper.sol │ │ ├── Logger.sol │ │ ├── MathHelper.sol │ │ ├── MathSD21x18.sol │ │ ├── RippleBase58.sol │ │ └── RiskHelper.sol │ └── util │ │ ├── GasInfo.sol │ │ └── MockERC20.sol ├── hardhat.config.ts ├── package.json └── yarn.lock └── lba ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .solhint.json ├── README.md ├── contracts ├── Airdrop.sol ├── LBA.sol ├── StakingV2.sol ├── VertexToken.sol ├── Vesting.sol └── interfaces │ ├── IAirdrop.sol │ ├── IEndpoint.sol │ ├── ILBA.sol │ ├── IStaking.sol │ └── IVesting.sol ├── env.ts ├── hardhat.config.ts ├── package.json ├── tsconfig.json └── yarn.lock /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ['main'] 6 | pull_request: 7 | branches: ['main'] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v3 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 16 22 | 23 | - name: Install global dependencies 24 | run: | 25 | npm install --global yarn 26 | git config --global user.email "ci@example.com" 27 | git config --global user.name "CI" 28 | 29 | # Core project 30 | - name: Get core yarn cache directory path 31 | id: core-yarn-cache-dir-path 32 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 33 | 34 | - name: Cache core yarn cache 35 | uses: actions/cache@v3 36 | id: core-yarn-cache 37 | with: 38 | path: ${{ steps.core-yarn-cache-dir-path.outputs.dir }} 39 | key: ${{ runner.os }}-yarn-core-${{ hashFiles('core/yarn.lock') }} 40 | restore-keys: | 41 | ${{ runner.os }}-yarn-core- 42 | 43 | - name: Install dependencies in core 44 | run: | 45 | cd core 46 | HUSKY=0 yarn install --frozen-lockfile 47 | 48 | # LBA project 49 | - name: Get lba yarn cache directory path 50 | id: lba-yarn-cache-dir-path 51 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 52 | 53 | - name: Cache lba yarn cache 54 | uses: actions/cache@v3 55 | id: lba-yarn-cache 56 | with: 57 | path: ${{ steps.lba-yarn-cache-dir-path.outputs.dir }} 58 | key: ${{ runner.os }}-yarn-lba-${{ hashFiles('lba/yarn.lock') }} 59 | restore-keys: | 60 | ${{ runner.os }}-yarn-lba- 61 | 62 | - name: Install dependencies in lba 63 | run: | 64 | cd lba 65 | HUSKY=0 yarn install --frozen-lockfile 66 | 67 | - name: Run lint in core 68 | run: | 69 | cd core 70 | yarn run lint 71 | 72 | - name: Run lint in lba 73 | run: | 74 | cd lba 75 | yarn run lint -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vertex Protocol 2 | 3 | This repository contains the smart contract implementations for the Vertex Protocol ecosystem. 4 | 5 | ## Project Structure 6 | 7 | The repository is organized into two main projects: 8 | 9 | - **[vertex-contracts/core](./core)**: EVM implementation of Vertex core functionality 10 | - **[vertex-contracts/lba](./lba)**: Vertex LBA (Liquidity Bootstrap Auction) contracts 11 | 12 | ## Requirements 13 | 14 | - Node.js >=16 15 | - [Yarn](https://yarnpkg.com/) 16 | 17 | ## Getting Started 18 | 19 | Each project has its own setup and development commands. Navigate to the respective directories for project-specific instructions: 20 | 21 | ``` 22 | # For Vertex EVM Core Contracts 23 | cd vertex-contracts/core 24 | yarn install 25 | yarn compile 26 | 27 | # For Vertex LBA Contracts 28 | cd vertex-contracts/lba 29 | yarn install 30 | # Follow the .env setup instructions 31 | ``` 32 | 33 | ## Available Commands 34 | 35 | ### Core Contracts 36 | 37 | - `yarn compile`: Compile Vertex EVM contracts 38 | - See project-specific README for more details 39 | 40 | ### LBA Contracts 41 | 42 | - `yarn lint`: Run prettier & SolHint 43 | - `yarn contracts:force-compile`: Compile contracts and generate TS bindings + ABIs 44 | - `yarn run-local-node`: Run a persistent local Hardhat node for testing 45 | - See project-specific README for more details 46 | 47 | ## Further Documentation 48 | 49 | For more detailed information about each project, please refer to their respective README files. -------------------------------------------------------------------------------- /core/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | 4 | # Typechain 5 | typechain-types/ 6 | artifacts/ 7 | 8 | # solidity-coverage 9 | coverage/ 10 | coverage.json -------------------------------------------------------------------------------- /core/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['@typescript-eslint/eslint-plugin'], 4 | extends: ['plugin:@typescript-eslint/recommended', 'prettier'], 5 | rules: { 6 | '@typescript-eslint/explicit-module-boundary-types': 'off', 7 | }, 8 | env: { 9 | node: true, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | secrets.json 2 | .secrets 3 | .env 4 | 5 | node_modules/ 6 | .idea/ 7 | 8 | # OpenZeppelin 9 | .openzeppelin/dev-*.json 10 | .openzeppelin/unknown-*.json 11 | .openzeppelin/.session 12 | 13 | # Hardhat 14 | cache/ 15 | 16 | # Typechain 17 | typechain-types/ 18 | artifacts/ 19 | 20 | # ABIs 21 | abis/ 22 | 23 | # solidity-coverage 24 | coverage/ 25 | coverage.json 26 | 27 | # Husky 28 | .husky -------------------------------------------------------------------------------- /core/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "printWidth": 120, 5 | "overrides": [ 6 | { 7 | "files": "*.sol", 8 | "options": { 9 | "printWidth": 80, 10 | "tabWidth": 4, 11 | "useTabs": false, 12 | "singleQuote": false, 13 | "bracketSpacing": false, 14 | "explicitTypes": "always" 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /core/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": [ 5 | "error", 6 | "^0.8.0" 7 | ], 8 | "not-rely-on-time": "off", 9 | "reason-string": "off", 10 | "avoid-low-level-calls": "off", 11 | "func-visibility": [ 12 | "warn", 13 | { 14 | "ignoreConstructors": true 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | # Vertex EVM Contracts 2 | 3 | EVM implementation of Vertex. A [Hardhat](https://hardhat.org/) project. 4 | 5 | # Local Development 6 | 7 | The following assumes the use of `node@>=16`. 8 | 9 | ## Install Dependencies 10 | 11 | `yarn install` 12 | 13 | ## Compile Contracts 14 | 15 | `yarn compile` 16 | -------------------------------------------------------------------------------- /core/contracts/ArbAirdrop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./interfaces/IArbAirdrop.sol"; 5 | import "./Endpoint.sol"; 6 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 7 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 8 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 9 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 10 | 11 | contract ArbAirdrop is OwnableUpgradeable, IArbAirdrop { 12 | address token; 13 | address sanctions; 14 | uint32 pastWeeks; 15 | 16 | mapping(uint32 => bytes32) merkleRoots; 17 | mapping(uint32 => mapping(address => uint256)) claimed; 18 | 19 | /// @custom:oz-upgrades-unsafe-allow constructor 20 | constructor() { 21 | _disableInitializers(); 22 | } 23 | 24 | function initialize(address _token, address _sanctions) 25 | external 26 | initializer 27 | { 28 | __Ownable_init(); 29 | token = _token; 30 | sanctions = _sanctions; 31 | } 32 | 33 | function registerMerkleRoot(uint32 week, bytes32 merkleRoot) 34 | external 35 | onlyOwner 36 | { 37 | pastWeeks += 1; 38 | require(week == pastWeeks, "Invalid week provided."); 39 | merkleRoots[week] = merkleRoot; 40 | } 41 | 42 | function _verifyProof( 43 | uint32 week, 44 | address sender, 45 | uint256 totalAmount, 46 | bytes32[] calldata proof 47 | ) internal { 48 | require(claimed[week][sender] == 0, "Already claimed."); 49 | require( 50 | merkleRoots[week] != bytes32(0), 51 | "Week hasn't been registered." 52 | ); 53 | require( 54 | !ISanctionsList(sanctions).isSanctioned(sender), 55 | "address is sanctioned." 56 | ); 57 | bytes32 leaf = keccak256( 58 | bytes.concat(keccak256(abi.encode(sender, totalAmount))) 59 | ); 60 | bool isValidLeaf = MerkleProof.verify(proof, merkleRoots[week], leaf); 61 | require(isValidLeaf, "Invalid proof."); 62 | claimed[week][sender] = totalAmount; 63 | } 64 | 65 | function _claim( 66 | uint32 week, 67 | uint256 totalAmount, 68 | bytes32[] calldata proof 69 | ) internal { 70 | _verifyProof(week, msg.sender, totalAmount, proof); 71 | SafeERC20.safeTransfer(IERC20(token), msg.sender, totalAmount); 72 | emit ClaimArb(msg.sender, week, totalAmount); 73 | } 74 | 75 | function claim(ClaimProof[] calldata claimProofs) external { 76 | for (uint32 i = 0; i < claimProofs.length; i++) { 77 | _claim( 78 | claimProofs[i].week, 79 | claimProofs[i].totalAmount, 80 | claimProofs[i].proof 81 | ); 82 | } 83 | } 84 | 85 | function getClaimed(address account) 86 | external 87 | view 88 | returns (uint256[] memory) 89 | { 90 | uint256[] memory result = new uint256[](pastWeeks + 1); 91 | for (uint32 week = 1; week <= pastWeeks; week++) { 92 | result[week] = claimed[week][account]; 93 | } 94 | return result; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /core/contracts/BaseEngine.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "hardhat/console.sol"; 6 | 7 | import "./common/Constants.sol"; 8 | import "./common/Errors.sol"; 9 | import "./libraries/MathHelper.sol"; 10 | import "./libraries/MathSD21x18.sol"; 11 | import "./interfaces/clearinghouse/IClearinghouse.sol"; 12 | import "./interfaces/engine/IProductEngine.sol"; 13 | import "./interfaces/IOffchainExchange.sol"; 14 | import "./interfaces/IEndpoint.sol"; 15 | import "./EndpointGated.sol"; 16 | import "./libraries/Logger.sol"; 17 | 18 | abstract contract BaseEngine is IProductEngine, EndpointGated { 19 | using MathSD21x18 for int128; 20 | 21 | IClearinghouse internal _clearinghouse; 22 | address internal _fees; // deprecated 23 | uint32[] internal productIds; 24 | 25 | mapping(uint32 => address) internal markets; // deprecated 26 | 27 | // Whether an address can apply deltas - all orderbooks and clearinghouse is whitelisted 28 | mapping(address => bool) internal canApplyDeltas; 29 | 30 | bytes32 internal constant RISK_STORAGE = keccak256("vertex.protocol.risk"); 31 | 32 | event BalanceUpdate(uint32 productId, bytes32 subaccount); 33 | event ProductUpdate(uint32 productId); 34 | 35 | function _productUpdate(uint32 productId) internal virtual {} 36 | 37 | struct Uint256Slot { 38 | uint256 value; 39 | } 40 | 41 | struct RiskStoreMappingSlot { 42 | mapping(uint32 => RiskHelper.RiskStore) value; 43 | } 44 | 45 | function _risk() internal pure returns (RiskStoreMappingSlot storage r) { 46 | bytes32 slot = RISK_STORAGE; 47 | assembly { 48 | r.slot := slot 49 | } 50 | } 51 | 52 | function _risk(uint32 productId, RiskStoreMappingSlot storage rmap) 53 | internal 54 | view 55 | returns (RiskHelper.Risk memory r) 56 | { 57 | RiskHelper.RiskStore memory s = rmap.value[productId]; 58 | r.longWeightInitialX18 = int128(s.longWeightInitial) * 1e9; 59 | r.shortWeightInitialX18 = int128(s.shortWeightInitial) * 1e9; 60 | r.longWeightMaintenanceX18 = int128(s.longWeightMaintenance) * 1e9; 61 | r.shortWeightMaintenanceX18 = int128(s.shortWeightMaintenance) * 1e9; 62 | r.priceX18 = s.priceX18; 63 | } 64 | 65 | function _risk(uint32 productId) 66 | internal 67 | view 68 | returns (RiskHelper.Risk memory) 69 | { 70 | return _risk(productId, _risk()); 71 | } 72 | 73 | function getRisk(uint32 productId) 74 | external 75 | view 76 | returns (RiskHelper.Risk memory) 77 | { 78 | return _risk(productId); 79 | } 80 | 81 | function _getInLpBalance(uint32 productId, bytes32 subaccount) 82 | internal 83 | view 84 | virtual 85 | returns ( 86 | // baseAmount, quoteAmount, quoteDeltaAmount (funding) 87 | int128, 88 | int128, 89 | int128 90 | ); 91 | 92 | function _getBalance(uint32 productId, bytes32 subaccount) 93 | internal 94 | view 95 | virtual 96 | returns (int128, int128); 97 | 98 | function getHealthContribution( 99 | bytes32 subaccount, 100 | IProductEngine.HealthType healthType 101 | ) public view returns (int128 health) { 102 | uint32[] memory _productIds = getProductIds(); 103 | RiskStoreMappingSlot storage r = _risk(); 104 | 105 | for (uint32 i = 0; i < _productIds.length; i++) { 106 | uint32 productId = _productIds[i]; 107 | RiskHelper.Risk memory risk = _risk(productId, r); 108 | { 109 | (int128 amount, int128 quoteAmount) = _getBalance( 110 | productId, 111 | subaccount 112 | ); 113 | int128 weight = RiskHelper._getWeightX18( 114 | risk, 115 | amount, 116 | healthType 117 | ); 118 | health += quoteAmount; 119 | if (amount != 0) { 120 | // anything with a short weight of 2 is a spot that 121 | // should not count towards health and exists out of the risk system 122 | // if we're getting a weight of 2 it means this is attempting to short 123 | // the spot, so we should error out 124 | if (weight == 2 * ONE) { 125 | return type(int128).min; 126 | } 127 | 128 | health += amount.mul(weight).mul(risk.priceX18); 129 | } 130 | } 131 | 132 | { 133 | ( 134 | int128 baseAmount, 135 | int128 quoteAmount, 136 | int128 quoteDeltaAmount 137 | ) = _getInLpBalance(productId, subaccount); 138 | if (baseAmount != 0) { 139 | int128 lpValue = RiskHelper._getLpRawValue( 140 | baseAmount, 141 | quoteAmount, 142 | risk.priceX18 143 | ); 144 | health += 145 | lpValue.mul( 146 | RiskHelper._getWeightX18(risk, 1, healthType) 147 | ) + 148 | quoteDeltaAmount; 149 | } 150 | } 151 | } 152 | } 153 | 154 | function getCoreRisk( 155 | bytes32 subaccount, 156 | uint32 productId, 157 | IProductEngine.HealthType healthType 158 | ) external view returns (IProductEngine.CoreRisk memory) { 159 | RiskHelper.Risk memory risk = _risk(productId); 160 | (int128 amount, ) = _getBalance(productId, subaccount); 161 | return 162 | IProductEngine.CoreRisk( 163 | amount, 164 | risk.priceX18, 165 | RiskHelper._getWeightX18(risk, 1, healthType) 166 | ); 167 | } 168 | 169 | function _balanceUpdate(uint32 productId, bytes32 subaccount) 170 | internal 171 | virtual 172 | {} 173 | 174 | function _assertInternal() internal view virtual { 175 | require(canApplyDeltas[msg.sender], ERR_UNAUTHORIZED); 176 | } 177 | 178 | function _initialize( 179 | address _clearinghouseAddr, 180 | address _offchainExchangeAddr, 181 | address _endpointAddr, 182 | address _admin 183 | ) internal initializer { 184 | __Ownable_init(); 185 | setEndpoint(_endpointAddr); 186 | transferOwnership(_admin); 187 | 188 | _clearinghouse = IClearinghouse(_clearinghouseAddr); 189 | 190 | canApplyDeltas[_endpointAddr] = true; 191 | canApplyDeltas[_clearinghouseAddr] = true; 192 | canApplyDeltas[_offchainExchangeAddr] = true; 193 | } 194 | 195 | function getClearinghouse() external view returns (address) { 196 | return address(_clearinghouse); 197 | } 198 | 199 | function getProductIds() public view returns (uint32[] memory) { 200 | return productIds; 201 | } 202 | 203 | function _addProductForId( 204 | uint32 productId, 205 | uint32 quoteId, 206 | address virtualBook, 207 | int128 sizeIncrement, 208 | int128 minSize, 209 | int128 lpSpreadX18, 210 | RiskHelper.RiskStore memory riskStore 211 | ) internal { 212 | require(virtualBook != address(0)); 213 | require( 214 | riskStore.longWeightInitial <= riskStore.longWeightMaintenance && 215 | riskStore.longWeightMaintenance <= 10**9 && 216 | riskStore.shortWeightInitial >= 217 | riskStore.shortWeightMaintenance && 218 | riskStore.shortWeightMaintenance >= 10**9, 219 | ERR_BAD_PRODUCT_CONFIG 220 | ); 221 | 222 | _risk().value[productId] = riskStore; 223 | 224 | // register product with clearinghouse 225 | _clearinghouse.registerProduct(productId); 226 | 227 | productIds.push(productId); 228 | // product ids are in ascending order 229 | for (uint256 i = productIds.length - 1; i > 0; i--) { 230 | if (productIds[i] < productIds[i - 1]) { 231 | uint32 t = productIds[i]; 232 | productIds[i] = productIds[i - 1]; 233 | productIds[i - 1] = t; 234 | } else { 235 | break; 236 | } 237 | } 238 | 239 | _exchange().updateMarket( 240 | productId, 241 | quoteId, 242 | virtualBook, 243 | sizeIncrement, 244 | minSize, 245 | lpSpreadX18 246 | ); 247 | 248 | emit AddProduct(productId); 249 | } 250 | 251 | function _exchange() internal view returns (IOffchainExchange) { 252 | return 253 | IOffchainExchange(IEndpoint(getEndpoint()).getOffchainExchange()); 254 | } 255 | 256 | function updatePrice(uint32 productId, int128 priceX18) external { 257 | require(msg.sender == address(_clearinghouse), ERR_UNAUTHORIZED); 258 | _risk().value[productId].priceX18 = priceX18; 259 | } 260 | 261 | function updateRisk(uint32 productId, RiskHelper.RiskStore memory riskStore) 262 | external 263 | onlyOwner 264 | { 265 | require( 266 | riskStore.longWeightInitial <= riskStore.longWeightMaintenance && 267 | riskStore.shortWeightInitial >= 268 | riskStore.shortWeightMaintenance, 269 | ERR_BAD_PRODUCT_CONFIG 270 | ); 271 | 272 | _risk().value[productId] = riskStore; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /core/contracts/BaseWithdrawPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; 6 | import "./libraries/MathHelper.sol"; 7 | import "./interfaces/IEndpoint.sol"; 8 | import "./Verifier.sol"; 9 | import "./interfaces/engine/ISpotEngine.sol"; 10 | import "./interfaces/IERC20Base.sol"; 11 | import "./libraries/ERC20Helper.sol"; 12 | import "./common/Constants.sol"; 13 | 14 | abstract contract BaseWithdrawPool is EIP712Upgradeable, OwnableUpgradeable { 15 | using ERC20Helper for IERC20Base; 16 | using MathSD21x18 for int128; 17 | 18 | /// @custom:oz-upgrades-unsafe-allow constructor 19 | constructor() { 20 | _disableInitializers(); 21 | } 22 | 23 | function _initialize(address _clearinghouse, address _verifier) 24 | internal 25 | initializer 26 | { 27 | __Ownable_init(); 28 | clearinghouse = _clearinghouse; 29 | verifier = _verifier; 30 | } 31 | 32 | address internal clearinghouse; 33 | 34 | address internal verifier; 35 | 36 | // submitted withdrawal idxs 37 | mapping(uint64 => bool) public markedIdxs; 38 | 39 | // collected withdrawal fees in native token decimals 40 | mapping(uint32 => int128) public fees; 41 | 42 | uint64 public minIdx; 43 | 44 | function submitFastWithdrawal( 45 | uint64 idx, 46 | bytes calldata transaction, 47 | bytes[] calldata signatures 48 | ) public { 49 | require(!markedIdxs[idx], "Withdrawal already submitted"); 50 | require(idx > minIdx, "idx too small"); 51 | markedIdxs[idx] = true; 52 | 53 | Verifier v = Verifier(verifier); 54 | v.requireValidTxSignatures(transaction, idx, signatures); 55 | 56 | IEndpoint.SignedWithdrawCollateral memory signedTx = abi.decode( 57 | transaction[1:], 58 | (IEndpoint.SignedWithdrawCollateral) 59 | ); 60 | 61 | IERC20Base token = getToken(signedTx.tx.productId); 62 | 63 | address sendTo = address(uint160(bytes20(signedTx.tx.sender))); 64 | uint128 transferAmount = signedTx.tx.amount; 65 | 66 | require(transferAmount <= INT128_MAX, ERR_CONVERSION_OVERFLOW); 67 | 68 | int128 fee = fastWithdrawalFeeAmount( 69 | token, 70 | signedTx.tx.productId, 71 | transferAmount 72 | ); 73 | 74 | require(transferAmount > uint128(fee), "Fee larger than balance"); 75 | transferAmount -= uint128(fee); 76 | fees[signedTx.tx.productId] += fee; 77 | 78 | handleWithdrawTransfer(token, sendTo, transferAmount); 79 | } 80 | 81 | function submitWithdrawal( 82 | IERC20Base token, 83 | address sendTo, 84 | uint128 amount, 85 | uint64 idx 86 | ) public { 87 | require(msg.sender == clearinghouse); 88 | 89 | if (markedIdxs[idx]) { 90 | return; 91 | } 92 | markedIdxs[idx] = true; 93 | // set minIdx to most recent withdrawal submitted by sequencer 94 | minIdx = idx; 95 | 96 | handleWithdrawTransfer(token, sendTo, amount); 97 | } 98 | 99 | function fastWithdrawalFeeAmount( 100 | IERC20Base token, 101 | uint32 productId, 102 | uint128 amount 103 | ) public view returns (int128) { 104 | uint8 decimals = token.decimals(); 105 | require(decimals <= MAX_DECIMALS); 106 | int256 multiplier = int256(10**(MAX_DECIMALS - uint8(decimals))); 107 | int128 amountX18 = int128(amount) * int128(multiplier); 108 | 109 | int128 proportionalFeeX18 = FAST_WITHDRAWAL_FEE_RATE.mul(amountX18); 110 | int128 minFeeX18 = 5 * 111 | IClearinghouse(clearinghouse).getWithdrawFee(productId); 112 | 113 | int128 feeX18 = MathHelper.max(proportionalFeeX18, minFeeX18); 114 | return feeX18 / int128(multiplier); 115 | } 116 | 117 | function removeLiquidity( 118 | uint32 productId, 119 | uint128 amount, 120 | address sendTo 121 | ) external onlyOwner { 122 | handleWithdrawTransfer(getToken(productId), sendTo, amount); 123 | } 124 | 125 | function checkMarkedIdxs(uint64[] calldata idxs) 126 | public 127 | view 128 | returns (bool[] memory) 129 | { 130 | bool[] memory marked = new bool[](idxs.length); 131 | for (uint256 i = 0; i < idxs.length; i++) { 132 | marked[i] = markedIdxs[idxs[i]]; 133 | } 134 | return marked; 135 | } 136 | 137 | function checkProductBalances(uint32[] calldata productIds) 138 | public 139 | view 140 | returns (uint256[] memory) 141 | { 142 | uint256[] memory balances = new uint256[](productIds.length); 143 | for (uint256 i = 0; i < productIds.length; i++) { 144 | IERC20Base token = getToken(productIds[i]); 145 | balances[i] = token.balanceOf(address(this)); 146 | } 147 | return balances; 148 | } 149 | 150 | function handleWithdrawTransfer( 151 | IERC20Base token, 152 | address to, 153 | uint128 amount 154 | ) internal virtual { 155 | token.safeTransfer(to, uint256(amount)); 156 | } 157 | 158 | function safeTransferFrom( 159 | IERC20Base token, 160 | address from, 161 | uint256 amount 162 | ) internal virtual { 163 | token.safeTransferFrom(from, address(this), amount); 164 | } 165 | 166 | function getToken(uint32 productId) internal view returns (IERC20Base) { 167 | IERC20Base token = IERC20Base(spotEngine().getConfig(productId).token); 168 | require(address(token) != address(0)); 169 | return token; 170 | } 171 | 172 | function spotEngine() internal view returns (ISpotEngine) { 173 | return 174 | ISpotEngine( 175 | IClearinghouse(clearinghouse).getEngineByType( 176 | IProductEngine.EngineType.SPOT 177 | ) 178 | ); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /core/contracts/ClearinghouseStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./interfaces/engine/IProductEngine.sol"; 5 | 6 | abstract contract ClearinghouseStorage { 7 | using MathSD21x18 for int128; 8 | 9 | struct LegacyHealthGroup { 10 | uint32 spotId; 11 | uint32 perpId; 12 | } 13 | 14 | struct LegacyRiskStore { 15 | int32 longWeightInitial; 16 | int32 shortWeightInitial; 17 | int32 longWeightMaintenance; 18 | int32 shortWeightMaintenance; 19 | int32 largePositionPenalty; 20 | } 21 | 22 | uint32 internal maxHealthGroup; // deprecated 23 | mapping(uint32 => LegacyHealthGroup) internal healthGroups; // deprecated 24 | mapping(uint32 => LegacyRiskStore) internal risks; // deprecated 25 | 26 | // Each clearinghouse has a quote ERC20 27 | address internal quote; 28 | 29 | address internal clearinghouse; 30 | address internal clearinghouseLiq; 31 | 32 | // fee calculator 33 | address internal fees; 34 | 35 | // Number of products registered across all engines 36 | uint32 internal numProducts; // deprecated 37 | 38 | // product ID -> engine address 39 | mapping(uint32 => IProductEngine) internal productToEngine; 40 | // Type to engine address 41 | mapping(IProductEngine.EngineType => IProductEngine) internal engineByType; 42 | // Supported engine types 43 | IProductEngine.EngineType[] internal supportedEngines; 44 | 45 | // insurance stuff, consider making it its own subaccount later 46 | int128 internal insurance; 47 | 48 | int128 internal lastLiquidationFees; 49 | 50 | uint256 internal spreads; 51 | 52 | address internal withdrawPool; 53 | 54 | function getLiqPriceX18(uint32 productId, int128 amount) 55 | internal 56 | view 57 | returns (int128, int128) 58 | { 59 | RiskHelper.Risk memory risk = IProductEngine(productToEngine[productId]) 60 | .getRisk(productId); 61 | return ( 62 | risk.priceX18.mul( 63 | ONE + 64 | (RiskHelper._getWeightX18( 65 | risk, 66 | amount, 67 | IProductEngine.HealthType.MAINTENANCE 68 | ) - ONE) / 69 | 5 70 | ), 71 | risk.priceX18 72 | ); 73 | } 74 | 75 | function getSpreadLiqPriceX18( 76 | uint32 spotId, 77 | uint32 perpId, 78 | int128 amount 79 | ) 80 | internal 81 | view 82 | returns ( 83 | int128, 84 | int128, 85 | int128 86 | ) 87 | { 88 | RiskHelper.Risk memory spotRisk = IProductEngine( 89 | productToEngine[spotId] 90 | ).getRisk(spotId); 91 | RiskHelper.Risk memory perpRisk = IProductEngine( 92 | productToEngine[perpId] 93 | ).getRisk(perpId); 94 | 95 | int128 spreadPenaltyX18; 96 | if (amount >= 0) { 97 | spreadPenaltyX18 = 98 | (ONE - 99 | RiskHelper._getWeightX18( 100 | perpRisk, 101 | amount, 102 | IProductEngine.HealthType.MAINTENANCE 103 | )) / 104 | 25; 105 | } else { 106 | spreadPenaltyX18 = 107 | (RiskHelper._getWeightX18( 108 | spotRisk, 109 | amount, 110 | IProductEngine.HealthType.MAINTENANCE 111 | ) - ONE) / 112 | 25; 113 | } 114 | 115 | if (amount > 0) { 116 | return ( 117 | spotRisk.priceX18.mul(ONE - spreadPenaltyX18), 118 | spotRisk.priceX18, 119 | perpRisk.priceX18 120 | ); 121 | } else { 122 | return ( 123 | spotRisk.priceX18.mul(ONE + spreadPenaltyX18), 124 | spotRisk.priceX18, 125 | perpRisk.priceX18 126 | ); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /core/contracts/EndpointGated.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "./interfaces/IEndpoint.sol"; 6 | import "./interfaces/IEndpointGated.sol"; 7 | import "./libraries/MathSD21x18.sol"; 8 | import "./common/Constants.sol"; 9 | import "hardhat/console.sol"; 10 | 11 | abstract contract EndpointGated is OwnableUpgradeable, IEndpointGated { 12 | address private endpoint; 13 | 14 | function setEndpoint(address _endpoint) internal onlyOwner { 15 | endpoint = _endpoint; 16 | } 17 | 18 | function getEndpoint() public view returns (address) { 19 | return endpoint; 20 | } 21 | 22 | function getOracleTime() internal view returns (uint128) { 23 | return IEndpoint(endpoint).getTime(); 24 | } 25 | 26 | modifier onlyEndpoint() { 27 | require( 28 | msg.sender == endpoint, 29 | "SequencerGated: caller is not the endpoint" 30 | ); 31 | _; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/contracts/MockSanctionsList.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | contract MockSanctionsList { 5 | function isSanctioned( 6 | address /* addr */ 7 | ) external pure returns (bool) { 8 | return false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/contracts/PerpEngine.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "hardhat/console.sol"; 5 | 6 | import "./common/Constants.sol"; 7 | import "./common/Errors.sol"; 8 | import "./interfaces/engine/IProductEngine.sol"; 9 | import "./interfaces/engine/IPerpEngine.sol"; 10 | import "./interfaces/clearinghouse/IClearinghouse.sol"; 11 | import "./libraries/MathHelper.sol"; 12 | import "./libraries/MathSD21x18.sol"; 13 | import "./BaseEngine.sol"; 14 | import "./PerpEngineLp.sol"; 15 | 16 | contract PerpEngine is PerpEngineLp { 17 | using MathSD21x18 for int128; 18 | 19 | function initialize( 20 | address _clearinghouse, 21 | address _offchainExchange, 22 | address, 23 | address _endpoint, 24 | address _admin 25 | ) external { 26 | _initialize(_clearinghouse, _offchainExchange, _endpoint, _admin); 27 | } 28 | 29 | function getEngineType() external pure returns (EngineType) { 30 | return EngineType.PERP; 31 | } 32 | 33 | /** 34 | * Actions 35 | */ 36 | 37 | /// @notice adds a new product with default parameters 38 | function addProduct( 39 | uint32 productId, 40 | address book, 41 | int128 sizeIncrement, 42 | int128 minSize, 43 | int128 lpSpreadX18, 44 | RiskHelper.RiskStore calldata riskStore 45 | ) public onlyOwner { 46 | _addProductForId( 47 | productId, 48 | QUOTE_PRODUCT_ID, 49 | book, 50 | sizeIncrement, 51 | minSize, 52 | lpSpreadX18, 53 | riskStore 54 | ); 55 | 56 | states[productId] = State({ 57 | cumulativeFundingLongX18: 0, 58 | cumulativeFundingShortX18: 0, 59 | availableSettle: 0, 60 | openInterest: 0 61 | }); 62 | 63 | lpStates[productId] = LpState({ 64 | supply: 0, 65 | lastCumulativeFundingX18: 0, 66 | cumulativeFundingPerLpX18: 0, 67 | base: 0, 68 | quote: 0 69 | }); 70 | } 71 | 72 | /// @notice changes the configs of a product, if a new book is provided 73 | /// also clears the book 74 | function updateProduct(bytes calldata rawTxn) external onlyEndpoint { 75 | UpdateProductTx memory txn = abi.decode(rawTxn, (UpdateProductTx)); 76 | RiskHelper.RiskStore memory riskStore = txn.riskStore; 77 | 78 | require( 79 | riskStore.longWeightInitial <= riskStore.longWeightMaintenance && 80 | riskStore.shortWeightInitial >= 81 | riskStore.shortWeightMaintenance, 82 | ERR_BAD_PRODUCT_CONFIG 83 | ); 84 | 85 | RiskHelper.RiskStore memory r = _risk().value[txn.productId]; 86 | r.longWeightInitial = riskStore.longWeightInitial; 87 | r.shortWeightInitial = riskStore.shortWeightInitial; 88 | r.longWeightMaintenance = riskStore.longWeightMaintenance; 89 | r.shortWeightMaintenance = riskStore.shortWeightMaintenance; 90 | _risk().value[txn.productId] = r; 91 | 92 | _exchange().updateMarket( 93 | txn.productId, 94 | type(uint32).max, 95 | address(0), 96 | txn.sizeIncrement, 97 | txn.minSize, 98 | txn.lpSpreadX18 99 | ); 100 | } 101 | 102 | function updateBalance( 103 | uint32 productId, 104 | bytes32 subaccount, 105 | int128 amountDelta, 106 | int128 vQuoteDelta 107 | ) external { 108 | // Only a market book can apply deltas 109 | _assertInternal(); 110 | State memory state = states[productId]; 111 | Balance memory balance = balances[productId][subaccount]; 112 | 113 | _updateBalance(state, balance, amountDelta, vQuoteDelta); 114 | 115 | states[productId] = state; 116 | balances[productId][subaccount] = balance; 117 | _balanceUpdate(productId, subaccount); 118 | } 119 | 120 | function settlePnl(bytes32 subaccount, uint256 productIds) 121 | external 122 | returns (int128) 123 | { 124 | _assertInternal(); 125 | int128 totalSettled = 0; 126 | 127 | while (productIds != 0) { 128 | uint32 productId = uint32(productIds & ((1 << 32) - 1)); 129 | // otherwise it means the product is a spot. 130 | if (productId % 2 == 0) { 131 | ( 132 | int128 canSettle, 133 | LpState memory lpState, 134 | LpBalance memory lpBalance, 135 | State memory state, 136 | Balance memory balance 137 | ) = getSettlementState(productId, subaccount); 138 | 139 | state.availableSettle -= canSettle; 140 | balance.vQuoteBalance -= canSettle; 141 | 142 | totalSettled += canSettle; 143 | 144 | lpStates[productId] = lpState; 145 | states[productId] = state; 146 | lpBalances[productId][subaccount] = lpBalance; 147 | balances[productId][subaccount] = balance; 148 | _balanceUpdate(productId, subaccount); 149 | } 150 | productIds >>= 32; 151 | } 152 | return totalSettled; 153 | } 154 | 155 | function calculatePositionPnl( 156 | LpState memory lpState, 157 | LpBalance memory lpBalance, 158 | Balance memory balance, 159 | uint32 productId 160 | ) internal view returns (int128 positionPnl) { 161 | int128 priceX18 = _risk(productId).priceX18; 162 | 163 | (int128 ammBase, int128 ammQuote) = MathHelper.ammEquilibrium( 164 | lpState.base, 165 | lpState.quote, 166 | priceX18 167 | ); 168 | 169 | if (lpBalance.amount == 0) { 170 | positionPnl = priceX18.mul(balance.amount) + balance.vQuoteBalance; 171 | } else { 172 | positionPnl = 173 | priceX18.mul( 174 | balance.amount + 175 | ammBase.mul(lpBalance.amount).div(lpState.supply) 176 | ) + 177 | balance.vQuoteBalance + 178 | ammQuote.mul(lpBalance.amount).div(lpState.supply); 179 | } 180 | } 181 | 182 | function getPositionPnl(uint32 productId, bytes32 subaccount) 183 | external 184 | view 185 | returns (int128) 186 | { 187 | ( 188 | LpState memory lpState, 189 | LpBalance memory lpBalance, 190 | , 191 | Balance memory balance 192 | ) = getStatesAndBalances(productId, subaccount); 193 | 194 | return calculatePositionPnl(lpState, lpBalance, balance, productId); 195 | } 196 | 197 | function getSettlementState(uint32 productId, bytes32 subaccount) 198 | public 199 | view 200 | returns ( 201 | int128 availableSettle, 202 | LpState memory lpState, 203 | LpBalance memory lpBalance, 204 | State memory state, 205 | Balance memory balance 206 | ) 207 | { 208 | (lpState, lpBalance, state, balance) = getStatesAndBalances( 209 | productId, 210 | subaccount 211 | ); 212 | 213 | availableSettle = MathHelper.min( 214 | calculatePositionPnl(lpState, lpBalance, balance, productId), 215 | state.availableSettle 216 | ); 217 | } 218 | 219 | function socializeSubaccount(bytes32 subaccount, int128 insurance) 220 | external 221 | returns (int128) 222 | { 223 | require(msg.sender == address(_clearinghouse), ERR_UNAUTHORIZED); 224 | 225 | uint32[] memory _productIds = getProductIds(); 226 | for (uint128 i = 0; i < _productIds.length; ++i) { 227 | uint32 productId = _productIds[i]; 228 | (State memory state, Balance memory balance) = getStateAndBalance( 229 | productId, 230 | subaccount 231 | ); 232 | if (balance.vQuoteBalance < 0) { 233 | int128 insuranceCover = MathHelper.min( 234 | insurance, 235 | -balance.vQuoteBalance 236 | ); 237 | insurance -= insuranceCover; 238 | balance.vQuoteBalance += insuranceCover; 239 | state.availableSettle += insuranceCover; 240 | 241 | // actually socialize if still not enough 242 | if (balance.vQuoteBalance < 0) { 243 | // socialize across all other participants 244 | int128 fundingPerShare = -balance.vQuoteBalance.div( 245 | state.openInterest 246 | ) / 2; 247 | state.cumulativeFundingLongX18 += fundingPerShare; 248 | state.cumulativeFundingShortX18 -= fundingPerShare; 249 | 250 | LpState memory lpState = lpStates[productId]; 251 | Balance memory tmp = Balance({ 252 | amount: lpState.base, 253 | vQuoteBalance: 0, 254 | lastCumulativeFundingX18: lpState 255 | .lastCumulativeFundingX18 256 | }); 257 | _updateBalance(state, tmp, 0, 0); 258 | if (lpState.supply != 0) { 259 | lpState.cumulativeFundingPerLpX18 += tmp 260 | .vQuoteBalance 261 | .div(lpState.supply); 262 | } 263 | lpState.lastCumulativeFundingX18 = state 264 | .cumulativeFundingLongX18; 265 | 266 | lpStates[productId] = lpState; 267 | balance.vQuoteBalance = 0; 268 | } 269 | states[productId] = state; 270 | balances[productId][subaccount] = balance; 271 | _balanceUpdate(productId, subaccount); 272 | } 273 | } 274 | return insurance; 275 | } 276 | 277 | function manualAssert(int128[] calldata openInterests) external view { 278 | for (uint128 i = 0; i < openInterests.length; ++i) { 279 | uint32 productId = productIds[i]; 280 | require( 281 | states[productId].openInterest == openInterests[i], 282 | ERR_DSYNC 283 | ); 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /core/contracts/PerpEngineLp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./PerpEngineState.sol"; 5 | import "./libraries/Logger.sol"; 6 | 7 | abstract contract PerpEngineLp is PerpEngineState { 8 | using MathSD21x18 for int128; 9 | 10 | function mintLp( 11 | uint32 productId, 12 | bytes32 subaccount, 13 | int128 amountBase, 14 | int128 quoteAmountLow, 15 | int128 quoteAmountHigh 16 | ) external { 17 | _assertInternal(); 18 | 19 | int128 sizeIncrement = _exchange().getSizeIncrement(productId); 20 | 21 | require( 22 | amountBase > 0 && 23 | quoteAmountLow > 0 && 24 | quoteAmountHigh > 0 && 25 | amountBase % sizeIncrement == 0, 26 | ERR_INVALID_LP_AMOUNT 27 | ); 28 | 29 | ( 30 | LpState memory lpState, 31 | LpBalance memory lpBalance, 32 | State memory state, 33 | Balance memory balance 34 | ) = getStatesAndBalances(productId, subaccount); 35 | 36 | int128 amountQuote = (lpState.base == 0) 37 | ? amountBase.mul(_risk(productId).priceX18) 38 | : amountBase.mul(lpState.quote.div(lpState.base)); 39 | require(amountQuote >= quoteAmountLow, ERR_SLIPPAGE_TOO_HIGH); 40 | require(amountQuote <= quoteAmountHigh, ERR_SLIPPAGE_TOO_HIGH); 41 | 42 | int128 toMint; 43 | if (lpState.supply == 0) { 44 | toMint = amountBase + amountQuote; 45 | } else { 46 | toMint = amountBase.div(lpState.base).mul(lpState.supply); 47 | } 48 | 49 | state.openInterest += amountBase; 50 | 51 | lpState.base += amountBase; 52 | lpState.quote += amountQuote; 53 | lpBalance.amount += toMint; 54 | _updateBalance(state, balance, -amountBase, -amountQuote); 55 | lpState.supply += toMint; 56 | 57 | lpBalances[productId][subaccount] = lpBalance; 58 | states[productId] = state; 59 | lpStates[productId] = lpState; 60 | balances[productId][subaccount] = balance; 61 | 62 | _balanceUpdate(productId, subaccount); 63 | } 64 | 65 | function burnLp( 66 | uint32 productId, 67 | bytes32 subaccount, 68 | int128 amountLp 69 | ) public returns (int128 amountBase, int128 amountQuote) { 70 | _assertInternal(); 71 | require(amountLp > 0, ERR_INVALID_LP_AMOUNT); 72 | int128 sizeIncrement = _exchange().getSizeIncrement(productId); 73 | 74 | ( 75 | LpState memory lpState, 76 | LpBalance memory lpBalance, 77 | State memory state, 78 | Balance memory balance 79 | ) = getStatesAndBalances(productId, subaccount); 80 | 81 | if (amountLp == type(int128).max) { 82 | amountLp = lpBalance.amount; 83 | } 84 | if (amountLp == 0) { 85 | return (0, 0); 86 | } 87 | 88 | require(lpBalance.amount >= amountLp, ERR_INSUFFICIENT_LP); 89 | lpBalance.amount -= amountLp; 90 | 91 | amountBase = MathHelper.floor( 92 | int128((int256(amountLp) * lpState.base) / lpState.supply), 93 | sizeIncrement 94 | ); 95 | 96 | amountQuote = int128( 97 | (int256(amountLp) * lpState.quote) / lpState.supply 98 | ); 99 | 100 | state.openInterest -= amountBase; 101 | 102 | _updateBalance(state, balance, amountBase, amountQuote); 103 | lpState.base -= amountBase; 104 | lpState.quote -= amountQuote; 105 | lpState.supply -= amountLp; 106 | 107 | lpStates[productId] = lpState; 108 | lpBalances[productId][subaccount] = lpBalance; 109 | states[productId] = state; 110 | balances[productId][subaccount] = balance; 111 | 112 | _balanceUpdate(productId, subaccount); 113 | } 114 | 115 | function swapLp( 116 | uint32 productId, 117 | int128 baseDelta, 118 | int128 quoteDelta 119 | ) external returns (int128, int128) { 120 | _assertInternal(); 121 | LpState memory lpState = lpStates[productId]; 122 | require( 123 | MathHelper.isSwapValid( 124 | baseDelta, 125 | quoteDelta, 126 | lpState.base, 127 | lpState.quote 128 | ), 129 | ERR_INVALID_MAKER 130 | ); 131 | 132 | states[productId].openInterest += baseDelta; 133 | 134 | lpState.base += baseDelta; 135 | lpState.quote += quoteDelta; 136 | lpStates[productId] = lpState; 137 | _productUpdate(productId); 138 | return (baseDelta, quoteDelta); 139 | } 140 | 141 | function decomposeLps(bytes32 liquidatee, bytes32 liquidator) 142 | external 143 | returns (int128 liquidationFees) 144 | { 145 | uint32[] memory _productIds = getProductIds(); 146 | for (uint128 i = 0; i < _productIds.length; ++i) { 147 | uint32 productId = _productIds[i]; 148 | (, int128 amountQuote) = burnLp( 149 | productId, 150 | liquidatee, 151 | type(int128).max 152 | ); 153 | if (amountQuote != 0) { 154 | int128 rewards = amountQuote.mul( 155 | (ONE - 156 | RiskHelper._getWeightX18( 157 | _risk(productId), 158 | amountQuote, 159 | IProductEngine.HealthType.MAINTENANCE 160 | )) / 50 161 | ); 162 | 163 | int128 fees = rewards.mul(LIQUIDATION_FEE_FRACTION); 164 | rewards -= fees; 165 | liquidationFees += fees; 166 | 167 | // transfer some of the burned proceeds to liquidator 168 | State memory state = states[productId]; 169 | Balance memory liquidateeBalance = balances[productId][ 170 | liquidatee 171 | ]; 172 | Balance memory liquidatorBalance = balances[productId][ 173 | liquidator 174 | ]; 175 | _updateBalance(state, liquidateeBalance, 0, -rewards - fees); 176 | _updateBalance(state, liquidatorBalance, 0, rewards); 177 | 178 | states[productId] = state; 179 | balances[productId][liquidatee] = liquidateeBalance; 180 | balances[productId][liquidator] = liquidatorBalance; 181 | _balanceUpdate(productId, liquidator); 182 | _balanceUpdate(productId, liquidatee); 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /core/contracts/PerpEngineState.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./interfaces/engine/IPerpEngine.sol"; 5 | import "./BaseEngine.sol"; 6 | 7 | int128 constant EMA_TIME_CONSTANT_X18 = 998334721450938752; 8 | int128 constant ONE_DAY_X18 = 86400_000000000000000000; // 24 hours 9 | 10 | // we will want to config this later, but for now this is global and a percentage 11 | int128 constant MAX_DAILY_FUNDING_RATE = 20000000000000000; // 0.02 12 | 13 | abstract contract PerpEngineState is IPerpEngine, BaseEngine { 14 | using MathSD21x18 for int128; 15 | 16 | mapping(uint32 => State) public states; 17 | mapping(uint32 => mapping(bytes32 => Balance)) public balances; 18 | 19 | mapping(uint32 => LpState) public lpStates; 20 | mapping(uint32 => mapping(bytes32 => LpBalance)) public lpBalances; 21 | 22 | function _updateBalance( 23 | State memory state, 24 | Balance memory balance, 25 | int128 balanceDelta, 26 | int128 vQuoteDelta 27 | ) internal pure { 28 | // pre update 29 | state.openInterest -= (balance.amount > 0) ? balance.amount : int128(0); 30 | int128 cumulativeFundingAmountX18 = (balance.amount > 0) 31 | ? state.cumulativeFundingLongX18 32 | : state.cumulativeFundingShortX18; 33 | int128 diffX18 = cumulativeFundingAmountX18 - 34 | balance.lastCumulativeFundingX18; 35 | int128 deltaQuote = vQuoteDelta - diffX18.mul(balance.amount); 36 | 37 | // apply delta 38 | balance.amount += balanceDelta; 39 | 40 | // apply vquote 41 | balance.vQuoteBalance += deltaQuote; 42 | 43 | // post update 44 | if (balance.amount > 0) { 45 | state.openInterest += balance.amount; 46 | balance.lastCumulativeFundingX18 = state.cumulativeFundingLongX18; 47 | } else { 48 | balance.lastCumulativeFundingX18 = state.cumulativeFundingShortX18; 49 | } 50 | } 51 | 52 | function _applyLpBalanceFunding( 53 | LpState memory lpState, 54 | LpBalance memory lpBalance, 55 | Balance memory balance 56 | ) internal pure { 57 | int128 vQuoteDelta = (lpState.cumulativeFundingPerLpX18 - 58 | lpBalance.lastCumulativeFundingX18).mul(lpBalance.amount); 59 | balance.vQuoteBalance += vQuoteDelta; 60 | lpBalance.lastCumulativeFundingX18 = lpState.cumulativeFundingPerLpX18; 61 | } 62 | 63 | function getStateAndBalance(uint32 productId, bytes32 subaccount) 64 | public 65 | view 66 | returns (State memory, Balance memory) 67 | { 68 | State memory state = states[productId]; 69 | Balance memory balance = balances[productId][subaccount]; 70 | _updateBalance(state, balance, 0, 0); 71 | return (state, balance); 72 | } 73 | 74 | function getBalance(uint32 productId, bytes32 subaccount) 75 | public 76 | view 77 | returns (Balance memory) 78 | { 79 | State memory state = states[productId]; 80 | Balance memory balance = balances[productId][subaccount]; 81 | _updateBalance(state, balance, 0, 0); 82 | return balance; 83 | } 84 | 85 | function _getBalance(uint32 productId, bytes32 subaccount) 86 | internal 87 | view 88 | virtual 89 | override 90 | returns (int128, int128) 91 | { 92 | State memory state = states[productId]; 93 | Balance memory balance = balances[productId][subaccount]; 94 | _updateBalance(state, balance, 0, 0); 95 | return (balance.amount, balance.vQuoteBalance); 96 | } 97 | 98 | function _getInLpBalance(uint32 productId, bytes32 subaccount) 99 | internal 100 | view 101 | virtual 102 | override 103 | returns ( 104 | // baseAmount, quoteAmount, deltaQuoteAmount (funding) 105 | int128, 106 | int128, 107 | int128 108 | ) 109 | { 110 | LpBalance memory lpBalance = lpBalances[productId][subaccount]; 111 | if (lpBalance.amount == 0) { 112 | return (0, 0, 0); 113 | } 114 | LpState memory lpState = lpStates[productId]; 115 | int128 ratio = lpBalance.amount.div(lpState.supply); 116 | int128 baseAmount = lpState.base.mul(ratio); 117 | int128 quoteAmount = lpState.quote.mul(ratio); 118 | 119 | int128 quoteDeltaAmount = lpState 120 | .cumulativeFundingPerLpX18 121 | .sub(lpBalance.lastCumulativeFundingX18) 122 | .mul(lpBalance.amount); 123 | return (baseAmount, quoteAmount, quoteDeltaAmount); 124 | } 125 | 126 | function getStatesAndBalances(uint32 productId, bytes32 subaccount) 127 | public 128 | view 129 | returns ( 130 | LpState memory, 131 | LpBalance memory, 132 | State memory, 133 | Balance memory 134 | ) 135 | { 136 | LpState memory lpState = lpStates[productId]; 137 | State memory state = states[productId]; 138 | LpBalance memory lpBalance = lpBalances[productId][subaccount]; 139 | Balance memory balance = balances[productId][subaccount]; 140 | 141 | _updateBalance(state, balance, 0, 0); 142 | _applyLpBalanceFunding(lpState, lpBalance, balance); 143 | return (lpState, lpBalance, state, balance); 144 | } 145 | 146 | function updateStates(uint128 dt, int128[] calldata avgPriceDiffs) 147 | external 148 | onlyEndpoint 149 | { 150 | int128 dtX18 = int128(dt).fromInt(); 151 | for (uint32 i = 0; i < avgPriceDiffs.length; i++) { 152 | uint32 productId = productIds[i]; 153 | State memory state = states[productId]; 154 | if (state.openInterest == 0) { 155 | continue; 156 | } 157 | require(dt < 7 * SECONDS_PER_DAY, ERR_INVALID_TIME); 158 | 159 | LpState memory lpState = lpStates[productId]; 160 | 161 | { 162 | int128 indexPriceX18 = _risk(productId).priceX18; 163 | 164 | // cap this price diff 165 | int128 priceDiffX18 = avgPriceDiffs[i]; 166 | 167 | int128 maxPriceDiff = MAX_DAILY_FUNDING_RATE.mul(indexPriceX18); 168 | 169 | if (priceDiffX18.abs() > maxPriceDiff) { 170 | // Proper sign 171 | priceDiffX18 = (priceDiffX18 > 0) 172 | ? maxPriceDiff 173 | : -maxPriceDiff; 174 | } 175 | 176 | int128 paymentAmount = priceDiffX18.mul(dtX18).div(ONE_DAY_X18); 177 | state.cumulativeFundingLongX18 += paymentAmount; 178 | state.cumulativeFundingShortX18 += paymentAmount; 179 | 180 | emit FundingPayment( 181 | productId, 182 | dt, 183 | state.openInterest, 184 | paymentAmount 185 | ); 186 | } 187 | 188 | { 189 | Balance memory balance = Balance({ 190 | amount: lpState.base, 191 | vQuoteBalance: 0, 192 | lastCumulativeFundingX18: lpState.lastCumulativeFundingX18 193 | }); 194 | _updateBalance(state, balance, 0, 0); 195 | if (lpState.supply != 0) { 196 | lpState.cumulativeFundingPerLpX18 += balance 197 | .vQuoteBalance 198 | .div(lpState.supply); 199 | } 200 | lpState.lastCumulativeFundingX18 = state 201 | .cumulativeFundingLongX18; 202 | } 203 | lpStates[productId] = lpState; 204 | states[productId] = state; 205 | _productUpdate(productId); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /core/contracts/SpotEngine.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./common/Constants.sol"; 5 | import "./common/Errors.sol"; 6 | import "./interfaces/engine/ISpotEngine.sol"; 7 | import "./interfaces/clearinghouse/IClearinghouse.sol"; 8 | import "./libraries/MathHelper.sol"; 9 | import "./libraries/MathSD21x18.sol"; 10 | import "./libraries/RiskHelper.sol"; 11 | import "./BaseEngine.sol"; 12 | import "./SpotEngineState.sol"; 13 | import "./SpotEngineLP.sol"; 14 | 15 | contract SpotEngine is SpotEngineLP { 16 | using MathSD21x18 for int128; 17 | 18 | function initialize( 19 | address _clearinghouse, 20 | address _offchainExchange, 21 | address _quote, 22 | address _endpoint, 23 | address _admin 24 | ) external { 25 | _initialize(_clearinghouse, _offchainExchange, _endpoint, _admin); 26 | 27 | configs[QUOTE_PRODUCT_ID] = Config({ 28 | token: _quote, 29 | interestInflectionUtilX18: 8e17, // .8 30 | interestFloorX18: 1e16, // .01 31 | interestSmallCapX18: 4e16, // .04 32 | interestLargeCapX18: ONE // 1 33 | }); 34 | _risk().value[QUOTE_PRODUCT_ID] = RiskHelper.RiskStore({ 35 | longWeightInitial: 1e9, 36 | shortWeightInitial: 1e9, 37 | longWeightMaintenance: 1e9, 38 | shortWeightMaintenance: 1e9, 39 | priceX18: ONE 40 | }); 41 | states[QUOTE_PRODUCT_ID] = State({ 42 | cumulativeDepositsMultiplierX18: ONE, 43 | cumulativeBorrowsMultiplierX18: ONE, 44 | totalDepositsNormalized: 0, 45 | totalBorrowsNormalized: 0 46 | }); 47 | productIds.push(QUOTE_PRODUCT_ID); 48 | emit AddProduct(QUOTE_PRODUCT_ID); 49 | } 50 | 51 | /** 52 | * View 53 | */ 54 | 55 | function getEngineType() external pure returns (EngineType) { 56 | return EngineType.SPOT; 57 | } 58 | 59 | function getConfig(uint32 productId) external view returns (Config memory) { 60 | return configs[productId]; 61 | } 62 | 63 | /** 64 | * Actions 65 | */ 66 | 67 | /// @notice adds a new product with default parameters 68 | function addProduct( 69 | uint32 productId, 70 | uint32 quoteId, 71 | address book, 72 | int128 sizeIncrement, 73 | int128 minSize, 74 | int128 lpSpreadX18, 75 | Config calldata config, 76 | RiskHelper.RiskStore calldata riskStore 77 | ) public onlyOwner { 78 | require(productId != QUOTE_PRODUCT_ID); 79 | _addProductForId( 80 | productId, 81 | quoteId, 82 | book, 83 | sizeIncrement, 84 | minSize, 85 | lpSpreadX18, 86 | riskStore 87 | ); 88 | 89 | configs[productId] = config; 90 | states[productId] = State({ 91 | cumulativeDepositsMultiplierX18: ONE, 92 | cumulativeBorrowsMultiplierX18: ONE, 93 | totalDepositsNormalized: 0, 94 | totalBorrowsNormalized: 0 95 | }); 96 | 97 | lpStates[productId] = LpState({ 98 | supply: 0, 99 | quote: Balance({amount: 0, lastCumulativeMultiplierX18: ONE}), 100 | base: Balance({amount: 0, lastCumulativeMultiplierX18: ONE}) 101 | }); 102 | } 103 | 104 | function updateProduct(bytes calldata rawTxn) external onlyEndpoint { 105 | UpdateProductTx memory txn = abi.decode(rawTxn, (UpdateProductTx)); 106 | RiskHelper.RiskStore memory riskStore = txn.riskStore; 107 | 108 | if (txn.productId != QUOTE_PRODUCT_ID) { 109 | require( 110 | riskStore.longWeightInitial <= 111 | riskStore.longWeightMaintenance && 112 | riskStore.shortWeightInitial >= 113 | riskStore.shortWeightMaintenance && 114 | configs[txn.productId].token == txn.config.token, 115 | ERR_BAD_PRODUCT_CONFIG 116 | ); 117 | 118 | RiskHelper.RiskStore memory r = _risk().value[txn.productId]; 119 | r.longWeightInitial = riskStore.longWeightInitial; 120 | r.shortWeightInitial = riskStore.shortWeightInitial; 121 | r.longWeightMaintenance = riskStore.longWeightMaintenance; 122 | r.shortWeightMaintenance = riskStore.shortWeightMaintenance; 123 | _risk().value[txn.productId] = r; 124 | 125 | _exchange().updateMarket( 126 | txn.productId, 127 | type(uint32).max, 128 | address(0), 129 | txn.sizeIncrement, 130 | txn.minSize, 131 | txn.lpSpreadX18 132 | ); 133 | } 134 | 135 | configs[txn.productId] = txn.config; 136 | } 137 | 138 | function updateQuoteFromInsurance(bytes32 subaccount, int128 insurance) 139 | external 140 | returns (int128) 141 | { 142 | _assertInternal(); 143 | State memory state = states[QUOTE_PRODUCT_ID]; 144 | BalanceNormalized memory balanceNormalized = balances[QUOTE_PRODUCT_ID][ 145 | subaccount 146 | ].balance; 147 | int128 balanceAmount = balanceNormalizedToBalance( 148 | state, 149 | balanceNormalized 150 | ).amount; 151 | if (balanceAmount < 0) { 152 | int128 topUpAmount = MathHelper.max( 153 | MathHelper.min(insurance, -balanceAmount), 154 | 0 155 | ); 156 | insurance -= topUpAmount; 157 | _updateBalanceNormalized(state, balanceNormalized, topUpAmount); 158 | } 159 | states[QUOTE_PRODUCT_ID] = state; 160 | balances[QUOTE_PRODUCT_ID][subaccount].balance = balanceNormalized; 161 | return insurance; 162 | } 163 | 164 | function updateBalance( 165 | uint32 productId, 166 | bytes32 subaccount, 167 | int128 amountDelta, 168 | int128 quoteDelta 169 | ) external { 170 | require(productId != QUOTE_PRODUCT_ID, ERR_INVALID_PRODUCT); 171 | _assertInternal(); 172 | State memory state = states[productId]; 173 | State memory quoteState = states[QUOTE_PRODUCT_ID]; 174 | 175 | BalanceNormalized memory balance = balances[productId][subaccount] 176 | .balance; 177 | 178 | BalanceNormalized memory quoteBalance = balances[QUOTE_PRODUCT_ID][ 179 | subaccount 180 | ].balance; 181 | 182 | _updateBalanceNormalized(state, balance, amountDelta); 183 | _updateBalanceNormalized(quoteState, quoteBalance, quoteDelta); 184 | 185 | balances[productId][subaccount].balance = balance; 186 | balances[QUOTE_PRODUCT_ID][subaccount].balance = quoteBalance; 187 | 188 | states[productId] = state; 189 | states[QUOTE_PRODUCT_ID] = quoteState; 190 | 191 | _balanceUpdate(productId, subaccount); 192 | _balanceUpdate(QUOTE_PRODUCT_ID, subaccount); 193 | } 194 | 195 | function updateBalance( 196 | uint32 productId, 197 | bytes32 subaccount, 198 | int128 amountDelta 199 | ) external { 200 | _assertInternal(); 201 | 202 | State memory state = states[productId]; 203 | 204 | BalanceNormalized memory balance = balances[productId][subaccount] 205 | .balance; 206 | _updateBalanceNormalized(state, balance, amountDelta); 207 | balances[productId][subaccount].balance = balance; 208 | 209 | states[productId] = state; 210 | _balanceUpdate(productId, subaccount); 211 | } 212 | 213 | // only check on withdraw -- ensure that users can't withdraw 214 | // funds that are in the Vertex contract but not officially 215 | // 'deposited' into the Vertex system and counted in balances 216 | // (i.e. if a user transfers tokens to the clearinghouse 217 | // without going through the standard deposit) 218 | function assertUtilization(uint32 productId) external view { 219 | (State memory _state, ) = getStateAndBalance(productId, X_ACCOUNT); 220 | int128 totalDeposits = _state.totalDepositsNormalized.mul( 221 | _state.cumulativeDepositsMultiplierX18 222 | ); 223 | int128 totalBorrows = _state.totalBorrowsNormalized.mul( 224 | _state.cumulativeBorrowsMultiplierX18 225 | ); 226 | require(totalDeposits >= totalBorrows, ERR_MAX_UTILIZATION); 227 | } 228 | 229 | function socializeSubaccount(bytes32 subaccount) external { 230 | require(msg.sender == address(_clearinghouse), ERR_UNAUTHORIZED); 231 | 232 | uint32[] memory _productIds = getProductIds(); 233 | for (uint128 i = 0; i < _productIds.length; ++i) { 234 | uint32 productId = _productIds[i]; 235 | 236 | State memory state = states[productId]; 237 | Balance memory balance = balanceNormalizedToBalance( 238 | state, 239 | balances[productId][subaccount].balance 240 | ); 241 | if (balance.amount < 0) { 242 | int128 totalDeposited = state.totalDepositsNormalized.mul( 243 | state.cumulativeDepositsMultiplierX18 244 | ); 245 | 246 | state.cumulativeDepositsMultiplierX18 = (totalDeposited + 247 | balance.amount).div(state.totalDepositsNormalized); 248 | 249 | require(state.cumulativeDepositsMultiplierX18 > 0); 250 | 251 | state.totalBorrowsNormalized += balance.amount.div( 252 | state.cumulativeBorrowsMultiplierX18 253 | ); 254 | 255 | balances[productId][subaccount].balance.amountNormalized = 0; 256 | 257 | if (productId == QUOTE_PRODUCT_ID) { 258 | for (uint32 j = 0; j < _productIds.length; ++j) { 259 | uint32 baseProductId = _productIds[j]; 260 | if (baseProductId == QUOTE_PRODUCT_ID) { 261 | continue; 262 | } 263 | LpState memory lpState = lpStates[baseProductId]; 264 | _updateBalanceWithoutDelta(state, lpState.quote); 265 | lpStates[baseProductId] = lpState; 266 | _productUpdate(baseProductId); 267 | } 268 | } else { 269 | LpState memory lpState = lpStates[productId]; 270 | _updateBalanceWithoutDelta(state, lpState.base); 271 | lpStates[productId] = lpState; 272 | } 273 | states[productId] = state; 274 | _balanceUpdate(productId, subaccount); 275 | } 276 | } 277 | } 278 | 279 | function manualAssert( 280 | int128[] calldata totalDeposits, 281 | int128[] calldata totalBorrows 282 | ) external view { 283 | for (uint128 i = 0; i < totalDeposits.length; ++i) { 284 | uint32 productId = productIds[i]; 285 | State memory state = states[productId]; 286 | require( 287 | state.totalDepositsNormalized.mul( 288 | state.cumulativeDepositsMultiplierX18 289 | ) == totalDeposits[i], 290 | ERR_DSYNC 291 | ); 292 | require( 293 | state.totalBorrowsNormalized.mul( 294 | state.cumulativeBorrowsMultiplierX18 295 | ) == totalBorrows[i], 296 | ERR_DSYNC 297 | ); 298 | } 299 | } 300 | 301 | function getToken(uint32 productId) external view returns (address) { 302 | return address(configs[productId].token); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /core/contracts/SpotEngineLP.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./SpotEngineState.sol"; 5 | import "./libraries/Logger.sol"; 6 | 7 | abstract contract SpotEngineLP is SpotEngineState { 8 | using MathSD21x18 for int128; 9 | 10 | function mintLp( 11 | uint32 productId, 12 | bytes32 subaccount, 13 | int128 amountBase, 14 | int128 quoteAmountLow, 15 | int128 quoteAmountHigh 16 | ) external { 17 | _assertInternal(); 18 | require( 19 | amountBase > 0 && quoteAmountLow > 0 && quoteAmountHigh > 0, 20 | ERR_INVALID_LP_AMOUNT 21 | ); 22 | 23 | require( 24 | _exchange().getMarketInfo(productId).quoteId == QUOTE_PRODUCT_ID, 25 | ERR_INVALID_PRODUCT 26 | ); 27 | 28 | LpState memory lpState = lpStates[productId]; 29 | State memory base = states[productId]; 30 | State memory quote = states[QUOTE_PRODUCT_ID]; 31 | 32 | int128 amountQuote = (lpState.base.amount == 0) 33 | ? amountBase.mul(_risk(productId).priceX18) 34 | : amountBase.mul(lpState.quote.amount.div(lpState.base.amount)); 35 | require(amountQuote >= quoteAmountLow, ERR_SLIPPAGE_TOO_HIGH); 36 | require(amountQuote <= quoteAmountHigh, ERR_SLIPPAGE_TOO_HIGH); 37 | 38 | int128 toMint; 39 | if (lpState.supply == 0) { 40 | toMint = amountBase + amountQuote; 41 | } else { 42 | toMint = amountBase.div(lpState.base.amount).mul(lpState.supply); 43 | } 44 | 45 | _updateBalance(base, lpState.base, amountBase); 46 | _updateBalance(quote, lpState.quote, amountQuote); 47 | lpState.supply += toMint; 48 | 49 | balances[productId][subaccount].lpBalance.amount += toMint; 50 | 51 | lpStates[productId] = lpState; 52 | 53 | BalanceNormalized memory baseBalance = balances[productId][subaccount] 54 | .balance; 55 | BalanceNormalized memory quoteBalance = balances[QUOTE_PRODUCT_ID][ 56 | subaccount 57 | ].balance; 58 | 59 | _updateBalanceNormalized(base, baseBalance, -amountBase); 60 | _updateBalanceNormalized(quote, quoteBalance, -amountQuote); 61 | 62 | balances[productId][subaccount].balance = baseBalance; 63 | balances[QUOTE_PRODUCT_ID][subaccount].balance = quoteBalance; 64 | states[productId] = base; 65 | states[QUOTE_PRODUCT_ID] = quote; 66 | 67 | _balanceUpdate(productId, subaccount); 68 | _balanceUpdate(QUOTE_PRODUCT_ID, subaccount); 69 | } 70 | 71 | function burnLp( 72 | uint32 productId, 73 | bytes32 subaccount, 74 | int128 amountLp 75 | ) public returns (int128 amountBase, int128 amountQuote) { 76 | _assertInternal(); 77 | require(amountLp > 0, ERR_INVALID_LP_AMOUNT); 78 | 79 | LpState memory lpState = lpStates[productId]; 80 | LpBalance memory lpBalance = balances[productId][subaccount].lpBalance; 81 | State memory base = states[productId]; 82 | State memory quote = states[QUOTE_PRODUCT_ID]; 83 | 84 | if (amountLp == type(int128).max) { 85 | amountLp = lpBalance.amount; 86 | } 87 | if (amountLp == 0) { 88 | return (0, 0); 89 | } 90 | 91 | require(lpBalance.amount >= amountLp, ERR_INSUFFICIENT_LP); 92 | lpBalance.amount -= amountLp; 93 | 94 | amountBase = int128( 95 | (int256(amountLp) * lpState.base.amount) / lpState.supply 96 | ); 97 | amountQuote = int128( 98 | (int256(amountLp) * lpState.quote.amount) / lpState.supply 99 | ); 100 | 101 | _updateBalance(base, lpState.base, -amountBase); 102 | _updateBalance(quote, lpState.quote, -amountQuote); 103 | lpState.supply -= amountLp; 104 | 105 | lpStates[productId] = lpState; 106 | balances[productId][subaccount].lpBalance = lpBalance; 107 | 108 | BalanceNormalized memory baseBalance = balances[productId][subaccount] 109 | .balance; 110 | BalanceNormalized memory quoteBalance = balances[QUOTE_PRODUCT_ID][ 111 | subaccount 112 | ].balance; 113 | 114 | _updateBalanceNormalized(base, baseBalance, amountBase); 115 | _updateBalanceNormalized(quote, quoteBalance, amountQuote); 116 | 117 | balances[productId][subaccount].balance = baseBalance; 118 | balances[QUOTE_PRODUCT_ID][subaccount].balance = quoteBalance; 119 | states[productId] = base; 120 | states[QUOTE_PRODUCT_ID] = quote; 121 | 122 | _balanceUpdate(productId, subaccount); 123 | _balanceUpdate(QUOTE_PRODUCT_ID, subaccount); 124 | } 125 | 126 | function swapLp( 127 | uint32 productId, 128 | int128 baseDelta, 129 | int128 quoteDelta 130 | ) external returns (int128, int128) { 131 | _assertInternal(); 132 | LpState memory lpState = lpStates[productId]; 133 | require( 134 | MathHelper.isSwapValid( 135 | baseDelta, 136 | quoteDelta, 137 | lpState.base.amount, 138 | lpState.quote.amount 139 | ), 140 | ERR_INVALID_MAKER 141 | ); 142 | 143 | int128 baseDepositsMultiplierX18 = states[productId] 144 | .cumulativeDepositsMultiplierX18; 145 | int128 quoteDepositsMultiplierX18 = states[QUOTE_PRODUCT_ID] 146 | .cumulativeDepositsMultiplierX18; 147 | 148 | lpState.base.amount += baseDelta; 149 | lpState.quote.amount += quoteDelta; 150 | lpStates[productId] = lpState; 151 | 152 | states[productId].totalDepositsNormalized += baseDelta.div( 153 | baseDepositsMultiplierX18 154 | ); 155 | states[QUOTE_PRODUCT_ID].totalDepositsNormalized += quoteDelta.div( 156 | quoteDepositsMultiplierX18 157 | ); 158 | _productUpdate(productId); 159 | return (baseDelta, quoteDelta); 160 | } 161 | 162 | function decomposeLps(bytes32 liquidatee, bytes32 liquidator) 163 | external 164 | returns (int128 liquidationFees) 165 | { 166 | uint32[] memory _productIds = getProductIds(); 167 | for (uint128 i = 0; i < _productIds.length; ++i) { 168 | uint32 productId = _productIds[i]; 169 | (, int128 amountQuote) = burnLp( 170 | productId, 171 | liquidatee, 172 | type(int128).max 173 | ); 174 | if (amountQuote != 0) { 175 | int128 rewards = amountQuote.mul( 176 | (ONE - 177 | RiskHelper._getWeightX18( 178 | _risk(productId), 179 | amountQuote, 180 | IProductEngine.HealthType.MAINTENANCE 181 | )) / 50 182 | ); 183 | int128 fees = rewards.mul(LIQUIDATION_FEE_FRACTION); 184 | rewards -= fees; 185 | liquidationFees += fees; 186 | 187 | State memory quote = states[QUOTE_PRODUCT_ID]; 188 | BalanceNormalized memory liquidateeQuote = balances[ 189 | QUOTE_PRODUCT_ID 190 | ][liquidatee].balance; 191 | BalanceNormalized memory liquidatorQuote = balances[ 192 | QUOTE_PRODUCT_ID 193 | ][liquidator].balance; 194 | 195 | _updateBalanceNormalized( 196 | quote, 197 | liquidateeQuote, 198 | -rewards - fees 199 | ); 200 | _updateBalanceNormalized(quote, liquidatorQuote, rewards); 201 | 202 | balances[QUOTE_PRODUCT_ID][liquidatee] 203 | .balance = liquidateeQuote; 204 | balances[QUOTE_PRODUCT_ID][liquidator] 205 | .balance = liquidatorQuote; 206 | states[QUOTE_PRODUCT_ID] = quote; 207 | _balanceUpdate(QUOTE_PRODUCT_ID, liquidator); 208 | _balanceUpdate(QUOTE_PRODUCT_ID, liquidatee); 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /core/contracts/Verifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; 6 | import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; 7 | import "./common/Errors.sol"; 8 | import "./libraries/MathHelper.sol"; 9 | import "./interfaces/IVerifier.sol"; 10 | import "./interfaces/IEndpoint.sol"; 11 | 12 | contract Verifier is EIP712Upgradeable, OwnableUpgradeable, IVerifier { 13 | Point[8] internal pubkeys; 14 | Point[256] internal aggregatePubkey; 15 | bool[256] internal isAggregatePubkeyLatest; 16 | uint256 internal nSigner; 17 | 18 | string internal constant LIQUIDATE_SUBACCOUNT_SIGNATURE = 19 | "LiquidateSubaccount(bytes32 sender,bytes32 liquidatee,uint32 productId,bool isEncodedSpread,int128 amount,uint64 nonce)"; 20 | string internal constant TRANSFER_QUOTE_SIGNATURE = 21 | "TransferQuote(bytes32 sender,bytes32 recipient,uint128 amount,uint64 nonce)"; 22 | string internal constant WITHDRAW_COLLATERAL_SIGNATURE = 23 | "WithdrawCollateral(bytes32 sender,uint32 productId,uint128 amount,uint64 nonce)"; 24 | string internal constant MINT_LP_SIGNATURE = 25 | "MintLp(bytes32 sender,uint32 productId,uint128 amountBase,uint128 quoteAmountLow,uint128 quoteAmountHigh,uint64 nonce)"; 26 | string internal constant BURN_LP_SIGNATURE = 27 | "BurnLp(bytes32 sender,uint32 productId,uint128 amount,uint64 nonce)"; 28 | string internal constant MINT_VLP_SIGNATURE = 29 | "MintVlp(bytes32 sender,uint128 quoteAmount,uint64 nonce)"; 30 | string internal constant BURN_VLP_SIGNATURE = 31 | "BurnVlp(bytes32 sender,uint128 vlpAmount,uint64 nonce)"; 32 | string internal constant LINK_SIGNER_SIGNATURE = 33 | "LinkSigner(bytes32 sender,bytes32 signer,uint64 nonce)"; 34 | 35 | /// @custom:oz-upgrades-unsafe-allow constructor 36 | constructor() { 37 | _disableInitializers(); 38 | } 39 | 40 | function initialize(Point[8] memory initialSet) external initializer { 41 | __Ownable_init(); 42 | for (uint256 i = 0; i < 8; ++i) { 43 | if (!isPointNone(initialSet[i])) { 44 | _assignPubkey(i, initialSet[i].x, initialSet[i].y); 45 | } 46 | } 47 | } 48 | 49 | function revertGasInfo(uint256 i, uint256 gasUsed) external pure { 50 | revert( 51 | string.concat( 52 | "G ", 53 | MathHelper.uint2str(uint128(i)), 54 | " ", 55 | MathHelper.uint2str(uint128(gasUsed)) 56 | ) 57 | ); 58 | } 59 | 60 | function assignPubKey( 61 | uint256 i, 62 | uint256 x, 63 | uint256 y 64 | ) public onlyOwner { 65 | _assignPubkey(i, x, y); 66 | } 67 | 68 | function _assignPubkey( 69 | uint256 i, 70 | uint256 x, 71 | uint256 y 72 | ) internal { 73 | require(i < 8); 74 | if (isPointNone(pubkeys[i])) { 75 | nSigner += 1; 76 | } 77 | pubkeys[i] = Point(x, y); 78 | for (uint256 s = (1 << i); s < 256; s = (s + 1) | (1 << i)) { 79 | isAggregatePubkeyLatest[s] = false; 80 | } 81 | } 82 | 83 | function deletePubkey(uint256 index) public onlyOwner { 84 | if (!isPointNone(pubkeys[index])) { 85 | nSigner -= 1; 86 | delete pubkeys[index]; 87 | } 88 | } 89 | 90 | function getPubkey(uint8 index) public view returns (Point memory) { 91 | return pubkeys[index]; 92 | } 93 | 94 | function getPubkeyAddress(uint8 index) public view returns (address) { 95 | Point memory p = getPubkey(index); 96 | return address(uint160(uint256(keccak256(abi.encode(p.x, p.y))))); 97 | } 98 | 99 | function getAggregatePubkey(uint8 signerBitmask) 100 | internal 101 | returns (Point memory) 102 | { 103 | if (signerBitmask == 0 || isAggregatePubkeyLatest[signerBitmask]) 104 | return aggregatePubkey[signerBitmask]; 105 | Point memory res; 106 | for (uint256 i = 0; i < 8; ++i) { 107 | if ((signerBitmask >> i) % 2 == 1) { 108 | require(!isPointNone(pubkeys[i])); 109 | res = pointAdd( 110 | getAggregatePubkey(signerBitmask ^ uint8(1 << i)), 111 | pubkeys[i] 112 | ); 113 | break; 114 | } 115 | } 116 | aggregatePubkey[signerBitmask] = res; 117 | isAggregatePubkeyLatest[signerBitmask] = true; 118 | return res; 119 | } 120 | 121 | // determine if 2/3 of the signers are included in this signing mask 122 | // and if the keys are present 123 | function checkQuorum(uint8 signerBitmask) internal view returns (bool) { 124 | uint256 nSigned = 0; 125 | for (uint256 i = 0; i < 8; ++i) { 126 | bool signed = ((signerBitmask >> i) & 1) == 1; 127 | if (signed) { 128 | if (isPointNone(pubkeys[i])) { 129 | return false; 130 | } 131 | nSigned += 1; 132 | } 133 | } 134 | return nSigned * 2 > nSigner; 135 | } 136 | 137 | function requireValidSignature( 138 | bytes32 message, 139 | bytes32 e, 140 | bytes32 s, 141 | uint8 signerBitmask 142 | ) public { 143 | require(checkQuorum(signerBitmask)); 144 | Point memory pubkey = getAggregatePubkey(signerBitmask); 145 | require( 146 | verify( 147 | pubkey.y % 2 == 0 ? 27 : 28, 148 | bytes32(pubkey.x), 149 | message, 150 | e, 151 | s 152 | ), 153 | "Verification failed" 154 | ); 155 | } 156 | 157 | /// SCHNORR IMPLEMENTATION BELOW 158 | // secp256k1 group order 159 | uint256 public constant Q = 160 | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; 161 | 162 | // parity := public key y-coord parity (27 or 28) 163 | // px := public key x-coord 164 | // message := 32-byte message 165 | // e := schnorr signature challenge 166 | // s := schnorr signature 167 | function verify( 168 | uint8 parity, 169 | bytes32 px, 170 | bytes32 message, 171 | bytes32 e, 172 | bytes32 s 173 | ) internal pure returns (bool) { 174 | // ecrecover = (m, v, r, s); 175 | bytes32 sp = bytes32(Q - mulmod(uint256(s), uint256(px), Q)); 176 | bytes32 ep = bytes32(Q - mulmod(uint256(e), uint256(px), Q)); 177 | 178 | require(sp != 0); 179 | // the ecrecover precompile implementation checks that the `r` and `s` 180 | // inputs are non-zero (in this case, `px` and `ep`), thus we don't need to 181 | // check if they're zero. 182 | address R = ecrecover(sp, parity, px, ep); 183 | require(R != address(0), "ecrecover failed"); 184 | return e == keccak256(abi.encodePacked(R, uint8(parity), px, message)); 185 | } 186 | 187 | uint256 public constant _P = 188 | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F; 189 | 190 | struct Point { 191 | uint256 x; 192 | uint256 y; 193 | } 194 | 195 | function pow( 196 | uint256 a, 197 | uint256 b, 198 | uint256 mod 199 | ) internal pure returns (uint256) { 200 | // a ^ b % mod 201 | uint256 res = 1; 202 | while (b > 0) { 203 | if (b % 2 == 1) { 204 | res = mulmod(res, a, mod); 205 | } 206 | a = mulmod(a, a, mod); 207 | b /= 2; 208 | } 209 | return res; 210 | } 211 | 212 | function isPointNone(Point memory u) internal pure returns (bool) { 213 | return u.x == 0 && u.y == 0; 214 | } 215 | 216 | function pointAdd(Point memory u, Point memory v) 217 | internal 218 | pure 219 | returns (Point memory) 220 | { 221 | if (isPointNone(u)) return v; 222 | if (isPointNone(v)) return u; 223 | uint256 lam = 0; 224 | if (u.x == v.x) { 225 | if (u.y != v.y) return Point(0, 0); 226 | lam = mulmod(3, u.x, _P); 227 | lam = mulmod(lam, u.x, _P); 228 | lam = mulmod(lam, pow(mulmod(2, v.y, _P), _P - 2, _P), _P); 229 | } else { 230 | lam = mulmod( 231 | addmod(v.y, _P - u.y, _P), 232 | pow(addmod(v.x, _P - u.x, _P), _P - 2, _P), 233 | _P 234 | ); 235 | } 236 | uint256 x3 = mulmod(lam, lam, _P); 237 | x3 = addmod(x3, _P - u.x, _P); 238 | x3 = addmod(x3, _P - v.x, _P); 239 | uint256 y3 = addmod(u.x, _P - x3, _P); 240 | y3 = mulmod(y3, lam, _P); 241 | y3 = addmod(y3, _P - u.y, _P); 242 | return Point(x3, y3); 243 | } 244 | 245 | function checkIndividualSignature( 246 | bytes32 digest, 247 | bytes memory signature, 248 | uint8 signerIndex 249 | ) public view returns (bool) { 250 | address expectedAddress = getPubkeyAddress(signerIndex); 251 | address recovered = ECDSA.recover(digest, signature); 252 | return expectedAddress == recovered; 253 | } 254 | 255 | function requireValidTxSignatures( 256 | bytes calldata txn, 257 | uint64 idx, 258 | bytes[] calldata signatures 259 | ) public view { 260 | bytes32 data = keccak256( 261 | abi.encodePacked(uint256(block.chainid), uint256(idx), txn) 262 | ); 263 | bytes32 hashedMsg = keccak256( 264 | abi.encodePacked("\x19Ethereum Signed Message:\n32", data) 265 | ); 266 | 267 | uint256 nSignatures = 0; 268 | for (uint256 i = 0; i < signatures.length; i++) { 269 | if (signatures[i].length > 0) { 270 | nSignatures += 1; 271 | require( 272 | checkIndividualSignature( 273 | hashedMsg, 274 | signatures[i], 275 | uint8(i) 276 | ), 277 | "invalid signature" 278 | ); 279 | } 280 | } 281 | require(nSignatures == nSigner, "not enough signatures"); 282 | } 283 | 284 | function validateSignature( 285 | bytes32 sender, 286 | address linkedSigner, 287 | bytes32 digest, 288 | bytes memory signature 289 | ) public pure { 290 | address recovered = ECDSA.recover(digest, signature); 291 | require( 292 | (recovered != address(0)) && 293 | ((recovered == address(uint160(bytes20(sender)))) || 294 | (recovered == linkedSigner)), 295 | ERR_INVALID_SIGNATURE 296 | ); 297 | } 298 | 299 | function computeDigest( 300 | IEndpoint.TransactionType txType, 301 | bytes calldata transactionBody 302 | ) public pure returns (bytes32) { 303 | bytes32 digest; 304 | 305 | if (txType == IEndpoint.TransactionType.LiquidateSubaccount) { 306 | IEndpoint.SignedLiquidateSubaccount memory signedTx = abi.decode( 307 | transactionBody, 308 | (IEndpoint.SignedLiquidateSubaccount) 309 | ); 310 | digest = keccak256( 311 | abi.encode( 312 | keccak256(bytes(LIQUIDATE_SUBACCOUNT_SIGNATURE)), 313 | signedTx.tx.sender, 314 | signedTx.tx.liquidatee, 315 | signedTx.tx.productId, 316 | signedTx.tx.isEncodedSpread, 317 | signedTx.tx.amount, 318 | signedTx.tx.nonce 319 | ) 320 | ); 321 | } else if (txType == IEndpoint.TransactionType.WithdrawCollateral) { 322 | IEndpoint.SignedWithdrawCollateral memory signedTx = abi.decode( 323 | transactionBody, 324 | (IEndpoint.SignedWithdrawCollateral) 325 | ); 326 | digest = keccak256( 327 | abi.encode( 328 | keccak256(bytes(WITHDRAW_COLLATERAL_SIGNATURE)), 329 | signedTx.tx.sender, 330 | signedTx.tx.productId, 331 | signedTx.tx.amount, 332 | signedTx.tx.nonce 333 | ) 334 | ); 335 | } else if (txType == IEndpoint.TransactionType.MintLp) { 336 | IEndpoint.SignedMintLp memory signedTx = abi.decode( 337 | transactionBody, 338 | (IEndpoint.SignedMintLp) 339 | ); 340 | digest = keccak256( 341 | abi.encode( 342 | keccak256(bytes(MINT_LP_SIGNATURE)), 343 | signedTx.tx.sender, 344 | signedTx.tx.productId, 345 | signedTx.tx.amountBase, 346 | signedTx.tx.quoteAmountLow, 347 | signedTx.tx.quoteAmountHigh, 348 | signedTx.tx.nonce 349 | ) 350 | ); 351 | } else if (txType == IEndpoint.TransactionType.BurnLp) { 352 | IEndpoint.SignedBurnLp memory signedTx = abi.decode( 353 | transactionBody, 354 | (IEndpoint.SignedBurnLp) 355 | ); 356 | digest = keccak256( 357 | abi.encode( 358 | keccak256(bytes(BURN_LP_SIGNATURE)), 359 | signedTx.tx.sender, 360 | signedTx.tx.productId, 361 | signedTx.tx.amount, 362 | signedTx.tx.nonce 363 | ) 364 | ); 365 | } else if (txType == IEndpoint.TransactionType.MintVlp) { 366 | IEndpoint.SignedMintVlp memory signedTx = abi.decode( 367 | transactionBody, 368 | (IEndpoint.SignedMintVlp) 369 | ); 370 | digest = keccak256( 371 | abi.encode( 372 | keccak256(bytes(MINT_VLP_SIGNATURE)), 373 | signedTx.tx.sender, 374 | signedTx.tx.quoteAmount, 375 | signedTx.tx.nonce 376 | ) 377 | ); 378 | } else if (txType == IEndpoint.TransactionType.BurnVlp) { 379 | IEndpoint.SignedBurnVlp memory signedTx = abi.decode( 380 | transactionBody, 381 | (IEndpoint.SignedBurnVlp) 382 | ); 383 | digest = keccak256( 384 | abi.encode( 385 | keccak256(bytes(BURN_VLP_SIGNATURE)), 386 | signedTx.tx.sender, 387 | signedTx.tx.vlpAmount, 388 | signedTx.tx.nonce 389 | ) 390 | ); 391 | } else if (txType == IEndpoint.TransactionType.LinkSigner) { 392 | IEndpoint.SignedLinkSigner memory signedTx = abi.decode( 393 | transactionBody, 394 | (IEndpoint.SignedLinkSigner) 395 | ); 396 | digest = keccak256( 397 | abi.encode( 398 | keccak256(bytes(LINK_SIGNER_SIGNATURE)), 399 | signedTx.tx.sender, 400 | signedTx.tx.signer, 401 | signedTx.tx.nonce 402 | ) 403 | ); 404 | } else if (txType == IEndpoint.TransactionType.TransferQuote) { 405 | IEndpoint.SignedTransferQuote memory signedTx = abi.decode( 406 | transactionBody, 407 | (IEndpoint.SignedTransferQuote) 408 | ); 409 | digest = keccak256( 410 | abi.encode( 411 | keccak256(bytes(TRANSFER_QUOTE_SIGNATURE)), 412 | signedTx.tx.sender, 413 | signedTx.tx.recipient, 414 | signedTx.tx.amount, 415 | signedTx.tx.nonce 416 | ) 417 | ); 418 | } else { 419 | revert(); 420 | } 421 | 422 | return digest; 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /core/contracts/VertexGateway.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; 6 | import "./Endpoint.sol"; 7 | import "./interfaces/IOffchainExchange.sol"; 8 | import "./interfaces/clearinghouse/IClearinghouse.sol"; 9 | import "./common/Errors.sol"; 10 | import "./common/Constants.sol"; 11 | import "./libraries/ERC20Helper.sol"; 12 | import "./libraries/MathHelper.sol"; 13 | import "./libraries/RippleBase58.sol"; 14 | import "./interfaces/engine/ISpotEngine.sol"; 15 | import "./interfaces/engine/IPerpEngine.sol"; 16 | import "./interfaces/IEndpoint.sol"; 17 | import "./interfaces/IERC20Base.sol"; 18 | 19 | import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; 20 | import {IInterchainTokenService} from "@axelar-network/interchain-token-service/contracts/interfaces/IInterchainTokenService.sol"; 21 | 22 | contract VertexGateway is EIP712Upgradeable, OwnableUpgradeable { 23 | bytes32 internal constant EXECUTE_SUCCESS = 24 | keccak256("its-execute-success"); 25 | using ERC20Helper for IERC20Base; 26 | using RippleBase58 for bytes; 27 | 28 | struct Config { 29 | bytes32 tokenId; 30 | address token; 31 | } 32 | 33 | address internal endpoint; 34 | address internal axelarGateway; 35 | address internal axelarGasService; 36 | address payable internal interchainTokenService; 37 | string public sourceChain; 38 | 39 | mapping(uint32 => Config) public configs; 40 | mapping(address => bytes32) internal tokenIds; 41 | mapping(address => bytes) public rippleAddresses; 42 | 43 | /// @custom:oz-upgrades-unsafe-allow constructor 44 | constructor() { 45 | _disableInitializers(); 46 | } 47 | 48 | function initialize( 49 | address _endpoint, 50 | address _axelarGateway, 51 | address _axelarGasService, 52 | address payable _interchainTokenService, 53 | string calldata _sourceChain 54 | ) external initializer { 55 | __Ownable_init(); 56 | endpoint = _endpoint; 57 | axelarGateway = _axelarGateway; 58 | axelarGasService = _axelarGasService; 59 | interchainTokenService = _interchainTokenService; 60 | sourceChain = _sourceChain; 61 | } 62 | 63 | modifier onlyIts() { 64 | require( 65 | msg.sender == interchainTokenService, 66 | "Not InterchainTokenService" 67 | ); 68 | _; 69 | } 70 | 71 | function equal(string memory a, string memory b) 72 | internal 73 | pure 74 | returns (bool) 75 | { 76 | return 77 | bytes(a).length == bytes(b).length && 78 | keccak256(bytes(a)) == keccak256(bytes(b)); 79 | } 80 | 81 | function addProduct( 82 | uint32 productId, 83 | bytes32 tokenId, 84 | address token 85 | ) external onlyOwner { 86 | configs[productId] = Config({tokenId: tokenId, token: token}); 87 | tokenIds[token] = tokenId; 88 | } 89 | 90 | function execute( 91 | bytes32 commandId, 92 | string calldata _sourceChain, 93 | string calldata sourceAddress, 94 | bytes calldata payload 95 | ) external { 96 | require(equal(sourceChain, _sourceChain), "Not authored sourceChain"); 97 | bytes32 payloadHash = keccak256(payload); 98 | require( 99 | IAxelarGateway(axelarGateway).validateContractCall( 100 | commandId, 101 | _sourceChain, 102 | sourceAddress, 103 | payloadHash 104 | ), 105 | "Not approved by gateway." 106 | ); 107 | address sender = bytes(sourceAddress).decodeFromRippleAddress(); 108 | bytes12 subaccountName; 109 | address signer; 110 | (subaccountName, signer) = abi.decode(payload, (bytes12, address)); 111 | bytes32 subaccount = bytes32(abi.encodePacked(sender, subaccountName)); 112 | 113 | linkSigner(subaccount, signer); 114 | } 115 | 116 | function executeWithInterchainToken( 117 | bytes32 commandId, 118 | string calldata _sourceChain, 119 | bytes calldata sourceAddress, 120 | bytes calldata payload, 121 | bytes32 tokenId, 122 | address token, 123 | uint256 amount 124 | ) external onlyIts returns (bytes32) { 125 | require(equal(sourceChain, _sourceChain), "Not authored sourceChain"); 126 | address sender = sourceAddress.decodeFromRippleAddress(); 127 | rippleAddresses[sender] = sourceAddress; 128 | bytes12 subaccountName; 129 | address signer; 130 | uint32 productId; 131 | (subaccountName, productId, signer) = abi.decode( 132 | payload, 133 | (bytes12, uint32, address) 134 | ); 135 | bytes32 subaccount = bytes32(abi.encodePacked(sender, subaccountName)); 136 | require(configs[productId].token == token, "product mismatched"); 137 | IERC20Base(token).approve(endpoint, amount); 138 | IEndpoint(endpoint).depositCollateralWithReferral( 139 | subaccount, 140 | productId, 141 | uint128(amount), 142 | "VertexGateway" 143 | ); 144 | 145 | if (signer != address(0)) { 146 | linkSigner(subaccount, signer); 147 | } 148 | 149 | return EXECUTE_SUCCESS; 150 | } 151 | 152 | function linkSigner(bytes32 sender, address signer) internal { 153 | bytes32 signerSubaccount = bytes32(uint256(uint160(signer)) << 96); 154 | IEndpoint.LinkSigner memory linkSigner = IEndpoint.LinkSigner( 155 | sender, 156 | signerSubaccount, 157 | IEndpoint(endpoint).getNonce(signer) 158 | ); 159 | bytes memory linkSignerTx = abi.encodePacked( 160 | uint8(19), 161 | abi.encode(linkSigner) 162 | ); 163 | Endpoint(endpoint).submitSlowModeTransaction(linkSignerTx); 164 | } 165 | 166 | function isNativeWallet(address wallet) external view returns (bool) { 167 | return rippleAddresses[wallet].length == 0; 168 | } 169 | 170 | function withdraw( 171 | IERC20Base token, 172 | address to, 173 | uint256 amount 174 | ) external { 175 | token.safeTransferFrom(msg.sender, address(this), amount); 176 | token.approve(interchainTokenService, amount); 177 | bytes32 tokenId = tokenIds[address(token)]; 178 | bytes memory rippleAddress = rippleAddresses[to]; 179 | IInterchainTokenService(interchainTokenService).interchainTransfer{ 180 | value: uint256(uint128(ONE)) 181 | }( 182 | tokenId, 183 | sourceChain, 184 | rippleAddress, 185 | amount, 186 | "", 187 | uint256(uint128(ONE)) 188 | ); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /core/contracts/WithdrawPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; 6 | import "./libraries/MathHelper.sol"; 7 | import "./interfaces/IEndpoint.sol"; 8 | import "./Verifier.sol"; 9 | import "./interfaces/engine/ISpotEngine.sol"; 10 | import "./interfaces/IERC20Base.sol"; 11 | import "./libraries/ERC20Helper.sol"; 12 | import "./common/Constants.sol"; 13 | import "./BaseWithdrawPool.sol"; 14 | 15 | contract WithdrawPool is BaseWithdrawPool { 16 | function initialize(address _clearinghouse, address _verifier) external { 17 | _initialize(_clearinghouse, _verifier); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/contracts/WithdrawPoolRipple.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 5 | import "./libraries/MathHelper.sol"; 6 | import "./interfaces/IEndpoint.sol"; 7 | import "./Verifier.sol"; 8 | import "./interfaces/engine/ISpotEngine.sol"; 9 | import "./interfaces/IERC20Base.sol"; 10 | import "./libraries/ERC20Helper.sol"; 11 | import "./common/Constants.sol"; 12 | import "./BaseWithdrawPool.sol"; 13 | import "./VertexGateway.sol"; 14 | 15 | contract WithdrawPoolRipple is BaseWithdrawPool { 16 | using ERC20Helper for IERC20Base; 17 | address internal vertexGateway; 18 | 19 | function initialize( 20 | address _clearinghouse, 21 | address _verifier, 22 | address _vertexGateway 23 | ) external { 24 | _initialize(_clearinghouse, _verifier); 25 | vertexGateway = _vertexGateway; 26 | } 27 | 28 | function handleWithdrawTransfer( 29 | IERC20Base token, 30 | address to, 31 | uint128 amount 32 | ) internal override { 33 | if (VertexGateway(vertexGateway).isNativeWallet(to)) { 34 | token.safeTransfer(to, uint256(amount)); 35 | } else { 36 | token.approve(vertexGateway, uint256(amount)); 37 | VertexGateway(vertexGateway).withdraw(token, to, uint256(amount)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/contracts/common/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | /// @dev Each clearinghouse has a unique quote product 5 | uint32 constant QUOTE_PRODUCT_ID = 0; 6 | 7 | /// @dev Fees account 8 | bytes32 constant FEES_ACCOUNT = bytes32(0); 9 | bytes32 constant X_ACCOUNT = 0x0000000000000000000000000000000000000000000000000000000000000001; 10 | bytes32 constant V_ACCOUNT = 0x0000000000000000000000000000000000000000000000000000000000000002; 11 | 12 | string constant DEFAULT_REFERRAL_CODE = "-1"; 13 | 14 | uint128 constant MINIMUM_LIQUIDITY = 10**3; 15 | 16 | int128 constant ONE = 10**18; 17 | 18 | uint8 constant MAX_DECIMALS = 18; 19 | 20 | int128 constant TAKER_SEQUENCER_FEE = 0; // $0.00 21 | 22 | int128 constant SLOW_MODE_FEE = 1000000; // $1 23 | 24 | int128 constant FAST_WITHDRAWAL_FEE_RATE = 1_000_000_000_000_000; // 0.1% 25 | 26 | int128 constant LIQUIDATION_FEE = 1e18; // $1 27 | int128 constant HEALTHCHECK_FEE = 1e18; // $1 28 | 29 | uint128 constant INT128_MAX = uint128(type(int128).max); 30 | 31 | uint64 constant SECONDS_PER_DAY = 3600 * 24; 32 | 33 | uint32 constant VRTX_PRODUCT_ID = 41; 34 | 35 | int128 constant LIQUIDATION_FEE_FRACTION = 500_000_000_000_000_000; // 50% 36 | 37 | int128 constant INTEREST_FEE_FRACTION = 200_000_000_000_000_000; // 20% 38 | 39 | int256 constant MIN_DEPOSIT_AMOUNT = 5 * ONE; 40 | 41 | uint32 constant MAX_ISOLATED_SUBACCOUNTS_PER_ADDRESS = 10; 42 | 43 | uint32 constant VLP_PRODUCT_ID = 153; 44 | 45 | uint96 constant MASK_6_BYTES = 0xFFFFFFFFFFFF000000000000; 46 | -------------------------------------------------------------------------------- /core/contracts/common/Errors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | // Trying to take an action on vertex when 5 | string constant ERR_REQUIRES_DEPOSIT = "RS"; 6 | 7 | // ERC20 Transfer failed 8 | string constant ERR_TRANSFER_FAILED = "TF"; 9 | 10 | // Unauthorized 11 | string constant ERR_UNAUTHORIZED = "U"; 12 | 13 | // Invalid product 14 | string constant ERR_INVALID_PRODUCT = "IP"; 15 | 16 | // Subaccount health too low 17 | string constant ERR_SUBACCT_HEALTH = "SH"; 18 | 19 | // Not liquidatable 20 | string constant ERR_NOT_LIQUIDATABLE = "NL"; 21 | 22 | // Liquidator health too low 23 | string constant ERR_NOT_LIQUIDATABLE_INITIAL = "NLI"; 24 | 25 | // Liquidatee has positive initial health 26 | string constant ERR_LIQUIDATED_TOO_MUCH = "LTM"; 27 | 28 | // Trying to liquidate quote, or 29 | string constant ERR_INVALID_LIQUIDATION_PARAMS = "NILP"; 30 | 31 | // Trying to liquidate perp but the amount is not divisible by sizeIncrement 32 | string constant ERR_INVALID_LIQUIDATION_AMOUNT = "NILA"; 33 | 34 | // Tried to liquidate too little, too much or signs are different 35 | string constant ERR_NOT_LIQUIDATABLE_AMT = "NLA"; 36 | 37 | // Tried to liquidate liabilities before perps 38 | string constant ERR_NOT_LIQUIDATABLE_LIABILITIES = "NLL"; 39 | 40 | // Tried to finalize subaccount that cannot be finalized 41 | string constant ERR_NOT_FINALIZABLE_SUBACCOUNT = "NFS"; 42 | 43 | // Not enough quote to settle 44 | string constant ERR_CANNOT_SETTLE = "CS"; 45 | 46 | // Not enough insurance to settle 47 | string constant ERR_NO_INSURANCE = "NI"; 48 | 49 | // Above reserve ratio 50 | string constant ERR_RESERVE_RATIO = "RR"; 51 | 52 | // Invalid socialize amount 53 | string constant ERR_INVALID_SOCIALIZE_AMT = "ISA"; 54 | 55 | // Socializing product with no open interest 56 | string constant ERR_NO_OPEN_INTEREST = "NOI"; 57 | 58 | // FOK not filled, this isn't rly an error so this is jank 59 | string constant ERR_FOK_NOT_FILLED = "ENF"; 60 | 61 | // bad product config via weights 62 | string constant ERR_BAD_PRODUCT_CONFIG = "BPC"; 63 | 64 | // subacct name too long 65 | string constant ERR_LONG_NAME = "LN"; 66 | 67 | // already registered in health group 68 | string constant ERR_ALREADY_REGISTERED = "AR"; 69 | 70 | // invalid health group provided 71 | string constant ERR_INVALID_HEALTH_GROUP = "IHG"; 72 | 73 | string constant ERR_GETTING_ZERO_HEALTH_GROUP = "GZHG"; 74 | 75 | // trying to burn more LP than owned 76 | string constant ERR_INSUFFICIENT_LP = "ILP"; 77 | 78 | // taker order subaccount fails risk or is invalid 79 | string constant ERR_INVALID_TAKER = "IT"; 80 | 81 | // maker order subaccount fails risk or is invalid 82 | string constant ERR_INVALID_MAKER = "IM"; 83 | 84 | string constant ERR_INVALID_SIGNATURE = "IS"; 85 | 86 | string constant ERR_ORDERS_CANNOT_BE_MATCHED = "OCBM"; 87 | 88 | string constant ERR_INVALID_LP_AMOUNT = "ILA"; 89 | 90 | string constant ERR_SLIPPAGE_TOO_HIGH = "STH"; 91 | 92 | string constant ERR_SUBACCOUNT_NOT_FOUND = "SNF"; 93 | 94 | string constant ERR_INVALID_PRICE = "IPR"; 95 | 96 | string constant ERR_INVALID_TIME = "ITI"; 97 | 98 | // states on node and engine are not same 99 | string constant ERR_DSYNC = "DSYNC"; 100 | 101 | string constant ERR_INVALID_SWAP_PARAMS = "ISP"; 102 | 103 | string constant ERR_CONVERSION_OVERFLOW = "CO"; 104 | 105 | string constant ERR_ONLY_CLEARINGHOUSE_CAN_SET_BOOK = "OCCSB"; 106 | 107 | // we match on containing these strings in sequencer 108 | string constant ERR_INVALID_SUBMISSION_INDEX = "IX"; 109 | string constant ERR_NO_SLOW_MODE_TXS_REMAINING = "no slow mode transactions remaining"; 110 | string constant ERR_INVALID_COUNT = "IC"; 111 | string constant ERR_SLOW_TX_TOO_RECENT = "STTR"; 112 | string constant ERR_WALLET_NOT_TRANSFERABLE = "WNT"; 113 | 114 | string constant ERR_WALLET_SANCTIONED = "WS"; 115 | 116 | string constant ERR_SLOW_MODE_WRONG_SENDER = "SMWS"; 117 | string constant ERR_WRONG_NONCE = "WN"; 118 | 119 | // initially wanted to call this 120 | // ERR_FULL_UTILIZATION but the shortened 121 | // error string may make people mad on the frontend 122 | string constant ERR_MAX_UTILIZATION = "MU"; 123 | 124 | string constant ERR_INVALID_RISK_GROUP = "IRG"; 125 | 126 | string constant ERR_VERIFY_SCHNORR = "VSR"; 127 | 128 | string constant ERR_DEPOSIT_TOO_SMALL = "DTS"; 129 | 130 | string constant ERR_CODE_NOT_MATCH = "CNM"; 131 | 132 | string constant ERR_INVALID_HOLDER_LIST = "IHL"; 133 | -------------------------------------------------------------------------------- /core/contracts/interfaces/IArbAirdrop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IArbAirdrop { 5 | event ClaimArb(address indexed account, uint32 week, uint256 amount); 6 | 7 | struct ClaimProof { 8 | uint32 week; 9 | uint256 totalAmount; 10 | bytes32[] proof; 11 | } 12 | 13 | function claim(ClaimProof[] calldata claimProofs) external; 14 | 15 | function getClaimed(address account) 16 | external 17 | view 18 | returns (uint256[] memory); 19 | } 20 | -------------------------------------------------------------------------------- /core/contracts/interfaces/IERC20Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IERC20Base { 5 | function decimals() external view returns (uint8); 6 | 7 | /** 8 | * @dev Moves `amount` tokens from the caller's account to `to`. 9 | * 10 | * Returns a boolean value indicating whether the operation succeeded. 11 | * 12 | * Emits a {Transfer} event. 13 | */ 14 | function transfer(address to, uint256 amount) external returns (bool); 15 | 16 | /** 17 | * @dev Moves `amount` tokens from `from` to `to` using the 18 | * allowance mechanism. `amount` is then deducted from the caller's 19 | * allowance. 20 | * 21 | * Returns a boolean value indicating whether the operation succeeded. 22 | * 23 | * Emits a {Transfer} event. 24 | */ 25 | function transferFrom( 26 | address from, 27 | address to, 28 | uint256 amount 29 | ) external returns (bool); 30 | 31 | function increaseAllowance(address spender, uint256 addedValue) 32 | external 33 | returns (bool); 34 | 35 | function decreaseAllowance(address spender, uint256 subtractedValue) 36 | external 37 | returns (bool); 38 | 39 | function balanceOf(address account) external view returns (uint256); 40 | 41 | function approve(address spender, uint256 value) external returns (bool); 42 | } 43 | -------------------------------------------------------------------------------- /core/contracts/interfaces/IEndpoint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./clearinghouse/IClearinghouse.sol"; 5 | 6 | interface IEndpoint { 7 | event SubmitTransactions(); 8 | 9 | // events that we parse transactions into 10 | enum TransactionType { 11 | LiquidateSubaccount, 12 | DepositCollateral, 13 | WithdrawCollateral, 14 | SpotTick, 15 | UpdatePrice, 16 | SettlePnl, 17 | MatchOrders, 18 | DepositInsurance, 19 | ExecuteSlowMode, 20 | MintLp, 21 | BurnLp, 22 | SwapAMM, 23 | MatchOrderAMM, 24 | DumpFees, 25 | ClaimSequencerFees, // deprecated 26 | PerpTick, 27 | ManualAssert, 28 | Rebate, // deprecated 29 | UpdateProduct, 30 | LinkSigner, 31 | UpdateFeeRates, 32 | BurnLpAndTransfer, 33 | MatchOrdersRFQ, 34 | TransferQuote, 35 | RebalanceXWithdraw, 36 | UpdateMinDepositRate, 37 | AssertCode, 38 | WithdrawInsurance, 39 | CreateIsolatedSubaccount, 40 | DelistProduct, 41 | MintVlp, 42 | BurnVlp, 43 | RebalanceVlp 44 | } 45 | 46 | struct UpdateProduct { 47 | address engine; 48 | bytes tx; 49 | } 50 | 51 | /// requires signature from sender 52 | enum LiquidationMode { 53 | SPREAD, 54 | SPOT, 55 | PERP 56 | } 57 | 58 | struct LegacyLiquidateSubaccount { 59 | bytes32 sender; 60 | bytes32 liquidatee; 61 | uint8 mode; 62 | uint32 healthGroup; 63 | int128 amount; 64 | uint64 nonce; 65 | } 66 | 67 | struct LiquidateSubaccount { 68 | bytes32 sender; 69 | bytes32 liquidatee; 70 | uint32 productId; 71 | bool isEncodedSpread; 72 | int128 amount; 73 | uint64 nonce; 74 | } 75 | 76 | struct LegacySignedLiquidateSubaccount { 77 | LegacyLiquidateSubaccount tx; 78 | bytes signature; 79 | } 80 | 81 | struct SignedLiquidateSubaccount { 82 | LiquidateSubaccount tx; 83 | bytes signature; 84 | } 85 | 86 | struct DepositCollateral { 87 | bytes32 sender; 88 | uint32 productId; 89 | uint128 amount; 90 | } 91 | 92 | struct SignedDepositCollateral { 93 | DepositCollateral tx; 94 | bytes signature; 95 | } 96 | 97 | struct WithdrawCollateral { 98 | bytes32 sender; 99 | uint32 productId; 100 | uint128 amount; 101 | uint64 nonce; 102 | } 103 | 104 | struct SignedWithdrawCollateral { 105 | WithdrawCollateral tx; 106 | bytes signature; 107 | } 108 | 109 | struct MintLp { 110 | bytes32 sender; 111 | uint32 productId; 112 | uint128 amountBase; 113 | uint128 quoteAmountLow; 114 | uint128 quoteAmountHigh; 115 | uint64 nonce; 116 | } 117 | 118 | struct SignedMintLp { 119 | MintLp tx; 120 | bytes signature; 121 | } 122 | 123 | struct BurnLp { 124 | bytes32 sender; 125 | uint32 productId; 126 | uint128 amount; 127 | uint64 nonce; 128 | } 129 | 130 | struct SignedBurnLp { 131 | BurnLp tx; 132 | bytes signature; 133 | } 134 | 135 | struct MintVlp { 136 | bytes32 sender; 137 | uint128 quoteAmount; 138 | uint64 nonce; 139 | } 140 | 141 | struct SignedMintVlp { 142 | MintVlp tx; 143 | bytes signature; 144 | int128 oraclePriceX18; 145 | } 146 | 147 | struct BurnVlp { 148 | bytes32 sender; 149 | uint128 vlpAmount; 150 | uint64 nonce; 151 | } 152 | 153 | struct SignedBurnVlp { 154 | BurnVlp tx; 155 | bytes signature; 156 | int128 oraclePriceX18; 157 | } 158 | 159 | struct RebalanceVlp { 160 | uint32 productId; 161 | int128 baseAmount; 162 | int128 quoteAmount; 163 | } 164 | 165 | struct LinkSigner { 166 | bytes32 sender; 167 | bytes32 signer; 168 | uint64 nonce; 169 | } 170 | 171 | struct SignedLinkSigner { 172 | LinkSigner tx; 173 | bytes signature; 174 | } 175 | 176 | /// callable by endpoint; no signature verifications needed 177 | struct PerpTick { 178 | uint128 time; 179 | int128[] avgPriceDiffs; 180 | } 181 | 182 | struct LegacySpotTick { 183 | uint128 time; 184 | } 185 | 186 | struct SpotTick { 187 | uint128 time; 188 | // utilization ratio across all chains 189 | int128[] utilizationRatiosX18; 190 | } 191 | 192 | struct ManualAssert { 193 | int128[] openInterests; 194 | int128[] totalDeposits; 195 | int128[] totalBorrows; 196 | } 197 | 198 | struct AssertCode { 199 | string[] contractNames; 200 | bytes32[] codeHashes; 201 | } 202 | 203 | struct WithdrawInsurance { 204 | uint128 amount; 205 | address sendTo; 206 | } 207 | 208 | struct DelistProduct { 209 | uint32 productId; 210 | int128 priceX18; 211 | bytes32[] subaccounts; 212 | } 213 | 214 | struct Rebate { 215 | bytes32[] subaccounts; 216 | int128[] amounts; 217 | } 218 | 219 | struct UpdateFeeRates { 220 | address user; 221 | uint32 productId; 222 | // the absolute value of fee rates can't be larger than 100%, 223 | // so their X18 values are in the range [-1e18, 1e18], which 224 | // can be stored by using int64. 225 | int64 makerRateX18; 226 | int64 takerRateX18; 227 | } 228 | 229 | struct ClaimSequencerFees { 230 | bytes32 subaccount; 231 | } 232 | 233 | struct RebalanceXWithdraw { 234 | uint32 productId; 235 | uint128 amount; 236 | address sendTo; 237 | } 238 | 239 | struct UpdateMinDepositRate { 240 | uint32 productId; 241 | int128 minDepositRateX18; 242 | } 243 | 244 | struct UpdatePrice { 245 | uint32 productId; 246 | int128 priceX18; 247 | } 248 | 249 | struct SettlePnl { 250 | bytes32[] subaccounts; 251 | uint256[] productIds; 252 | } 253 | 254 | /// matching 255 | struct Order { 256 | bytes32 sender; 257 | int128 priceX18; 258 | int128 amount; 259 | uint64 expiration; 260 | uint64 nonce; 261 | } 262 | 263 | struct SignedOrder { 264 | Order order; 265 | bytes signature; 266 | } 267 | 268 | struct LegacyMatchOrders { 269 | uint32 productId; 270 | bool amm; 271 | SignedOrder taker; 272 | SignedOrder maker; 273 | } 274 | 275 | struct MatchOrders { 276 | uint32 productId; 277 | SignedOrder taker; 278 | SignedOrder maker; 279 | } 280 | 281 | struct MatchOrdersWithSigner { 282 | MatchOrders matchOrders; 283 | address takerLinkedSigner; 284 | address makerLinkedSigner; 285 | } 286 | 287 | // just swap against AMM -- theres no maker order 288 | struct MatchOrderAMM { 289 | uint32 productId; 290 | int128 baseDelta; 291 | int128 quoteDelta; 292 | SignedOrder taker; 293 | } 294 | 295 | struct SwapAMM { 296 | bytes32 sender; 297 | uint32 productId; 298 | int128 amount; 299 | int128 priceX18; 300 | } 301 | 302 | struct DepositInsurance { 303 | uint128 amount; 304 | } 305 | 306 | struct SlowModeTx { 307 | uint64 executableAt; 308 | address sender; 309 | bytes tx; 310 | } 311 | 312 | struct SlowModeConfig { 313 | uint64 timeout; 314 | uint64 txCount; 315 | uint64 txUpTo; 316 | } 317 | 318 | // legacy :( 319 | struct Prices { 320 | int128 spotPriceX18; 321 | int128 perpPriceX18; 322 | } 323 | 324 | struct BurnLpAndTransfer { 325 | bytes32 sender; 326 | uint32 productId; 327 | uint128 amount; 328 | bytes32 recipient; 329 | } 330 | 331 | struct TransferQuote { 332 | bytes32 sender; 333 | bytes32 recipient; 334 | uint128 amount; 335 | uint64 nonce; 336 | } 337 | 338 | struct SignedTransferQuote { 339 | TransferQuote tx; 340 | bytes signature; 341 | } 342 | 343 | struct IsolatedOrder { 344 | bytes32 sender; 345 | int128 priceX18; 346 | int128 amount; 347 | uint64 expiration; 348 | uint64 nonce; 349 | int128 margin; 350 | } 351 | 352 | struct CreateIsolatedSubaccount { 353 | IsolatedOrder order; 354 | uint32 productId; 355 | bytes signature; 356 | } 357 | 358 | function depositCollateral( 359 | bytes12 subaccountName, 360 | uint32 productId, 361 | uint128 amount 362 | ) external; 363 | 364 | function depositCollateralWithReferral( 365 | bytes12 subaccountName, 366 | uint32 productId, 367 | uint128 amount, 368 | string calldata referralCode 369 | ) external; 370 | 371 | function depositCollateralWithReferral( 372 | bytes32 subaccount, 373 | uint32 productId, 374 | uint128 amount, 375 | string calldata referralCode 376 | ) external; 377 | 378 | function submitSlowModeTransaction(bytes calldata transaction) external; 379 | 380 | function getTime() external view returns (uint128); 381 | 382 | function getSequencer() external view returns (address); 383 | 384 | function getNonce(address sender) external view returns (uint64); 385 | 386 | function getOffchainExchange() external view returns (address); 387 | 388 | function getPriceX18(uint32 productId) external view returns (int128); 389 | } 390 | -------------------------------------------------------------------------------- /core/contracts/interfaces/IEndpointGated.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.13; 3 | 4 | import "./IEndpoint.sol"; 5 | 6 | interface IEndpointGated { 7 | function getEndpoint() external view returns (address endpoint); 8 | } 9 | -------------------------------------------------------------------------------- /core/contracts/interfaces/IFEndpoint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Endpoint.sol"; 5 | 6 | interface IFEndpoint { 7 | function setPriceX18(uint32 productId, int128 priceX18) external; 8 | } 9 | -------------------------------------------------------------------------------- /core/contracts/interfaces/IOffchainExchange.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./clearinghouse/IClearinghouse.sol"; 5 | 6 | interface IOffchainExchange { 7 | event FillOrder( 8 | uint32 indexed productId, 9 | // original order information 10 | bytes32 indexed digest, 11 | bytes32 indexed subaccount, 12 | int128 priceX18, 13 | int128 amount, 14 | uint64 expiration, 15 | uint64 nonce, 16 | // whether this order is taking or making 17 | bool isTaker, 18 | // amount paid in fees (in quote) 19 | int128 feeAmount, 20 | // change in this subaccount's base balance from this fill 21 | int128 baseDelta, 22 | // change in this subaccount's quote balance from this fill 23 | int128 quoteDelta 24 | ); 25 | 26 | event CloseIsolatedSubaccount( 27 | bytes32 indexed isolatedSubaccount, 28 | bytes32 indexed parentSubaccount 29 | ); 30 | 31 | struct FeeRates { 32 | int64 makerRateX18; 33 | int64 takerRateX18; 34 | uint8 isNonDefault; // 1: non-default, 0: default 35 | } 36 | 37 | struct LpParams { 38 | int128 lpSpreadX18; 39 | } 40 | 41 | struct MarketInfoStore { 42 | int64 minSize; 43 | int64 sizeIncrement; 44 | int128 collectedFees; 45 | } 46 | 47 | struct MarketInfo { 48 | uint32 quoteId; 49 | int128 minSize; 50 | int128 sizeIncrement; 51 | int128 collectedFees; 52 | } 53 | 54 | function initialize(address _clearinghouse, address _endpoint) external; 55 | 56 | function updateFeeRates( 57 | address user, 58 | uint32 productId, 59 | int64 makerRateX18, 60 | int64 takerRateX18 61 | ) external; 62 | 63 | function updateMarket( 64 | uint32 productId, 65 | uint32 quoteId, 66 | address virtualBook, 67 | int128 sizeIncrement, 68 | int128 minSize, 69 | int128 lpSpreadX18 70 | ) external; 71 | 72 | function getMinSize(uint32 productId) external view returns (int128); 73 | 74 | function getDigest(uint32 productId, IEndpoint.Order memory order) 75 | external 76 | view 77 | returns (bytes32); 78 | 79 | function getSizeIncrement(uint32 productId) external view returns (int128); 80 | 81 | function getMarketInfo(uint32 productId) 82 | external 83 | view 84 | returns (MarketInfo memory); 85 | 86 | function getLpParams(uint32 productId) 87 | external 88 | view 89 | returns (LpParams memory); 90 | 91 | function swapAMM(IEndpoint.SwapAMM calldata tx) external; 92 | 93 | function matchOrderAMM( 94 | IEndpoint.MatchOrderAMM calldata tx, 95 | address takerLinkedSigner 96 | ) external; 97 | 98 | function matchOrders(IEndpoint.MatchOrdersWithSigner calldata tx) external; 99 | 100 | function dumpFees() external; 101 | 102 | function createIsolatedSubaccount( 103 | IEndpoint.CreateIsolatedSubaccount memory tx, 104 | address linkedSigner 105 | ) external returns (bytes32); 106 | 107 | function isIsolatedSubaccountActive(bytes32 parent, bytes32 subaccount) 108 | external 109 | view 110 | returns (bool); 111 | 112 | function getParentSubaccount(bytes32 subaccount) 113 | external 114 | view 115 | returns (bytes32); 116 | 117 | function tryCloseIsolatedSubaccount(bytes32 subaccount) external; 118 | } 119 | -------------------------------------------------------------------------------- /core/contracts/interfaces/IVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | import "./IEndpoint.sol"; 4 | 5 | interface IVerifier { 6 | function requireValidSignature( 7 | bytes32 message, 8 | bytes32 e, 9 | bytes32 s, 10 | uint8 signerBitmask 11 | ) external; 12 | 13 | function revertGasInfo(uint256 i, uint256 gasUsed) external pure; 14 | 15 | function validateSignature( 16 | bytes32 sender, 17 | address linkedSigner, 18 | bytes32 digest, 19 | bytes memory signature 20 | ) external pure; 21 | 22 | function computeDigest( 23 | IEndpoint.TransactionType txType, 24 | bytes calldata transactionBody 25 | ) external view returns (bytes32); 26 | } 27 | -------------------------------------------------------------------------------- /core/contracts/interfaces/clearinghouse/IClearinghouse.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IClearinghouseEventEmitter.sol"; 5 | import "../engine/IProductEngine.sol"; 6 | import "../IEndpoint.sol"; 7 | import "../IEndpointGated.sol"; 8 | import "../../libraries/RiskHelper.sol"; 9 | 10 | interface IClearinghouse is IClearinghouseEventEmitter, IEndpointGated { 11 | function addEngine( 12 | address engine, 13 | address offchainExchange, 14 | IProductEngine.EngineType engineType 15 | ) external; 16 | 17 | function registerProduct(uint32 productId) external; 18 | 19 | function transferQuote(IEndpoint.TransferQuote calldata tx) external; 20 | 21 | function depositCollateral(IEndpoint.DepositCollateral calldata tx) 22 | external; 23 | 24 | function withdrawCollateral( 25 | bytes32 sender, 26 | uint32 productId, 27 | uint128 amount, 28 | address sendTo, 29 | uint64 idx 30 | ) external; 31 | 32 | function mintLp(IEndpoint.MintLp calldata tx) external; 33 | 34 | function burnLp(IEndpoint.BurnLp calldata tx) external; 35 | 36 | function mintVlp(IEndpoint.MintVlp calldata txn, int128 oraclePriceX18) 37 | external; 38 | 39 | function burnVlp(IEndpoint.BurnVlp calldata txn, int128 oraclePriceX18) 40 | external; 41 | 42 | function liquidateSubaccount(IEndpoint.LiquidateSubaccount calldata tx) 43 | external; 44 | 45 | function depositInsurance(bytes calldata transaction) external; 46 | 47 | function withdrawInsurance(bytes calldata transaction, uint64 idx) external; 48 | 49 | function delistProduct(bytes calldata transaction) external; 50 | 51 | function settlePnl(bytes calldata transaction) external; 52 | 53 | function rebalanceXWithdraw(bytes calldata transaction, uint64 nSubmissions) 54 | external; 55 | 56 | function updateMinDepositRate(bytes calldata transaction) external; 57 | 58 | function updateFeeRates(bytes calldata transaction) external; 59 | 60 | function updatePrice(bytes calldata transaction) 61 | external 62 | returns (uint32, int128); 63 | 64 | function rebalanceVlp(bytes calldata transaction) external; 65 | 66 | function claimSequencerFees(int128[] calldata fees) external; 67 | 68 | /// @notice Retrieve quote ERC20 address 69 | function getQuote() external view returns (address); 70 | 71 | /// @notice Returns the registered engine address by type 72 | function getEngineByType(IProductEngine.EngineType engineType) 73 | external 74 | view 75 | returns (address); 76 | 77 | /// @notice Returns the engine associated with a product ID 78 | function getEngineByProduct(uint32 productId) 79 | external 80 | view 81 | returns (address); 82 | 83 | /// @notice Returns health for the subaccount across all engines 84 | function getHealth(bytes32 subaccount, IProductEngine.HealthType healthType) 85 | external 86 | view 87 | returns (int128); 88 | 89 | /// @notice Returns the amount of insurance remaining in this clearinghouse 90 | function getInsurance() external view returns (int128); 91 | 92 | function getSpreads() external view returns (uint256); 93 | 94 | function upgradeClearinghouseLiq(address _clearinghouseLiq) external; 95 | 96 | function getClearinghouseLiq() external view returns (address); 97 | 98 | function burnLpAndTransfer(IEndpoint.BurnLpAndTransfer calldata txn) 99 | external; 100 | 101 | function requireMinDeposit(uint32 productId, uint128 amount) external; 102 | 103 | function assertCode(bytes calldata tx) external; 104 | 105 | function manualAssert(bytes calldata tx) external; 106 | 107 | function getWithdrawPool() external view returns (address); 108 | 109 | function getSlowModeFee() external view returns (uint256); 110 | 111 | function getWithdrawFee(uint32 productId) external view returns (int128); 112 | 113 | function setWithdrawPool(address _withdrawPool) external; 114 | } 115 | -------------------------------------------------------------------------------- /core/contracts/interfaces/clearinghouse/IClearinghouseEventEmitter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IClearinghouseEventEmitter { 5 | /// @notice Emitted during initialization 6 | event ClearinghouseInitialized(address endpoint, address quote); 7 | 8 | /// @notice Emitted when collateral is modified for a subaccount 9 | event ModifyCollateral( 10 | int128 amount, 11 | bytes32 indexed subaccount, 12 | uint32 productId 13 | ); 14 | 15 | event Liquidation( 16 | bytes32 indexed liquidatorSubaccount, 17 | bytes32 indexed liquidateeSubaccount, 18 | uint32 productId, 19 | bool isEncodedSpread, 20 | int128 amount, 21 | int128 amountQuote 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /core/contracts/interfaces/clearinghouse/IClearinghouseLiq.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IClearinghouseEventEmitter.sol"; 5 | import "../engine/IProductEngine.sol"; 6 | import "../IEndpoint.sol"; 7 | import "../IEndpointGated.sol"; 8 | 9 | interface IClearinghouseLiq is IClearinghouseEventEmitter, IEndpointGated { 10 | function liquidateSubaccountImpl(IEndpoint.LiquidateSubaccount calldata tx) 11 | external; 12 | } 13 | -------------------------------------------------------------------------------- /core/contracts/interfaces/engine/IPerpEngine.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IProductEngine.sol"; 5 | import "../../libraries/RiskHelper.sol"; 6 | 7 | interface IPerpEngine is IProductEngine { 8 | event FundingPayment( 9 | uint32 productId, 10 | uint128 dt, 11 | int128 openInterest, 12 | int128 payment 13 | ); 14 | 15 | struct State { 16 | int128 cumulativeFundingLongX18; 17 | int128 cumulativeFundingShortX18; 18 | int128 availableSettle; 19 | int128 openInterest; 20 | } 21 | 22 | struct Balance { 23 | int128 amount; 24 | int128 vQuoteBalance; 25 | int128 lastCumulativeFundingX18; 26 | } 27 | 28 | struct LpState { 29 | int128 supply; 30 | // TODO: this should be removed; we can just get it from State.cumulativeFundingLongX18 31 | int128 lastCumulativeFundingX18; 32 | int128 cumulativeFundingPerLpX18; 33 | int128 base; 34 | int128 quote; 35 | } 36 | 37 | struct LpBalance { 38 | int128 amount; 39 | // NOTE: funding payments should be rolled 40 | // into Balance.vQuoteBalance; 41 | int128 lastCumulativeFundingX18; 42 | } 43 | 44 | struct UpdateProductTx { 45 | uint32 productId; 46 | int128 sizeIncrement; 47 | int128 minSize; 48 | int128 lpSpreadX18; 49 | RiskHelper.RiskStore riskStore; 50 | } 51 | 52 | function getStateAndBalance(uint32 productId, bytes32 subaccount) 53 | external 54 | view 55 | returns (State memory, Balance memory); 56 | 57 | function getBalance(uint32 productId, bytes32 subaccount) 58 | external 59 | view 60 | returns (Balance memory); 61 | 62 | function getStatesAndBalances(uint32 productId, bytes32 subaccount) 63 | external 64 | view 65 | returns ( 66 | LpState memory, 67 | LpBalance memory, 68 | State memory, 69 | Balance memory 70 | ); 71 | 72 | /// @dev Returns amount settled and emits SettlePnl events for each product 73 | function settlePnl(bytes32 subaccount, uint256 productIds) 74 | external 75 | returns (int128); 76 | 77 | function getSettlementState(uint32 productId, bytes32 subaccount) 78 | external 79 | view 80 | returns ( 81 | int128 availableSettle, 82 | LpState memory lpState, 83 | LpBalance memory lpBalance, 84 | State memory state, 85 | Balance memory balance 86 | ); 87 | 88 | function updateBalance( 89 | uint32 productId, 90 | bytes32 subaccount, 91 | int128 amountDelta, 92 | int128 vQuoteDelta 93 | ) external; 94 | 95 | function updateStates(uint128 dt, int128[] calldata avgPriceDiffs) external; 96 | 97 | function manualAssert(int128[] calldata openInterests) external view; 98 | 99 | function getPositionPnl(uint32 productId, bytes32 subaccount) 100 | external 101 | view 102 | returns (int128); 103 | 104 | function socializeSubaccount(bytes32 subaccount, int128 insurance) 105 | external 106 | returns (int128); 107 | } 108 | -------------------------------------------------------------------------------- /core/contracts/interfaces/engine/IProductEngine.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import "../clearinghouse/IClearinghouse.sol"; 6 | import "../../libraries/RiskHelper.sol"; 7 | 8 | interface IProductEngine { 9 | event AddProduct(uint32 productId); 10 | 11 | enum EngineType { 12 | SPOT, 13 | PERP 14 | } 15 | 16 | enum HealthType { 17 | INITIAL, 18 | MAINTENANCE, 19 | PNL 20 | } 21 | 22 | struct ProductDelta { 23 | uint32 productId; 24 | bytes32 subaccount; 25 | int128 amountDelta; 26 | int128 vQuoteDelta; 27 | } 28 | 29 | struct CoreRisk { 30 | int128 amount; 31 | int128 price; 32 | int128 longWeight; 33 | } 34 | 35 | /// @notice Initializes the engine 36 | function initialize( 37 | address _clearinghouse, 38 | address _offchainExchange, 39 | address _quote, 40 | address _endpoint, 41 | address _admin 42 | ) external; 43 | 44 | function getHealthContribution( 45 | bytes32 subaccount, 46 | IProductEngine.HealthType healthType 47 | ) external view returns (int128); 48 | 49 | function getCoreRisk( 50 | bytes32 subaccount, 51 | uint32 productId, 52 | IProductEngine.HealthType healthType 53 | ) external view returns (IProductEngine.CoreRisk memory); 54 | 55 | function updateProduct(bytes calldata txn) external; 56 | 57 | function swapLp( 58 | uint32 productId, 59 | int128 baseDelta, 60 | int128 quoteDelta 61 | ) external returns (int128, int128); 62 | 63 | function mintLp( 64 | uint32 productId, 65 | bytes32 subaccount, 66 | int128 amountBase, 67 | int128 quoteAmountLow, 68 | int128 quoteAmountHigh 69 | ) external; 70 | 71 | function burnLp( 72 | uint32 productId, 73 | bytes32 subaccount, 74 | // passing 0 here means to burn all 75 | int128 amountLp 76 | ) external returns (int128, int128); 77 | 78 | function decomposeLps(bytes32 liquidatee, bytes32 liquidator) 79 | external 80 | returns (int128); 81 | 82 | /// @notice return clearinghouse addr 83 | function getClearinghouse() external view returns (address); 84 | 85 | /// @notice return productIds associated with engine 86 | function getProductIds() external view returns (uint32[] memory); 87 | 88 | function getRisk(uint32 productId) 89 | external 90 | view 91 | returns (RiskHelper.Risk memory); 92 | 93 | /// @notice return the type of engine 94 | function getEngineType() external pure returns (IProductEngine.EngineType); 95 | 96 | function updatePrice(uint32 productId, int128 priceX18) external; 97 | } 98 | -------------------------------------------------------------------------------- /core/contracts/interfaces/engine/ISpotEngine.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IProductEngine.sol"; 5 | import "../../libraries/RiskHelper.sol"; 6 | 7 | interface ISpotEngine is IProductEngine { 8 | event SpotBalance( 9 | bytes32 indexed subaccount, 10 | uint32 indexed productId, 11 | int128 amount, 12 | int128 lastCumulativeMultiplierX18 13 | ); 14 | 15 | event InterestPayment( 16 | uint32 productId, 17 | uint128 dt, 18 | int128 depositRateMultiplierX18, 19 | int128 borrowRateMultiplierX18, 20 | int128 feeAmount 21 | ); 22 | 23 | struct Config { 24 | address token; 25 | int128 interestInflectionUtilX18; 26 | int128 interestFloorX18; 27 | int128 interestSmallCapX18; 28 | int128 interestLargeCapX18; 29 | } 30 | 31 | struct State { 32 | int128 cumulativeDepositsMultiplierX18; 33 | int128 cumulativeBorrowsMultiplierX18; 34 | int128 totalDepositsNormalized; 35 | int128 totalBorrowsNormalized; 36 | } 37 | 38 | struct Balance { 39 | int128 amount; 40 | int128 lastCumulativeMultiplierX18; 41 | } 42 | 43 | struct BalanceNormalized { 44 | int128 amountNormalized; 45 | } 46 | 47 | struct LpState { 48 | int128 supply; 49 | Balance quote; 50 | Balance base; 51 | } 52 | 53 | struct LpBalance { 54 | int128 amount; 55 | } 56 | 57 | struct Balances { 58 | BalanceNormalized balance; 59 | LpBalance lpBalance; 60 | } 61 | 62 | struct UpdateProductTx { 63 | uint32 productId; 64 | int128 sizeIncrement; 65 | int128 minSize; 66 | int128 lpSpreadX18; 67 | Config config; 68 | RiskHelper.RiskStore riskStore; 69 | } 70 | 71 | function getStateAndBalance(uint32 productId, bytes32 subaccount) 72 | external 73 | view 74 | returns (State memory, Balance memory); 75 | 76 | function getBalance(uint32 productId, bytes32 subaccount) 77 | external 78 | view 79 | returns (Balance memory); 80 | 81 | function getStatesAndBalances(uint32 productId, bytes32 subaccount) 82 | external 83 | view 84 | returns ( 85 | LpState memory, 86 | LpBalance memory, 87 | State memory, 88 | Balance memory 89 | ); 90 | 91 | function getConfig(uint32 productId) external view returns (Config memory); 92 | 93 | function getToken(uint32 productId) external view returns (address); 94 | 95 | function updateBalance( 96 | uint32 productId, 97 | bytes32 subaccount, 98 | int128 amountDelta 99 | ) external; 100 | 101 | function updateBalance( 102 | uint32 productId, 103 | bytes32 subaccount, 104 | int128 amountDelta, 105 | int128 quoteDelta 106 | ) external; 107 | 108 | function updateQuoteFromInsurance(bytes32 subaccount, int128 insurance) 109 | external 110 | returns (int128); 111 | 112 | function updateStates(uint128 dt) external; 113 | 114 | function updateMinDepositRate(uint32 productId, int128 minDepositRateX18) 115 | external; 116 | 117 | function manualAssert( 118 | int128[] calldata totalDeposits, 119 | int128[] calldata totalBorrows 120 | ) external view; 121 | 122 | function socializeSubaccount(bytes32 subaccount) external; 123 | 124 | function assertUtilization(uint32 productId) external view; 125 | } 126 | -------------------------------------------------------------------------------- /core/contracts/libraries/ERC20Helper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "../interfaces/IERC20Base.sol"; 5 | import "../common/Errors.sol"; 6 | import "hardhat/console.sol"; 7 | 8 | // @dev Adapted from https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/TransferHelper.sol 9 | library ERC20Helper { 10 | function safeTransfer( 11 | IERC20Base self, 12 | address to, 13 | uint256 amount 14 | ) internal { 15 | (bool success, bytes memory data) = address(self).call( 16 | abi.encodeWithSelector(IERC20Base.transfer.selector, to, amount) 17 | ); 18 | require( 19 | success && (data.length == 0 || abi.decode(data, (bool))), 20 | ERR_TRANSFER_FAILED 21 | ); 22 | } 23 | 24 | function safeTransferFrom( 25 | IERC20Base self, 26 | address from, 27 | address to, 28 | uint256 amount 29 | ) internal { 30 | (bool success, bytes memory data) = address(self).call( 31 | abi.encodeWithSelector( 32 | IERC20Base.transferFrom.selector, 33 | from, 34 | to, 35 | amount 36 | ) 37 | ); 38 | 39 | require( 40 | success && (data.length == 0 || abi.decode(data, (bool))), 41 | ERR_TRANSFER_FAILED 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/contracts/libraries/Logger.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | import "@openzeppelin/contracts/utils/Strings.sol"; 4 | import "./MathHelper.sol"; 5 | 6 | library Logger { 7 | event VertexEVMLog(string message); 8 | 9 | function log(string memory message) internal { 10 | emit VertexEVMLog(message); 11 | } 12 | 13 | function log(int128 value) internal { 14 | log(MathHelper.int2str(value)); 15 | } 16 | 17 | function log(string memory message, int128 value) internal { 18 | log(string.concat(message, " ", MathHelper.int2str(value))); 19 | } 20 | 21 | function log(string memory message, uint128 value) internal { 22 | log(string.concat(message, " ", MathHelper.uint2str(value))); 23 | } 24 | 25 | // function log(string memory message, uint32 value) internal { 26 | // log(message, uint128(value)); 27 | // } 28 | 29 | function log(string memory message, address value) internal { 30 | log( 31 | string.concat(message, " ", Strings.toHexString(uint160(value), 20)) 32 | ); 33 | } 34 | 35 | function log(string memory messages, bytes32 value) internal { 36 | log(string.concat(messages, " ", string(abi.encodePacked(value)))); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/contracts/libraries/MathHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "./MathSD21x18.sol"; 4 | 5 | /// @title MathHelper 6 | /// @dev Provides basic math functions 7 | library MathHelper { 8 | using MathSD21x18 for int128; 9 | 10 | /// @notice Returns market id for two given product ids 11 | function max(int128 a, int128 b) internal pure returns (int128) { 12 | return a > b ? a : b; 13 | } 14 | 15 | function min(int128 a, int128 b) internal pure returns (int128) { 16 | return a < b ? a : b; 17 | } 18 | 19 | function abs(int128 val) internal pure returns (int128) { 20 | return val < 0 ? -val : val; 21 | } 22 | 23 | // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) 24 | function sqrt(int128 y) internal pure returns (int128 z) { 25 | require(y >= 0, "ds-math-sqrt-non-positive"); 26 | if (y > 3) { 27 | z = y; 28 | int128 x = y / 2 + 1; 29 | while (x < z) { 30 | z = x; 31 | x = (y / x + x) / 2; 32 | } 33 | } else if (y != 0) { 34 | z = 1; 35 | } 36 | } 37 | 38 | function sqrt256(int256 y) internal pure returns (int256 z) { 39 | require(y >= 0, "ds-math-sqrt-non-positive"); 40 | if (y > 3) { 41 | z = y; 42 | int256 x = y / 2 + 1; 43 | while (x < z) { 44 | z = x; 45 | x = (y / x + x) / 2; 46 | } 47 | } else if (y != 0) { 48 | z = 1; 49 | } 50 | } 51 | 52 | function int2str(int128 value) internal pure returns (string memory) { 53 | if (value == 0) { 54 | return "0"; 55 | } 56 | 57 | bool negative = value < 0; 58 | uint128 absval = uint128(negative ? -value : value); 59 | string memory out = uint2str(absval); 60 | if (negative) { 61 | out = string.concat("-", out); 62 | } 63 | return out; 64 | } 65 | 66 | function uint2str(uint128 value) internal pure returns (string memory) { 67 | if (value == 0) { 68 | return "0"; 69 | } 70 | uint128 temp = value; 71 | uint128 digits; 72 | while (temp != 0) { 73 | digits++; 74 | temp /= 10; 75 | } 76 | bytes memory buffer = new bytes(digits); 77 | while (value != 0) { 78 | digits -= 1; 79 | buffer[digits] = bytes1(uint8(48 + uint128(value % 10))); 80 | value /= 10; 81 | } 82 | return string(buffer); 83 | } 84 | 85 | // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.1.0/contracts/math/SignedSafeMath.sol#L86 86 | function add(int128 x, int128 y) internal pure returns (int128) { 87 | int128 z = x + y; 88 | require((y >= 0 && z >= x) || (y < 0 && z < x), "ds-math-add-overflow"); 89 | return z; 90 | } 91 | 92 | // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.1.0/contracts/math/SignedSafeMath.sol#L69 93 | function sub(int128 x, int128 y) internal pure returns (int128) { 94 | int128 z = x - y; 95 | require( 96 | (y >= 0 && z <= x) || (y < 0 && z > x), 97 | "ds-math-sub-underflow" 98 | ); 99 | return z; 100 | } 101 | 102 | function mul(int128 x, int128 y) internal pure returns (int128 z) { 103 | require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); 104 | } 105 | 106 | function floor(int128 x, int128 y) internal pure returns (int128 z) { 107 | require(y > 0, "ds-math-floor-neg-mod"); 108 | int128 r = x % y; 109 | if (r == 0) { 110 | z = x; 111 | } else { 112 | z = (x >= 0 ? x - r : x - r - y); 113 | } 114 | } 115 | 116 | function ceil(int128 x, int128 y) internal pure returns (int128 z) { 117 | require(y > 0, "ds-math-ceil-neg-mod"); 118 | int128 r = x % y; 119 | if (r == 0) { 120 | z = x; 121 | } else { 122 | z = (x >= 0 ? x + y - r : x - r); 123 | } 124 | } 125 | 126 | // we don't need to floor base with sizeIncrement in this function 127 | // because this function is only used by `view` functions, which means 128 | // the returned values will not be written into storage. 129 | function ammEquilibrium( 130 | int128 base, 131 | int128 quote, 132 | int128 priceX18 133 | ) internal pure returns (int128, int128) { 134 | if (base == 0 || quote == 0) { 135 | return (0, 0); 136 | } 137 | int256 k = int256(base) * quote; 138 | // base * price * base == k 139 | // base = sqrt(k / price); 140 | base = int128(MathHelper.sqrt256((k * 1e18) / priceX18)); 141 | quote = (base == 0) ? int128(0) : int128(k / base); 142 | return (base, quote); 143 | } 144 | 145 | function isSwapValid( 146 | int128 baseDelta, 147 | int128 quoteDelta, 148 | int128 base, 149 | int128 quote 150 | ) internal pure returns (bool) { 151 | if ( 152 | base == 0 || 153 | quote == 0 || 154 | base + baseDelta <= 0 || 155 | quote + quoteDelta <= 0 156 | ) { 157 | return false; 158 | } 159 | int256 kPrev = int256(base) * quote; 160 | int256 kNew = int256(base + baseDelta) * (quote + quoteDelta); 161 | return kNew > kPrev; 162 | } 163 | 164 | function swap( 165 | int128 amountSwap, 166 | int128 base, 167 | int128 quote, 168 | int128 priceX18, 169 | int128 sizeIncrement, 170 | int128 lpSpreadX18 171 | ) internal pure returns (int128, int128) { 172 | // (amountSwap % sizeIncrement) is guaranteed to be 0 173 | if (base == 0 || quote == 0) { 174 | return (0, 0); 175 | } 176 | int128 currentPriceX18 = quote.div(base); 177 | 178 | int128 keepRateX18 = 1e18 - lpSpreadX18; 179 | 180 | // selling 181 | if (amountSwap > 0) { 182 | priceX18 = priceX18.div(keepRateX18); 183 | if (priceX18 >= currentPriceX18) { 184 | return (0, 0); 185 | } 186 | } else { 187 | priceX18 = priceX18.mul(keepRateX18); 188 | if (priceX18 <= currentPriceX18) { 189 | return (0, 0); 190 | } 191 | } 192 | 193 | int256 k = int256(base) * quote; 194 | int128 baseAtPrice = int128( 195 | (MathHelper.sqrt256(k) * 1e9) / MathHelper.sqrt(priceX18) 196 | ); 197 | // base -> base + amountSwap 198 | 199 | int128 baseSwapped; 200 | 201 | if ( 202 | (amountSwap > 0 && base + amountSwap > baseAtPrice) || 203 | (amountSwap < 0 && base + amountSwap < baseAtPrice) 204 | ) { 205 | // we hit price limits before we exhaust amountSwap 206 | if (baseAtPrice >= base) { 207 | baseSwapped = MathHelper.floor( 208 | baseAtPrice - base, 209 | sizeIncrement 210 | ); 211 | } else { 212 | baseSwapped = MathHelper.ceil( 213 | baseAtPrice - base, 214 | sizeIncrement 215 | ); 216 | } 217 | } else { 218 | // just swap it all 219 | // amountSwap is already guaranteed to adhere to sizeIncrement 220 | baseSwapped = amountSwap; 221 | } 222 | 223 | int128 quoteSwapped = int128(k / (base + baseSwapped) - quote); 224 | if (amountSwap > 0) { 225 | quoteSwapped = quoteSwapped.mul(keepRateX18); 226 | } else { 227 | quoteSwapped = quoteSwapped.div(keepRateX18); 228 | } 229 | return (baseSwapped, quoteSwapped); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /core/contracts/libraries/MathSD21x18.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "prb-math/contracts/PRBMathSD59x18.sol"; 5 | 6 | library MathSD21x18 { 7 | using PRBMathSD59x18 for int256; 8 | 9 | int128 private constant ONE_X18 = 1000000000000000000; 10 | int128 private constant MIN_X18 = -0x80000000000000000000000000000000; 11 | int128 private constant MAX_X18 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; 12 | string private constant ERR_OVERFLOW = "OF"; 13 | string private constant ERR_DIV_BY_ZERO = "DBZ"; 14 | 15 | function fromInt(int128 x) internal pure returns (int128) { 16 | unchecked { 17 | int256 result = int256(x) * ONE_X18; 18 | require(result >= MIN_X18 && result <= MAX_X18, ERR_OVERFLOW); 19 | return int128(result); 20 | } 21 | } 22 | 23 | function mulDiv( 24 | int128 x, 25 | int128 y, 26 | int128 z 27 | ) internal pure returns (int128) { 28 | unchecked { 29 | require(z != 0, ERR_DIV_BY_ZERO); 30 | int256 result = (int256(x) * y) / z; 31 | require(result >= MIN_X18 && result <= MAX_X18, ERR_OVERFLOW); 32 | return int128(result); 33 | } 34 | } 35 | 36 | function toInt(int128 x) internal pure returns (int128) { 37 | unchecked { 38 | return int128(x / ONE_X18); 39 | } 40 | } 41 | 42 | function add(int128 x, int128 y) internal pure returns (int128) { 43 | unchecked { 44 | int256 result = int256(x) + y; 45 | require(result >= MIN_X18 && result <= MAX_X18, ERR_OVERFLOW); 46 | return int128(result); 47 | } 48 | } 49 | 50 | function sub(int128 x, int128 y) internal pure returns (int128) { 51 | unchecked { 52 | int256 result = int256(x) - y; 53 | require(result >= MIN_X18 && result <= MAX_X18, ERR_OVERFLOW); 54 | return int128(result); 55 | } 56 | } 57 | 58 | function mul(int128 x, int128 y) internal pure returns (int128) { 59 | unchecked { 60 | int256 result = (int256(x) * y) / ONE_X18; 61 | require(result >= MIN_X18 && result <= MAX_X18, ERR_OVERFLOW); 62 | return int128(result); 63 | } 64 | } 65 | 66 | function div(int128 x, int128 y) internal pure returns (int128) { 67 | unchecked { 68 | require(y != 0, ERR_DIV_BY_ZERO); 69 | int256 result = (int256(x) * ONE_X18) / y; 70 | require(result >= MIN_X18 && result <= MAX_X18, ERR_OVERFLOW); 71 | return int128(result); 72 | } 73 | } 74 | 75 | function abs(int128 x) internal pure returns (int128) { 76 | unchecked { 77 | require(x != MIN_X18, ERR_OVERFLOW); 78 | return x < 0 ? -x : x; 79 | } 80 | } 81 | 82 | function sqrt(int128 x) internal pure returns (int128) { 83 | unchecked { 84 | int256 result = int256(x).sqrt(); 85 | require(result >= MIN_X18 && result <= MAX_X18, ERR_OVERFLOW); 86 | return int128(result); 87 | } 88 | } 89 | 90 | // note that y is not X18 91 | function pow(int128 x, int128 y) internal pure returns (int128) { 92 | unchecked { 93 | require(y >= 0, ERR_OVERFLOW); 94 | int128 result = ONE_X18; 95 | for (int128 i = 1; i <= y; i *= 2) { 96 | if (i & y != 0) { 97 | result = mul(result, x); 98 | } 99 | x = mul(x, x); 100 | } 101 | return result; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /core/contracts/libraries/RippleBase58.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | // revised from https://github.com/storyicon/base58-solidity 5 | 6 | library RippleBase58 { 7 | bytes constant ALPHABET = 8 | "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"; 9 | 10 | function decode(bytes memory data_) internal pure returns (bytes memory) { 11 | unchecked { 12 | uint256 zero = 49; 13 | uint256 b58sz = data_.length; 14 | uint256 zcount = 0; 15 | for (uint256 i = 0; i < b58sz && uint8(data_[i]) == zero; i++) { 16 | zcount++; 17 | } 18 | uint256 t; 19 | uint256 c; 20 | bool f; 21 | bytes memory binu = new bytes(2 * (((b58sz * 8351) / 6115) + 1)); 22 | uint32[] memory outi = new uint32[]((b58sz + 3) / 4); 23 | for (uint256 i = 0; i < data_.length; i++) { 24 | bytes1 r = data_[i]; 25 | (c, f) = indexOf(ALPHABET, r); 26 | require(f, "invalid base58 digit"); 27 | for (int256 k = int256(outi.length) - 1; k >= 0; k--) { 28 | t = uint64(outi[uint256(k)]) * 58 + c; 29 | c = t >> 32; 30 | outi[uint256(k)] = uint32(t & 0xffffffff); 31 | } 32 | } 33 | uint64 mask = uint64(b58sz % 4) * 8; 34 | if (mask == 0) { 35 | mask = 32; 36 | } 37 | mask -= 8; 38 | uint256 outLen = 0; 39 | for (uint256 j = 0; j < outi.length; j++) { 40 | while (mask < 32) { 41 | binu[outLen] = bytes1(uint8(outi[j] >> mask)); 42 | outLen++; 43 | if (mask < 8) { 44 | break; 45 | } 46 | mask -= 8; 47 | } 48 | mask = 24; 49 | } 50 | for (uint256 msb = zcount; msb < binu.length; msb++) { 51 | if (binu[msb] > 0) { 52 | return slice(binu, msb - zcount, outLen); 53 | } 54 | } 55 | 56 | return slice(binu, 0, outLen); 57 | } 58 | } 59 | 60 | function decodeFromRippleAddress(bytes memory data) 61 | internal 62 | pure 63 | returns (address) 64 | { 65 | uint160 result; 66 | bytes memory a = decode(data); 67 | for (uint256 i = 0; i + 4 < a.length; i++) { 68 | result = result * 256 + uint8(a[i]); 69 | } 70 | return address(result); 71 | } 72 | 73 | /** 74 | * @notice slice is used to slice the given byte, returns the bytes in the range of [start_, end_) 75 | * @param data_ raw data, passed in as bytes. 76 | * @param start_ start index. 77 | * @param end_ end index. 78 | * @return slice data 79 | */ 80 | function slice( 81 | bytes memory data_, 82 | uint256 start_, 83 | uint256 end_ 84 | ) internal pure returns (bytes memory) { 85 | unchecked { 86 | bytes memory ret = new bytes(end_ - start_); 87 | for (uint256 i = 0; i < end_ - start_; i++) { 88 | ret[i] = data_[i + start_]; 89 | } 90 | return ret; 91 | } 92 | } 93 | 94 | /** 95 | * @notice indexOf is used to find where char_ appears in data_. 96 | * @param data_ raw data, passed in as bytes. 97 | * @param char_ target byte. 98 | * @return index, and whether the search was successful. 99 | */ 100 | function indexOf(bytes memory data_, bytes1 char_) 101 | internal 102 | pure 103 | returns (uint256, bool) 104 | { 105 | unchecked { 106 | for (uint256 i = 0; i < data_.length; i++) { 107 | if (data_[i] == char_) { 108 | return (i, true); 109 | } 110 | } 111 | return (0, false); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /core/contracts/libraries/RiskHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | import "./MathSD21x18.sol"; 4 | import "../interfaces/engine/IProductEngine.sol"; 5 | import "../common/Constants.sol"; 6 | import "../common/Errors.sol"; 7 | import "./MathHelper.sol"; 8 | 9 | /// @title RiskHelper 10 | /// @dev Provides basic math functions 11 | library RiskHelper { 12 | using MathSD21x18 for int128; 13 | 14 | struct RiskStore { 15 | // these weights are all 16 | // between 0 and 2 17 | // these integers are the real 18 | // weights times 1e9 19 | int32 longWeightInitial; 20 | int32 shortWeightInitial; 21 | int32 longWeightMaintenance; 22 | int32 shortWeightMaintenance; 23 | int128 priceX18; 24 | } 25 | 26 | struct Risk { 27 | int128 longWeightInitialX18; 28 | int128 shortWeightInitialX18; 29 | int128 longWeightMaintenanceX18; 30 | int128 shortWeightMaintenanceX18; 31 | int128 priceX18; 32 | } 33 | 34 | function _getSpreadHealthRebateAmount( 35 | Risk memory perpRisk, 36 | int128 basisAmount, 37 | int128 priceSumX18, 38 | IProductEngine.HealthType healthType 39 | ) internal pure returns (int128) { 40 | // 5x more leverage than the standard perp 41 | // by refunding 4/5 of the health penalty 42 | int128 rebateRateX18 = ((ONE - _getWeightX18(perpRisk, 1, healthType)) * 43 | 4) / 5; 44 | return rebateRateX18.mul(priceSumX18).mul(basisAmount); 45 | } 46 | 47 | function _getLpRawValue( 48 | int128 baseAmount, 49 | int128 quoteAmount, 50 | int128 priceX18 51 | ) internal pure returns (int128) { 52 | // naive way: value an LP token by value of the raw components 2 * arithmetic mean of base value and quote value 53 | // price manipulation proof way: use the geometric mean 54 | return 55 | 2 * 56 | int128( 57 | MathHelper.sqrt256( 58 | int256(baseAmount.mul(priceX18)) * quoteAmount 59 | ) 60 | ); 61 | } 62 | 63 | function _getWeightX18( 64 | Risk memory risk, 65 | int128 amount, 66 | IProductEngine.HealthType healthType 67 | ) internal pure returns (int128) { 68 | // (1 + imf * sqrt(amount)) 69 | if (healthType == IProductEngine.HealthType.PNL) { 70 | return ONE; 71 | } 72 | 73 | int128 weight; 74 | if (amount >= 0) { 75 | weight = healthType == IProductEngine.HealthType.INITIAL 76 | ? risk.longWeightInitialX18 77 | : risk.longWeightMaintenanceX18; 78 | } else { 79 | weight = healthType == IProductEngine.HealthType.INITIAL 80 | ? risk.shortWeightInitialX18 81 | : risk.shortWeightMaintenanceX18; 82 | } 83 | 84 | return weight; 85 | } 86 | 87 | function isIsolatedSubaccount(bytes32 subaccount) 88 | internal 89 | pure 90 | returns (bool) 91 | { 92 | return uint256(subaccount) & 0xFFFFFF == 6910831; 93 | } 94 | 95 | function getIsolatedProductId(bytes32 subaccount) 96 | internal 97 | pure 98 | returns (uint32) 99 | { 100 | if (!isIsolatedSubaccount(subaccount)) { 101 | return 0; 102 | } 103 | return uint32((uint256(subaccount) >> 32) & 0xFFFF); 104 | } 105 | 106 | function getIsolatedId(bytes32 subaccount) internal pure returns (uint8) { 107 | if (!isIsolatedSubaccount(subaccount)) { 108 | return 0; 109 | } 110 | return uint8((uint256(subaccount) >> 24) & 0xFF); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /core/contracts/util/GasInfo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | contract GasInfo { 5 | /// Arbitrum: 6 | /// @notice Get gas prices. Uses the caller's preferred aggregator, or the default if the caller doesn't have a preferred one. 7 | /// @return return gas prices in wei 8 | /// ( 9 | /// per L2 tx, 10 | /// per L1 calldata byte 11 | /// per storage allocation, 12 | /// per ArbGas base, 13 | /// per ArbGas congestion, 14 | /// per ArbGas total 15 | /// ) 16 | function getPricesInWei() 17 | public 18 | pure 19 | returns ( 20 | uint256, 21 | uint256, // this value is the 22 | uint256, 23 | uint256, 24 | uint256, 25 | uint256 26 | ) 27 | { 28 | return (0, 0, 0, 0, 0, 0); 29 | } 30 | 31 | /// Optimism: 32 | // to compute approximate wei per L1 calldata byte, we do getL1Fee('0xF * 1000') / getL1GasUsed('0xF * 1000') 33 | /// @notice Computes the amount of L1 gas used for a transaction. Adds 68 bytes 34 | /// of padding to account for the fact that the input does not have a signature. 35 | /// @param _data Unsigned fully RLP-encoded transaction to get the L1 gas for. 36 | /// @return Amount of L1 gas used to publish the transaction. 37 | function getL1GasUsed(bytes memory _data) public view returns (uint256) { 38 | return 0; 39 | } 40 | 41 | /// @notice Computes the L1 portion of the fee based on the size of the rlp encoded input 42 | /// transaction, the current L1 base fee, and the various dynamic parameters. 43 | /// @param _data Unsigned fully RLP-encoded transaction to get the L1 fee for. 44 | /// @return L1 fee that should be paid for the tx 45 | function getL1Fee(bytes memory _data) external view returns (uint256) { 46 | return 0; 47 | } 48 | 49 | /// @custom:legacy 50 | /// @notice Retrieves the number of decimals used in the scalar. 51 | /// @return Number of decimals used in the scalar. 52 | function decimals() public pure returns (uint256) { 53 | return 0; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/contracts/util/MockERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract MockERC20 is ERC20 { 7 | uint256 private constant INITIAL_SUPPLY = 100 ether; 8 | uint8 private _decimals; 9 | 10 | constructor( 11 | string memory name_, 12 | string memory symbol_, 13 | uint8 decimals_ 14 | ) ERC20(name_, symbol_) { 15 | _mint(msg.sender, INITIAL_SUPPLY); 16 | _decimals = decimals_; 17 | } 18 | 19 | /// @dev Unpermissioned minting for testing 20 | function mint(address account, uint256 amount) external { 21 | require(amount < 100 ether, "MockERC20: amount too large"); 22 | _mint(account, amount); 23 | } 24 | 25 | function decimals() public view virtual override returns (uint8) { 26 | return _decimals; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @type import('hardhat/config').HardhatUserConfig 3 | */ 4 | import '@nomicfoundation/hardhat-chai-matchers'; 5 | import '@nomiclabs/hardhat-ethers'; 6 | import '@nomiclabs/hardhat-etherscan'; 7 | import '@nomiclabs/hardhat-solhint'; 8 | import '@openzeppelin/hardhat-upgrades'; 9 | import '@typechain/hardhat'; 10 | import 'dotenv/config'; 11 | import 'hardhat-deploy'; 12 | import 'solidity-coverage'; 13 | import 'hardhat-gas-reporter'; 14 | import 'hardhat-contract-sizer'; 15 | import 'hardhat-abi-exporter'; 16 | import { HardhatUserConfig } from 'hardhat/config'; 17 | 18 | const config: HardhatUserConfig = { 19 | solidity: { 20 | version: '0.8.13', 21 | settings: { 22 | optimizer: { 23 | enabled: true, 24 | runs: 200, 25 | }, 26 | }, 27 | }, 28 | defaultNetwork: 'localhost', 29 | contractSizer: { 30 | runOnCompile: true, 31 | }, 32 | abiExporter: { 33 | path: './abis', 34 | runOnCompile: true, 35 | clear: true, 36 | flat: true, 37 | spacing: 2, 38 | }, 39 | gasReporter: { 40 | onlyCalledMethods: true, 41 | showTimeSpent: true, 42 | }, 43 | mocha: { 44 | timeout: 1000000000, 45 | }, 46 | }; 47 | 48 | export default config; 49 | -------------------------------------------------------------------------------- /core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vertex-core", 3 | "version": "3.4.0", 4 | "license": "UNLICENSED", 5 | "description": "EVM implementation of Vertex", 6 | "scripts": { 7 | "prepare": "husky install", 8 | "lint": "eslint './**/*.ts' --fix && prettier --write './**/*.{ts,sol}' --loglevel silent && solhint --fix 'contracts/**/*.sol'", 9 | "compile": "hardhat compile", 10 | "force-compile": "TS_NODE_TRANSPILE_ONLY=1 hardhat clean && TS_NODE_TRANSPILE_ONLY=1 hardhat compile" 11 | }, 12 | "engines": { 13 | "node": ">=16" 14 | }, 15 | "dependencies": { 16 | "@chainlink/contracts": "^0.4.1", 17 | "@ethersproject/abi": "^5.6.4", 18 | "@ethersproject/bignumber": "^5.6.2", 19 | "@ethersproject/bytes": "^5.6.1", 20 | "@ethersproject/providers": "^5.6.8", 21 | "@openzeppelin/contracts": "^4.8.0-rc.2", 22 | "@openzeppelin/contracts-upgradeable": "^4.8.0-rc.2", 23 | "evm-bn": "^1.1.1", 24 | "fs-extra": "^10.1.0", 25 | "lodash": "^4.17.21", 26 | "mathjs": "^10.6.1", 27 | "node-fetch": "2", 28 | "prb-math": "^2.4.3", 29 | "redis": "^4.3.0" 30 | }, 31 | "devDependencies": { 32 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.2", 33 | "@nomicfoundation/hardhat-network-helpers": "^1.0.3", 34 | "@nomicfoundation/hardhat-toolbox": "^1.0.2", 35 | "@nomiclabs/hardhat-ethers": "^2.1.0", 36 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 37 | "@nomiclabs/hardhat-solhint": "^2.0.1", 38 | "@openzeppelin/hardhat-upgrades": "^1.18.1", 39 | "@typechain/ethers-v5": "^10.1.0", 40 | "@typechain/hardhat": "^6.1.2", 41 | "@types/chai": "^4.3.1", 42 | "@types/fs-extra": "^9.0.13", 43 | "@types/lodash": "^4.14.182", 44 | "@types/mocha": "^9.1.1", 45 | "@types/node": "^17.0.40", 46 | "@types/node-fetch": "^2.6.2", 47 | "@typescript-eslint/eslint-plugin": "^5.30.7", 48 | "@typescript-eslint/parser": "^5.30.7", 49 | "chai": "^4.3.6", 50 | "dotenv": "^16.0.1", 51 | "eslint": "^8.20.0", 52 | "eslint-config-prettier": "^8.5.0", 53 | "ethers": "^5.6.9", 54 | "hardhat": "^2.10.1", 55 | "hardhat-abi-exporter": "^2.9.0", 56 | "hardhat-contract-sizer": "^2.5.1", 57 | "hardhat-deploy": "^0.11.10", 58 | "hardhat-gas-reporter": "^1.0.8", 59 | "husky": "^8.0.0", 60 | "lint-staged": "^13.0.3", 61 | "prettier": "^2.6.2", 62 | "prettier-plugin-solidity": "^1.0.0-beta.19", 63 | "solhint": "^3.3.7", 64 | "solidity-coverage": "^0.7.21", 65 | "ts-node": "^10.8.1", 66 | "typechain": "^8.1.0", 67 | "typescript": "^4.7.3" 68 | }, 69 | "lint-staged": { 70 | "*.{ts,sol}": "yarn lint" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lba/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | 4 | # Typechain 5 | typechain-types/ 6 | artifacts/ 7 | 8 | # solidity-coverage 9 | coverage/ 10 | coverage.json -------------------------------------------------------------------------------- /lba/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['@typescript-eslint/eslint-plugin'], 4 | extends: ['plugin:@typescript-eslint/recommended', 'prettier'], 5 | rules: { 6 | '@typescript-eslint/explicit-module-boundary-types': 'off', 7 | }, 8 | env: { 9 | node: true, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /lba/.gitignore: -------------------------------------------------------------------------------- 1 | infra/shared/deployment.localhost.json 2 | infra/shared/secrets.*.json 3 | !infra/shared/secrets.example.json 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | .pybuilder/ 80 | target/ 81 | **/Cargo.lock 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # Mac tmp files 91 | .DS_Store 92 | 93 | # pyenv 94 | # For a library or package, you might want to ignore these files since the code is 95 | # intended to run in multiple environments; otherwise, check them in: 96 | # .python-version 97 | 98 | # pipenv 99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 102 | # install all needed dependencies. 103 | #Pipfile.lock 104 | 105 | # poetry 106 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 107 | # This is especially recommended for binary packages to ensure reproducibility, and is more 108 | # commonly ignored for libraries. 109 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 110 | #poetry.lock 111 | 112 | # pdm 113 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 114 | #pdm.lock 115 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 116 | # in version control. 117 | # https://pdm.fming.dev/#use-with-ide 118 | .pdm.toml 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # Jetbrains 164 | .idea/ 165 | 166 | infra/out/*.json 167 | 168 | .vscode/ 169 | 170 | # dev env 171 | **/*.nix 172 | 173 | # local testing files 174 | vertex-utils/products/products.localhost.json 175 | vertex-utils/deployment/deployment.localhost.json 176 | engine/tests/test_local.rs 177 | 178 | # admin scripts 179 | admin/run-testnet.sh 180 | admin/run-prod.sh 181 | admin/exports 182 | 183 | node_modules 184 | 185 | secrets.json 186 | .secrets 187 | .env 188 | 189 | # Deploy config 190 | tasks/deploy/config/spotConfig.json 191 | tasks/deploy/config/perpConfig.json 192 | 193 | # Deploy temp files 194 | tasks/deploy/temp/* 195 | deployment.localhost.json 196 | deployment.local.json 197 | products.localhost.json 198 | 199 | # OpenZeppelin 200 | .openzeppelin/dev-*.json 201 | .openzeppelin/unknown-1337.json 202 | .openzeppelin/.session 203 | 204 | # Hardhat 205 | cache/ 206 | 207 | # Typechain 208 | typechain-types/ 209 | artifacts/ 210 | 211 | # ABIs 212 | abis/ 213 | 214 | # solidity-coverage 215 | coverage/ 216 | coverage.json 217 | 218 | # powerful bash script 219 | konst-helper.sh 220 | 221 | .python-version 222 | venv 223 | *.nix -------------------------------------------------------------------------------- /lba/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": [ 5 | "error", 6 | "^0.8.0" 7 | ], 8 | "not-rely-on-time": "off", 9 | "reason-string": "off", 10 | "avoid-low-level-calls": "off", 11 | "func-visibility": [ 12 | "warn", 13 | { 14 | "ignoreConstructors": true 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lba/README.md: -------------------------------------------------------------------------------- 1 | # Vertex LBA 2 | 3 | Vertex LBA contracts. 4 | 5 | ## Dependencies 6 | 7 | - node >=16 8 | - [yarn](https://www.npmjs.com/package/yarn) 9 | 10 | ## Quickstart 11 | 12 | 1. Install yarn dependencies: `yarn install`. 13 | 3. Copy `.env.example` to `.env` - modify as necessary 14 | 15 | ## Common Commands 16 | 17 | Common commands can be found in the `package.json` file, and runnable with `yarn `. For 18 | example: `yarn contracts:compile`. 19 | 20 | `lint`: Runs prettier & SolHint 21 | 22 | `contracts:force-compile`: Compiles contracts and generates TS bindings + ABIs 23 | 24 | `run-local-node`: Runs a persistent local [Hardhat node](https://hardhat.org/hardhat-network) for local testing 25 | -------------------------------------------------------------------------------- /lba/contracts/Airdrop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./interfaces/ILBA.sol"; 5 | import "./interfaces/IVesting.sol"; 6 | import "./interfaces/IStaking.sol"; 7 | import "./interfaces/IStakingV2.sol"; 8 | import "./interfaces/IAirdrop.sol"; 9 | import "./interfaces/ISanctionsList.sol"; 10 | import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; 11 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 12 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 13 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 14 | 15 | contract Airdrop is OwnableUpgradeable, IAirdrop { 16 | address token; 17 | address lba; 18 | address sanctions; 19 | uint32 airdropEpoch; 20 | uint32 pastEpochs; 21 | 22 | mapping(uint32 => bytes32) merkleRoots; 23 | 24 | // tokens will not be claimed after deadline. 25 | uint64[] public claimingDeadlines; 26 | mapping(uint32 => mapping(address => uint256)) claimed; 27 | 28 | address staking; 29 | address stakingV2; 30 | 31 | /// @custom:oz-upgrades-unsafe-allow constructor 32 | constructor() { 33 | _disableInitializers(); 34 | } 35 | 36 | function initialize( 37 | address _token, 38 | address _lba, 39 | address _sanctions, 40 | uint32 _airdropEpoch 41 | ) external initializer { 42 | __Ownable_init(); 43 | token = _token; 44 | lba = _lba; 45 | airdropEpoch = _airdropEpoch; 46 | pastEpochs = airdropEpoch - 1; 47 | sanctions = _sanctions; 48 | for (uint32 i = 0; i < airdropEpoch; i++) { 49 | claimingDeadlines.push(0); 50 | } 51 | } 52 | 53 | function registerStakingV2(address _stakingV2) external onlyOwner { 54 | require(stakingV2 == address(0), "already registered."); 55 | stakingV2 = _stakingV2; 56 | } 57 | 58 | function registerStaking(address _staking) external onlyOwner { 59 | require(staking == address(0), "already registered."); 60 | staking = _staking; 61 | } 62 | 63 | function registerMerkleRoot( 64 | uint32 epoch, 65 | uint64 deadline, 66 | bytes32 merkleRoot 67 | ) external onlyOwner { 68 | pastEpochs += 1; 69 | require(epoch == pastEpochs, "Invalid epoch provided."); 70 | claimingDeadlines.push(deadline); 71 | merkleRoots[epoch] = merkleRoot; 72 | } 73 | 74 | function _verifyProof( 75 | uint32 epoch, 76 | address sender, 77 | uint256 amount, 78 | uint256 totalAmount, 79 | bytes32[] calldata proof 80 | ) internal { 81 | require(amount > 0, "Trying to claim zero rewards."); 82 | require( 83 | claimed[epoch][sender] + amount <= totalAmount, 84 | "Trying to claim more rewards than unclaimed rewards." 85 | ); 86 | require( 87 | merkleRoots[epoch] != bytes32(0), 88 | "Epoch hasn't been registered." 89 | ); 90 | require(block.timestamp < claimingDeadlines[epoch], "deadline passed."); 91 | require( 92 | !ISanctionsList(sanctions).isSanctioned(sender), 93 | "address is sanctioned." 94 | ); 95 | 96 | bytes32 leaf = keccak256( 97 | bytes.concat(keccak256(abi.encode(sender, totalAmount))) 98 | ); 99 | bool isValidLeaf = MerkleProof.verify(proof, merkleRoots[epoch], leaf); 100 | require(isValidLeaf, "Invalid proof."); 101 | 102 | claimed[epoch][sender] += amount; 103 | emit ClaimVrtx(sender, epoch, amount); 104 | } 105 | 106 | function claimToLBA( 107 | uint256 amount, 108 | uint256 totalAmount, 109 | bytes32[] calldata proof 110 | ) external { 111 | _verifyProof(airdropEpoch, msg.sender, amount, totalAmount, proof); 112 | require( 113 | ILBA(lba).getStage() == ILBA.Stage.DepositingTokens, 114 | "Not at Depositing to LBA stage." 115 | ); 116 | SafeERC20.safeApprove(IERC20(token), lba, amount); 117 | ILBA(lba).depositVrtx(msg.sender, amount); 118 | } 119 | 120 | function claim( 121 | uint32 epoch, 122 | uint256 amount, 123 | uint256 totalAmount, 124 | bytes32[] calldata proof 125 | ) public { 126 | _verifyProof(epoch, msg.sender, amount, totalAmount, proof); 127 | if (epoch == airdropEpoch) { 128 | // airdrop phase 129 | require( 130 | ILBA(lba).getStage() >= ILBA.Stage.LpMinted, 131 | "LBA hasn't finished, can't claim to wallet." 132 | ); 133 | } 134 | SafeERC20.safeTransfer(IERC20(token), msg.sender, amount); 135 | } 136 | 137 | function claimAndStake( 138 | uint32 epoch, 139 | uint256 amount, 140 | uint256 totalAmount, 141 | bytes32[] calldata proof 142 | ) external { 143 | _verifyProof(epoch, msg.sender, amount, totalAmount, proof); 144 | if (epoch == airdropEpoch) { 145 | // airdrop phase 146 | require( 147 | ILBA(lba).getStage() >= ILBA.Stage.LpMinted, 148 | "LBA hasn't finished, can't claim to wallet." 149 | ); 150 | } 151 | uint64 v2StartTime = IStaking(staking).getV2StartTime(); 152 | if (v2StartTime == 0 || block.timestamp < v2StartTime) { 153 | SafeERC20.safeApprove(IERC20(token), staking, amount); 154 | IStaking(staking).stakeAs(msg.sender, amount); 155 | } else { 156 | SafeERC20.safeApprove(IERC20(token), stakingV2, amount); 157 | IStakingV2(stakingV2).stakeAs(msg.sender, uint128(amount)); 158 | } 159 | } 160 | 161 | function distributeRewards(uint256 amount) external onlyOwner { 162 | SafeERC20.safeApprove(IERC20(token), lba, amount); 163 | ILBA(lba).distributeRewards(amount); 164 | } 165 | 166 | function getClaimed( 167 | address account 168 | ) external view returns (uint256[] memory) { 169 | uint256[] memory result = new uint256[](pastEpochs + 1); 170 | for (uint32 epoch = airdropEpoch; epoch <= pastEpochs; epoch++) { 171 | result[epoch] = claimed[epoch][account]; 172 | } 173 | return result; 174 | } 175 | 176 | function getClaimingDeadlines() external view returns (uint64[] memory) { 177 | return claimingDeadlines; 178 | } 179 | 180 | function setClaimingDeadline( 181 | uint32 epoch, 182 | uint64 claimingDeadline 183 | ) external onlyOwner { 184 | require( 185 | epoch < claimingDeadlines.length, 186 | "epoch hasn't been registered." 187 | ); 188 | claimingDeadlines[epoch] = claimingDeadline; 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /lba/contracts/StakingV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./interfaces/IStakingV2.sol"; 5 | import "./interfaces/IStaking.sol"; 6 | import "./interfaces/ISanctionsList.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 8 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 9 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 10 | 11 | contract StakingV2 is IStakingV2, OwnableUpgradeable { 12 | uint32 constant INF = type(uint32).max; 13 | uint128 constant ONE = 10 ** 18; 14 | 15 | address vrtxToken; 16 | address sanctions; 17 | address stakingV1; 18 | 19 | Config defaultConfig; 20 | uint128 totalVrtx; 21 | uint128 totalLiquid; 22 | uint128 migrationBonusPool; 23 | 24 | GlobalYieldsBreakdown[] globalYieldsBreakdown; 25 | 26 | mapping(address => LastActionTimes) lastActionTimes; 27 | mapping(address => uint128) liquidShares; 28 | mapping(address => ReleaseSchedule) releaseSchedules; 29 | mapping(address => Config) configs; 30 | mapping(address => State) states; 31 | mapping(address => address) tradingWallet; 32 | 33 | /// @custom:oz-upgrades-unsafe-allow constructor 34 | constructor() { 35 | _disableInitializers(); 36 | } 37 | 38 | modifier onlyV1() { 39 | require(msg.sender == stakingV1, "Not V1"); 40 | _; 41 | } 42 | 43 | function initialize( 44 | address _vrtxToken, 45 | address _sanctions, 46 | address _stakingV1, 47 | uint32 _withdrawLockingTime 48 | ) external initializer { 49 | __Ownable_init(); 50 | vrtxToken = _vrtxToken; 51 | sanctions = _sanctions; 52 | stakingV1 = _stakingV1; 53 | defaultConfig = Config(_withdrawLockingTime, 0, ONE / 10, 0); 54 | } 55 | 56 | function _stake(address staker, uint128 amount) internal { 57 | uint128 liquid; 58 | if (totalVrtx > 0) { 59 | liquid = uint128((uint256(totalLiquid) * amount) / totalVrtx); 60 | totalLiquid += liquid; 61 | totalVrtx += amount; 62 | liquidShares[staker] += liquid; 63 | } else { 64 | require(amount > ONE, "First stake at least 1 VRTX"); 65 | liquid = amount; 66 | totalLiquid = amount; 67 | totalVrtx = amount; 68 | liquidShares[staker] = amount; 69 | } 70 | states[staker].cumulativeStakedAmount += amount; 71 | states[staker].currentStakedAmount += amount; 72 | uint64 currentTime = uint64(block.timestamp); 73 | lastActionTimes[staker].lastStakeTime = currentTime; 74 | 75 | emit ModifyStake(staker, int128(amount), int128(liquid)); 76 | } 77 | 78 | // stake as `staker`, but VRTX is transferred from `msg.sender`. 79 | function stakeAs(address staker, uint128 amount) public { 80 | require(amount > 0, "Trying to stake 0 tokens."); 81 | require( 82 | !ISanctionsList(sanctions).isSanctioned(staker), 83 | "address is sanctioned." 84 | ); 85 | SafeERC20.safeTransferFrom( 86 | IERC20(vrtxToken), 87 | msg.sender, 88 | address(this), 89 | amount 90 | ); 91 | uint64 currentTime = uint64(block.timestamp); 92 | uint64 v2BonusDeadline = IStaking(stakingV1).getV2BonusDeadline(); 93 | if (v2BonusDeadline > 0 && currentTime <= v2BonusDeadline) { 94 | uint128 bonus = amount / 40; 95 | require(migrationBonusPool >= bonus, "insufficient bonus pool"); 96 | migrationBonusPool -= bonus; 97 | amount += bonus; 98 | } 99 | _stake(staker, amount); 100 | } 101 | 102 | function stake(uint128 amount) external { 103 | stakeAs(msg.sender, amount); 104 | } 105 | 106 | function migrationBonusDeposit(uint128 amount) external onlyOwner { 107 | require(amount > 0, "Trying to deposit 0 tokens."); 108 | SafeERC20.safeTransferFrom( 109 | IERC20(vrtxToken), 110 | msg.sender, 111 | address(this), 112 | amount 113 | ); 114 | migrationBonusPool += amount; 115 | } 116 | 117 | function migrationBonusWithdraw() external onlyOwner { 118 | require(migrationBonusPool > 0, "Trying to withdraw 0 tokens."); 119 | SafeERC20.safeTransfer( 120 | IERC20(vrtxToken), 121 | msg.sender, 122 | migrationBonusPool 123 | ); 124 | migrationBonusPool = 0; 125 | } 126 | 127 | function migrate( 128 | address staker, 129 | uint128 amount, 130 | uint128 bonus 131 | ) external onlyV1 { 132 | require(amount > 0, "Trying to migrate 0 tokens."); 133 | require( 134 | 40 * bonus <= amount * 3, 135 | "bonus/amount should less or equal to 7.5%" 136 | ); 137 | require(bonus <= migrationBonusPool, "insufficient bonus pool"); 138 | require( 139 | !ISanctionsList(sanctions).isSanctioned(staker), 140 | "address is sanctioned." 141 | ); 142 | SafeERC20.safeTransferFrom( 143 | IERC20(vrtxToken), 144 | stakingV1, 145 | address(this), 146 | amount 147 | ); 148 | migrationBonusPool -= bonus; 149 | _stake(staker, amount + bonus); 150 | } 151 | 152 | function _getVrtxBalance(address account) internal view returns (uint128) { 153 | if (totalLiquid == 0) return 0; 154 | return 155 | uint128((uint256(liquidShares[account]) * totalVrtx) / totalLiquid); 156 | } 157 | 158 | function _withdraw(address account) internal returns (uint128 amount) { 159 | amount = _getVrtxBalance(account); 160 | require(amount > 0, "Trying to withdraw 0 staked tokens"); 161 | require( 162 | !ISanctionsList(sanctions).isSanctioned(account), 163 | "address is sanctioned." 164 | ); 165 | uint64 currentTime = uint64(block.timestamp); 166 | uint64 withdrawableTime = _getWithdrawableTime(account); 167 | require(currentTime >= withdrawableTime, "not yet time to withdraw"); 168 | 169 | uint128 liquid = liquidShares[account]; 170 | totalVrtx -= amount; 171 | totalLiquid -= liquid; 172 | delete liquidShares[account]; 173 | states[account].cumulativeWithdrawnAmount += amount; 174 | states[account].currentStakedAmount = 0; 175 | 176 | lastActionTimes[account].lastWithdrawTime = currentTime; 177 | emit ModifyStake(account, -int128(amount), -int128(liquid)); 178 | } 179 | 180 | function withdraw() external { 181 | address sender = msg.sender; 182 | uint128 amount = _withdraw(sender); 183 | 184 | Config memory config = _getConfig(sender); 185 | uint128 toDistributeAmount = uint128( 186 | (uint256(amount) * config.toDistributeRatio) / ONE 187 | ); 188 | uint128 toTreasuryAmount = uint128( 189 | (uint256(amount) * config.toTreasuryRatio) / ONE 190 | ); 191 | uint128 burnAmount = toDistributeAmount + toTreasuryAmount; 192 | require(burnAmount < amount, "No VRTX after burning"); 193 | uint128 withdrawAmount = amount - burnAmount; 194 | 195 | if (totalLiquid > ONE) { 196 | totalVrtx += toDistributeAmount; 197 | } else { 198 | toTreasuryAmount += toDistributeAmount; 199 | } 200 | states[sender].cumulativeBurnedAmount += burnAmount; 201 | if (toTreasuryAmount > 0) { 202 | SafeERC20.safeTransfer( 203 | IERC20(vrtxToken), 204 | owner(), 205 | uint256(toTreasuryAmount) 206 | ); 207 | } 208 | 209 | SafeERC20.safeTransfer( 210 | IERC20(vrtxToken), 211 | sender, 212 | uint256(withdrawAmount) 213 | ); 214 | } 215 | 216 | function withdrawSlow() external { 217 | address sender = msg.sender; 218 | uint128 amount = _withdraw(sender); 219 | 220 | require( 221 | releaseSchedules[sender].amount == 0, 222 | "Having Scheduled VRTX Withdraw" 223 | ); 224 | uint64 currentTime = uint64(block.timestamp); 225 | Config memory config = _getConfig(sender); 226 | releaseSchedules[sender] = ReleaseSchedule({ 227 | releaseTime: currentTime + config.withdrawLockingTime, 228 | amount: amount 229 | }); 230 | } 231 | 232 | function claimWithdraw() external { 233 | address sender = msg.sender; 234 | require( 235 | !ISanctionsList(sanctions).isSanctioned(sender), 236 | "address is sanctioned." 237 | ); 238 | ReleaseSchedule memory schedule = releaseSchedules[sender]; 239 | require(schedule.amount > 0, "No Withdraw scheduled."); 240 | uint64 currentTime = uint64(block.timestamp); 241 | require( 242 | currentTime >= schedule.releaseTime, 243 | "Scheduled Time Not Arrived" 244 | ); 245 | uint128 amount = schedule.amount; 246 | delete releaseSchedules[sender]; 247 | SafeERC20.safeTransfer(IERC20(vrtxToken), sender, uint256(amount)); 248 | } 249 | 250 | function distributeRewards( 251 | uint128 baseAmount, 252 | uint128 feesAmount, 253 | uint128 usdcAmount 254 | ) external onlyOwner { 255 | uint128 amount = baseAmount + feesAmount; 256 | require(amount > 0, "must distribute non-zero rewards."); 257 | require(totalVrtx > ONE, "cannot distribute if no VRTX staked"); 258 | address sender = msg.sender; 259 | SafeERC20.safeTransferFrom( 260 | IERC20(vrtxToken), 261 | sender, 262 | address(this), 263 | uint256(amount) 264 | ); 265 | globalYieldsBreakdown.push( 266 | GlobalYieldsBreakdown({ 267 | distributionTime: uint64(block.timestamp), 268 | baseYieldAmount: baseAmount, 269 | feesYieldAmount: feesAmount, 270 | totalVrtxBalance: totalVrtx, 271 | usdcAmount: usdcAmount 272 | }) 273 | ); 274 | totalVrtx += amount; 275 | } 276 | 277 | function connectTradingWallet(address wallet) external { 278 | tradingWallet[msg.sender] = wallet; 279 | emit ConnectTradingWallet(msg.sender, wallet); 280 | } 281 | 282 | function getTradingWallet(address account) public view returns (address) { 283 | address wallet = tradingWallet[account]; 284 | if (wallet == address(0)) { 285 | wallet = account; 286 | } 287 | return wallet; 288 | } 289 | 290 | function getReleaseSchedule( 291 | address account 292 | ) external view returns (ReleaseSchedule memory releaseSchedule) { 293 | releaseSchedule = releaseSchedules[account]; 294 | } 295 | 296 | function getVrtxBalance(address account) external view returns (uint128) { 297 | return _getVrtxBalance(account); 298 | } 299 | 300 | function getTotalVrtxBalance() external view returns (uint128) { 301 | return totalVrtx; 302 | } 303 | 304 | function getLastActionTimes( 305 | address account 306 | ) external view returns (LastActionTimes memory) { 307 | return lastActionTimes[account]; 308 | } 309 | 310 | function getTotalLiquid() external view returns (uint128) { 311 | return totalLiquid; 312 | } 313 | 314 | function _getConfig(address account) internal view returns (Config memory) { 315 | Config memory config = configs[account]; 316 | if (config.withdrawLockingTime == 0) 317 | config.withdrawLockingTime = defaultConfig.withdrawLockingTime; 318 | if (config.minimumStakingPeriod == 0) 319 | config.minimumStakingPeriod = defaultConfig.minimumStakingPeriod; 320 | if (config.toDistributeRatio == 0) 321 | config.toDistributeRatio = defaultConfig.toDistributeRatio; 322 | if (config.toTreasuryRatio == 0) 323 | config.toTreasuryRatio = defaultConfig.toTreasuryRatio; 324 | return config; 325 | } 326 | 327 | function getConfig(address account) external view returns (Config memory) { 328 | return _getConfig(account); 329 | } 330 | 331 | function getDefaultConfig() external view returns (Config memory) { 332 | return defaultConfig; 333 | } 334 | 335 | function getState(address account) external view returns (State memory) { 336 | return states[account]; 337 | } 338 | 339 | function _getWithdrawableTime( 340 | address account 341 | ) internal view returns (uint64) { 342 | return 343 | _getConfig(account).minimumStakingPeriod + 344 | lastActionTimes[account].lastStakeTime; 345 | } 346 | 347 | function getWithdrawableTime( 348 | address account 349 | ) external view returns (uint64) { 350 | return _getWithdrawableTime(account); 351 | } 352 | 353 | function getMigrationBonusPool() external view returns (uint128) { 354 | return migrationBonusPool; 355 | } 356 | 357 | function updateConfig( 358 | address account, 359 | Config memory config 360 | ) external onlyOwner { 361 | configs[account] = config; 362 | } 363 | 364 | function updateDefaultConfig(Config memory config) external onlyOwner { 365 | defaultConfig = config; 366 | } 367 | 368 | function getGlobalYieldsBreakdown() 369 | external 370 | view 371 | returns (GlobalYieldsBreakdown[] memory) 372 | { 373 | return globalYieldsBreakdown; 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /lba/contracts/VertexToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; 5 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 6 | import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; 7 | import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; 8 | 9 | contract VertexToken is Initializable, ERC20Upgradeable, OwnableUpgradeable { 10 | using EnumerableSet for EnumerableSet.AddressSet; 11 | 12 | event MintAccessGranted(address indexed minter); 13 | event BurnAccessGranted(address indexed burner); 14 | event MintAccessRevoked(address indexed minter); 15 | event BurnAccessRevoked(address indexed burner); 16 | 17 | EnumerableSet.AddressSet private minters; 18 | EnumerableSet.AddressSet private burners; 19 | 20 | /// @custom:oz-upgrades-unsafe-allow constructor 21 | constructor() { 22 | _disableInitializers(); 23 | } 24 | 25 | function initialize() external initializer { 26 | __ERC20_init("Vertex", "VRTX"); 27 | __Ownable_init(); 28 | } 29 | 30 | function mint(address account, uint256 amount) external onlyMinter { 31 | _mint(account, amount); 32 | } 33 | 34 | function burn(uint256 amount) external onlyBurner { 35 | _burn(msg.sender, amount); 36 | } 37 | 38 | function grantMintRole(address minter) external onlyOwner { 39 | if (minters.add(minter)) { 40 | emit MintAccessGranted(minter); 41 | } 42 | } 43 | 44 | function grantBurnRole(address burner) external onlyOwner { 45 | if (burners.add(burner)) { 46 | emit BurnAccessGranted(burner); 47 | } 48 | } 49 | 50 | function revokeMintRole(address minter) external onlyOwner { 51 | if (minters.remove(minter)) { 52 | emit MintAccessRevoked(minter); 53 | } 54 | } 55 | 56 | function revokeBurnRole(address burner) external onlyOwner { 57 | if (burners.remove(burner)) { 58 | emit BurnAccessRevoked(burner); 59 | } 60 | } 61 | 62 | function getMinters() external view returns (address[] memory) { 63 | return minters.values(); 64 | } 65 | 66 | function getBurners() external view returns (address[] memory) { 67 | return burners.values(); 68 | } 69 | 70 | modifier onlyMinter() { 71 | require(minters.contains(msg.sender), "only minter can mint tokens."); 72 | _; 73 | } 74 | 75 | modifier onlyBurner() { 76 | require(burners.contains(msg.sender), "only burner can burn tokens."); 77 | _; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lba/contracts/Vesting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | import "./interfaces/IVesting.sol"; 5 | import "./interfaces/ISanctionsList.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 8 | import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; 9 | 10 | contract Vesting is IVesting, OwnableUpgradeable { 11 | struct VestingSchedule { 12 | address beneficiary; 13 | uint64 startTime; 14 | uint64 endTime; 15 | uint256 amount; 16 | } 17 | 18 | address token; 19 | address sanctions; 20 | uint64 totalVestingSchedules; 21 | mapping(uint64 => VestingSchedule) vestingSchedules; 22 | mapping(uint64 => uint256) claimedAmounts; 23 | mapping(address => uint64[]) vestingScheduleIds; 24 | 25 | /// @custom:oz-upgrades-unsafe-allow constructor 26 | constructor() { 27 | _disableInitializers(); 28 | } 29 | 30 | function initialize( 31 | address _token, 32 | address _sanctions 33 | ) external initializer { 34 | __Ownable_init(); 35 | token = _token; 36 | sanctions = _sanctions; 37 | } 38 | 39 | function _pruneExpiredVestingSchedules(address account) internal { 40 | uint64 currentTime = uint64(block.timestamp); 41 | uint32 i = 0; 42 | while (i < vestingScheduleIds[account].length) { 43 | if ( 44 | vestingSchedules[vestingScheduleIds[account][i]].endTime <= 45 | currentTime 46 | ) { 47 | vestingScheduleIds[account][i] = vestingScheduleIds[account][ 48 | vestingScheduleIds[account].length - 1 49 | ]; 50 | vestingScheduleIds[account].pop(); 51 | } else { 52 | i += 1; 53 | } 54 | } 55 | } 56 | 57 | function registerVesting( 58 | address account, 59 | uint256 amount, 60 | uint64 period 61 | ) external onlyOwner { 62 | SafeERC20.safeTransferFrom( 63 | IERC20(token), 64 | msg.sender, 65 | address(this), 66 | amount 67 | ); 68 | uint64 currentTime = uint64(block.timestamp); 69 | uint64 vestingScheduleId = totalVestingSchedules++; 70 | vestingScheduleIds[account].push(vestingScheduleId); 71 | vestingSchedules[vestingScheduleId] = VestingSchedule({ 72 | beneficiary: account, 73 | startTime: currentTime, 74 | endTime: currentTime + period, 75 | amount: amount 76 | }); 77 | } 78 | 79 | function getVestable( 80 | uint64 vestingScheduleId 81 | ) public view returns (uint256 vestable) { 82 | uint64 currentTime = uint64(block.timestamp); 83 | VestingSchedule memory vestingSchedule = vestingSchedules[ 84 | vestingScheduleId 85 | ]; 86 | if (currentTime < vestingSchedule.startTime) { 87 | vestable = 0; 88 | } else if (currentTime >= vestingSchedule.endTime) { 89 | vestable = vestingSchedule.amount; 90 | } else { 91 | uint64 elpased = currentTime - vestingSchedule.startTime; 92 | uint64 total = vestingSchedule.endTime - vestingSchedule.startTime; 93 | vestable = (vestingSchedule.amount * elpased) / total; 94 | } 95 | } 96 | 97 | function getVested(uint64 vestingScheduleId) public view returns (uint256) { 98 | return claimedAmounts[vestingScheduleId]; 99 | } 100 | 101 | function getClaimable( 102 | address account 103 | ) external view returns (uint256 claimable) { 104 | for (uint32 i = 0; i < vestingScheduleIds[account].length; i++) { 105 | uint64 vestingScheduleId = vestingScheduleIds[account][i]; 106 | claimable += 107 | getVestable(vestingScheduleId) - 108 | getVested(vestingScheduleId); 109 | } 110 | } 111 | 112 | function claim() external { 113 | address sender = msg.sender; 114 | require( 115 | !ISanctionsList(sanctions).isSanctioned(sender), 116 | "address is sanctioned." 117 | ); 118 | uint256 totalClaimable = 0; 119 | for (uint32 i = 0; i < vestingScheduleIds[sender].length; i++) { 120 | uint64 vestingScheduleId = vestingScheduleIds[sender][i]; 121 | uint256 claimable = getVestable(vestingScheduleId) - 122 | getVested(vestingScheduleId); 123 | claimedAmounts[vestingScheduleId] += claimable; 124 | totalClaimable += claimable; 125 | } 126 | if (totalClaimable != 0) { 127 | SafeERC20.safeTransfer(IERC20(token), sender, totalClaimable); 128 | } 129 | _pruneExpiredVestingSchedules(sender); 130 | } 131 | 132 | function getVestingSchedule( 133 | uint64 vestingScheduleId 134 | ) external view returns (VestingSchedule memory) { 135 | return vestingSchedules[vestingScheduleId]; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lba/contracts/interfaces/IAirdrop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IAirdrop { 5 | function claimToLBA( 6 | uint256 amount, 7 | uint256 totalAmount, 8 | bytes32[] calldata proof 9 | ) external; 10 | 11 | function claim( 12 | uint32 epoch, 13 | uint256 amount, 14 | uint256 totalAmount, 15 | bytes32[] calldata proof 16 | ) external; 17 | 18 | function getClaimed( 19 | address account 20 | ) external view returns (uint256[] memory); 21 | 22 | function getClaimingDeadlines() external view returns (uint64[] memory); 23 | } 24 | -------------------------------------------------------------------------------- /lba/contracts/interfaces/IEndpoint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IEndpoint { 5 | event SubmitTransactions(); 6 | 7 | event SubmitSlowModeTransaction( 8 | uint64 executableAt, 9 | address sender, 10 | bytes tx 11 | ); 12 | 13 | // events that we parse transactions into 14 | enum TransactionType { 15 | LiquidateSubaccount, 16 | DepositCollateral, 17 | WithdrawCollateral, 18 | SpotTick, 19 | UpdatePrice, 20 | SettlePnl, 21 | MatchOrders, 22 | DepositInsurance, 23 | ExecuteSlowMode, 24 | MintLp, 25 | BurnLp, 26 | SwapAMM, 27 | MatchOrderAMM, 28 | DumpFees, 29 | ClaimSequencerFees, 30 | PerpTick, 31 | ManualAssert, 32 | Rebate, 33 | UpdateProduct, 34 | LinkSigner, 35 | UpdateFeeRates, 36 | BurnLpAndTransfer 37 | } 38 | 39 | struct MintLp { 40 | bytes32 sender; 41 | uint32 productId; 42 | uint128 amountBase; 43 | uint128 quoteAmountLow; 44 | uint128 quoteAmountHigh; 45 | uint64 nonce; 46 | } 47 | 48 | struct BurnLpAndTransfer { 49 | bytes32 sender; 50 | uint32 productId; 51 | uint128 amount; 52 | bytes32 recipient; 53 | } 54 | 55 | function depositCollateral( 56 | bytes12 subaccountName, 57 | uint32 productId, 58 | uint128 amount 59 | ) external; 60 | 61 | function submitSlowModeTransaction(bytes calldata transaction) external; 62 | 63 | function getPriceX18(uint32 productId) external view returns (int128); 64 | } 65 | -------------------------------------------------------------------------------- /lba/contracts/interfaces/ILBA.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface ILBA { 5 | enum Stage { 6 | NotStarted, 7 | DepositingTokens, 8 | WithdrawingUsdc, 9 | LBAFinished, 10 | DepositedToVertex, 11 | LpMinted, 12 | LpVesting, 13 | LpVested 14 | } 15 | 16 | struct Config { 17 | uint64 depositStartTime; 18 | uint64 depositEndTime; 19 | uint64 withdrawEndTime; 20 | uint64 lpVestStartTime; 21 | uint64 lpVestEndTime; 22 | } 23 | 24 | struct State { 25 | uint256 totalVrtxDeposited; 26 | uint256 totalUsdcDeposited; 27 | uint256 totalLpMinted; 28 | uint256 totalLpWithdrawn; 29 | uint256 cumulativeRewardsPerShareX18; 30 | } 31 | 32 | function getStage() external view returns (Stage stage); 33 | 34 | function depositVrtx(address account, uint256 amount) external; 35 | 36 | function depositUsdc(uint256 amount) external; 37 | 38 | function getDepositedVrtx(address account) external view returns (uint256); 39 | 40 | function getDepositedUsdc(address account) external view returns (uint256); 41 | 42 | function getMaxWithdrawableUsdc( 43 | address account 44 | ) external view returns (uint256); 45 | 46 | function withdrawUsdc(uint256 amount) external; 47 | 48 | function getVrtxInitialPriceX18() external view returns (uint256); 49 | 50 | function getLpBalance(address account) external view returns (uint256); 51 | 52 | function getLockedLpBalance( 53 | address account 54 | ) external view returns (uint256 lockedLpBalance); 55 | 56 | function getWithdrawableLpBalance( 57 | address account 58 | ) external view returns (uint256); 59 | 60 | function withdrawLiquidity(uint256 lpAmount) external; 61 | 62 | function getConfig() external view returns (Config memory); 63 | 64 | function getState() external view returns (State memory); 65 | 66 | function getClaimableRewards( 67 | address account 68 | ) external view returns (uint256); 69 | 70 | function claimRewards() external; 71 | 72 | function distributeRewards(uint256 amount) external; 73 | } 74 | -------------------------------------------------------------------------------- /lba/contracts/interfaces/IStaking.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IStaking { 5 | struct Segment { 6 | uint64 startTime; 7 | int256 vrtxSize; 8 | address owner; 9 | uint32 version; 10 | } 11 | 12 | struct QueueIndex { 13 | uint64 count; 14 | uint64 upTo; 15 | } 16 | 17 | struct State { 18 | uint256 vrtxStaked; 19 | int256 sumSize; 20 | int256 sumSizeXTime; 21 | } 22 | 23 | struct ReleaseSchedule { 24 | uint64 releaseTime; 25 | uint256 amount; 26 | } 27 | 28 | struct Checkpoint { 29 | uint64 time; 30 | uint256 vrtxStaked; 31 | int256 sumSize; 32 | int256 sumSizeXTime; 33 | uint256 rewards; 34 | } 35 | 36 | struct LastActionTimes { 37 | uint64 lastStakeTime; 38 | uint64 lastWithdrawTime; 39 | } 40 | 41 | struct WithdrawnVrtxStates { 42 | uint256 vrtxClaimable; 43 | uint256 vrtxPendingUnlock; 44 | } 45 | 46 | function stake(uint256 amount) external; 47 | 48 | function withdraw(uint256 amount) external; 49 | 50 | function claimVrtx() external; 51 | 52 | function claimUsdc() external; 53 | 54 | function getWithdrawnVrtxStates( 55 | address account 56 | ) external view returns (WithdrawnVrtxStates memory); 57 | 58 | function getRewardsBreakdown( 59 | address account 60 | ) external view returns (uint256[] memory); 61 | 62 | function getUsdcClaimable(address account) external view returns (uint256); 63 | 64 | function getVrtxStaked(address account) external view returns (uint256); 65 | 66 | function getTotalVrtxStaked() external view returns (uint256); 67 | 68 | function getScore(address account) external view returns (uint256); 69 | 70 | function getTotalScore() external view returns (uint256); 71 | 72 | function getLastActionTimes( 73 | address account 74 | ) external view returns (LastActionTimes memory); 75 | } 76 | -------------------------------------------------------------------------------- /lba/contracts/interfaces/IVesting.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-or-later 2 | pragma solidity ^0.8.0; 3 | 4 | interface IVesting { 5 | function registerVesting( 6 | address account, 7 | uint256 amount, 8 | uint64 period 9 | ) external; 10 | 11 | function getVested( 12 | uint64 vestingScheduleId 13 | ) external view returns (uint256); 14 | 15 | function getVestable( 16 | uint64 vestingScheduleId 17 | ) external view returns (uint256); 18 | 19 | function getClaimable(address account) external view returns (uint256); 20 | 21 | function claim() external; 22 | } 23 | -------------------------------------------------------------------------------- /lba/env.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | 3 | declare global { 4 | // eslint-disable-next-line @typescript-eslint/no-namespace 5 | namespace NodeJS { 6 | interface ProcessEnv { 7 | VERBOSE?: string; 8 | AUTOMINE_INTERVAL?: string; 9 | DEFAULT_NETWORK?: string; 10 | } 11 | } 12 | } 13 | 14 | interface EnvVars { 15 | verbose: boolean; 16 | automineInterval?: number; 17 | defaultNetwork?: string; 18 | } 19 | 20 | const automineInterval = Number(process.env.AUTOMINE_INTERVAL); 21 | 22 | export const env: EnvVars = { 23 | verbose: process.env.VERBOSE === "TRUE", 24 | automineInterval: Number.isFinite(automineInterval) 25 | ? automineInterval 26 | : undefined, 27 | defaultNetwork: process.env.DEFAULT_NETWORK, 28 | }; 29 | 30 | // Disable console.debug for non-verbose 31 | if (!env.verbose) { 32 | // eslint-disable-next-line @typescript-eslint/no-empty-function 33 | console.debug = function () {}; 34 | } 35 | -------------------------------------------------------------------------------- /lba/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @type import('hardhat/config').HardhatUserConfig 3 | */ 4 | import "@nomicfoundation/hardhat-chai-matchers"; 5 | import "@nomiclabs/hardhat-ethers"; 6 | import "@nomiclabs/hardhat-etherscan"; 7 | import "@nomiclabs/hardhat-solhint"; 8 | import "@openzeppelin/hardhat-upgrades"; 9 | import "@typechain/hardhat"; 10 | import "dotenv/config"; 11 | import "hardhat-deploy"; 12 | import "solidity-coverage"; 13 | import { HardhatUserConfig } from "hardhat/config"; 14 | 15 | // Custom tasks 16 | import "./tasks"; 17 | import { env } from "./env"; 18 | 19 | const config: HardhatUserConfig = { 20 | solidity: { 21 | version: "0.8.13", 22 | settings: { 23 | optimizer: { 24 | enabled: true, 25 | runs: 1000, 26 | }, 27 | }, 28 | }, 29 | defaultNetwork: env.defaultNetwork ?? "local", 30 | networks: { 31 | local: { 32 | chainId: 1337, 33 | // Automine for testing, periodic mini 34 | mining: { 35 | auto: !env.automineInterval, 36 | interval: env.automineInterval, 37 | }, 38 | allowUnlimitedContractSize: true, 39 | url: "http://0.0.0.0:8545", 40 | }, 41 | hardhat: { 42 | chainId: 1337, 43 | }, 44 | "sepolia-test": { 45 | // [deployer, sequencer] (keys are fetched at runtime) 46 | accounts: [], 47 | url: "https://arbitrum-sepolia.infura.io/v3/6ec1f9738a3a46b7af2d85e8a07a96e8", 48 | }, 49 | prod: { 50 | // [deployer, sequencer] (keys are fetched at runtime) 51 | accounts: [], 52 | url: "https://arb1.arbitrum.io/rpc", 53 | }, 54 | "blast-test": { 55 | // [deployer, sequencer] (keys are fetched at runtime) 56 | accounts: [], 57 | url: "https://orbital-spring-bush.blast-sepolia.quiknode.pro/cce002b2627e40aefcc8b188206fec4572a1b225", 58 | }, 59 | "blast-prod": { 60 | // [deployer, sequencer] (keys are fetched at runtime) 61 | accounts: [], 62 | url: "https://rpc.blast.io", 63 | }, 64 | "mantle-test": { 65 | // [deployer, sequencer] (keys are fetched at runtime) 66 | accounts: [], 67 | url: "https://rpc.sepolia.mantle.xyz", 68 | }, 69 | "mantle-prod": { 70 | // [deployer, sequencer] (keys are fetched at runtime) 71 | accounts: [], 72 | url: "https://thrilling-sleek-mansion.mantle-mainnet.quiknode.pro/0aca8f7bac033c42843e041b2ab43dfa36f4f59d", 73 | }, 74 | "sei-test": { 75 | // [deployer, sequencer] (keys are fetched at runtime) 76 | accounts: [], 77 | url: "https://lively-small-wave.sei-atlantic.quiknode.pro/fda3c38ead9aa11afb2b4aeb16916f11c4f381a1", 78 | }, 79 | "sei-prod": { 80 | // [deployer, sequencer] (keys are fetched at runtime) 81 | accounts: [], 82 | url: "https://tiniest-solemn-wish.sei-pacific.quiknode.pro/dff8d3abf2754d0c8aa58cd2360a3638e68f02ff", 83 | }, 84 | "base-test": { 85 | // [deployer, sequencer] (keys are fetched at runtime) 86 | accounts: [], 87 | url: "https://fittest-cold-county.base-sepolia.quiknode.pro/3d08b02db8b5327853fcc7b233e1db60940c7056", 88 | }, 89 | "base-prod": { 90 | // [deployer, sequencer] (keys are fetched at runtime) 91 | accounts: [], 92 | url: "https://side-dark-borough.base-mainnet.quiknode.pro/54a4ef64b532c1f9139e5b7e007b23a3ec307098/", 93 | }, 94 | }, 95 | paths: { 96 | tests: "./tests", 97 | }, 98 | etherscan: { 99 | apiKey: { 100 | prod: "B8N4V8ZXEXT2HTWEA5JN68NJ2GD4BRQXAZ", 101 | "sepolia-test": "B8N4V8ZXEXT2HTWEA5JN68NJ2GD4BRQXAZ", 102 | "mantle-test": "B8N4V8ZXEXT2HTWEA5JN68NJ2GD4BRQXAZ", 103 | "mantle-prod": "B8N4V8ZXEXT2HTWEA5JN68NJ2GD4BRQXAZ", 104 | "blast-test": "B8N4V8ZXEXT2HTWEA5JN68NJ2GD4BRQXAZ", 105 | "blast-prod": "MUFXMVYWU9VKIP5QZMNNZN4GA5SVAERDUW", 106 | "sei-test": "B8N4V8ZXEXT2HTWEA5JN68NJ2GD4BRQXAZ", 107 | "sei-prod": "B8N4V8ZXEXT2HTWEA5JN68NJ2GD4BRQXAZ", 108 | "base-test": "QP14D6T7U98VDES41IE8J2WX6QC4DPQ41N", 109 | "base-prod": "QP14D6T7U98VDES41IE8J2WX6QC4DPQ41N", 110 | }, 111 | customChains: [ 112 | { 113 | network: "prod", 114 | chainId: 42161, 115 | urls: { 116 | apiURL: "https://api.arbiscan.io/api", 117 | browserURL: "https://arbiscan.io/", 118 | }, 119 | }, 120 | { 121 | network: "sepolia-test", 122 | chainId: 421614, 123 | urls: { 124 | apiURL: "https://api-sepolia.arbiscan.io/api", 125 | browserURL: "https://sepolia.arbiscan.io/", 126 | }, 127 | }, 128 | { 129 | network: "mantle-test", 130 | chainId: 5003, 131 | urls: { 132 | apiURL: "https://explorer.sepolia.mantle.xyz/api", 133 | browserURL: "https://explorer.sepolia.mantle.xyz", 134 | }, 135 | }, 136 | { 137 | network: "mantle-prod", 138 | chainId: 5000, 139 | urls: { 140 | apiURL: "https://explorer.mantle.xyz/api", 141 | browserURL: "https://explorer.mantle.xyz", 142 | }, 143 | }, 144 | { 145 | network: "blast-test", 146 | chainId: 168587773, 147 | urls: { 148 | apiURL: 149 | "https://api.routescan.io/v2/network/testnet/evm/168587773/etherscan", 150 | browserURL: "https://testnet.blastscan.io", 151 | }, 152 | }, 153 | { 154 | network: "blast-prod", 155 | chainId: 81457, 156 | urls: { 157 | apiURL: "https://api.blastscan.io/api", 158 | browserURL: "https://blastscan.io", 159 | }, 160 | }, 161 | { 162 | network: "sei-test", 163 | chainId: 1328, 164 | urls: { 165 | apiURL: "https://seitrace.com/atlantic-2/api", 166 | browserURL: "https://seitrace.com/?chain=atlantic-2", 167 | }, 168 | }, 169 | { 170 | network: "sei-prod", 171 | chainId: 1329, 172 | urls: { 173 | apiURL: "https://seitrace.com/pacific-1/api", 174 | browserURL: "https://seitrace.com/?chain=pacific-1", 175 | }, 176 | }, 177 | { 178 | network: "base-test", 179 | chainId: 84532, 180 | urls: { 181 | apiURL: "https://api-sepolia.basescan.org/api", 182 | browserURL: "https://sepolia-explorer.base.org", 183 | }, 184 | }, 185 | { 186 | network: "base-prod", 187 | chainId: 8453, 188 | urls: { 189 | apiURL: "https://api.basescan.org/api", 190 | browserURL: "https://basescan.org", 191 | }, 192 | }, 193 | ], 194 | }, 195 | }; 196 | 197 | export default config; 198 | -------------------------------------------------------------------------------- /lba/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vertex-lba", 3 | "version": "3.4.0", 4 | "license": "UNLICENSED", 5 | "description": "Vertex LBA contracts", 6 | "scripts": { 7 | "prepare": "husky install", 8 | "lint": "eslint './**/*.ts' --fix && prettier --write './**/*.{ts,sol}' --loglevel silent && solhint --fix 'contracts/**/*.sol'", 9 | "contracts:compile": "hardhat compile && hardhat update-abis", 10 | "contracts:force-compile": "TS_NODE_TRANSPILE_ONLY=1 hardhat clean && TS_NODE_TRANSPILE_ONLY=1 hardhat compile && hardhat update-abis", 11 | "run-local-node": "hardhat node --network hardhat --hostname 0.0.0.0", 12 | "postinstall": "patch-package" 13 | }, 14 | "engines": { 15 | "node": ">=16" 16 | }, 17 | "dependencies": { 18 | "@aws-sdk/client-secrets-manager": "^3.306.0", 19 | "@chainlink/contracts": "^0.4.1", 20 | "@ethersproject/abi": "^5.6.4", 21 | "@ethersproject/bignumber": "^5.6.2", 22 | "@ethersproject/bytes": "^5.6.1", 23 | "@ethersproject/providers": "^5.6.8", 24 | "@openzeppelin/contracts": "^4.8.0-rc.2", 25 | "@openzeppelin/contracts-upgradeable": "^4.8.0-rc.2", 26 | "@openzeppelin/merkle-tree": "^1.0.5", 27 | "@arbitrum/sdk": "^v3.1.9", 28 | "aws-sdk": "^2.1352.0", 29 | "evm-bn": "^1.1.1", 30 | "fs-extra": "^10.1.0", 31 | "lodash": "^4.17.21", 32 | "mathjs": "^10.6.1", 33 | "node-fetch": "2", 34 | "prb-math": "^2.4.3", 35 | "redis": "^4.3.0" 36 | }, 37 | "devDependencies": { 38 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.2", 39 | "@nomicfoundation/hardhat-network-helpers": "^1.0.3", 40 | "@nomicfoundation/hardhat-toolbox": "^1.0.2", 41 | "@nomiclabs/hardhat-ethers": "^2.1.0", 42 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 43 | "@nomiclabs/hardhat-solhint": "^2.0.1", 44 | "@openzeppelin/hardhat-upgrades": "^1.25.0", 45 | "@typechain/ethers-v5": "^10.1.0", 46 | "@typechain/hardhat": "^6.1.2", 47 | "@types/chai": "^4.3.1", 48 | "@types/fs-extra": "^9.0.13", 49 | "@types/lodash": "^4.14.182", 50 | "@types/mocha": "^9.1.1", 51 | "@types/node": "^17.0.40", 52 | "@types/node-fetch": "^2.6.2", 53 | "@typescript-eslint/eslint-plugin": "^5.30.7", 54 | "@typescript-eslint/parser": "^5.30.7", 55 | "chai": "^4.3.6", 56 | "dotenv": "^16.0.1", 57 | "eslint": "^8.20.0", 58 | "eslint-config-prettier": "^8.5.0", 59 | "ethers": "^5.6.9", 60 | "hardhat": "^2.10.1", 61 | "hardhat-deploy": "^0.11.10", 62 | "husky": "^8.0.0", 63 | "lerna": "^5.4.0", 64 | "lint-staged": "^13.0.3", 65 | "patch-package": "^8.0.0", 66 | "postinstall-postinstall": "^2.1.0", 67 | "prettier": "^2.6.2", 68 | "prettier-plugin-solidity": "^1.0.0-beta.19", 69 | "solhint": "^3.3.7", 70 | "solidity-coverage": "^0.7.21", 71 | "ts-node": "^10.8.1", 72 | "typechain": "^8.1.0", 73 | "typescript": "^4.7.3" 74 | }, 75 | "lint-staged": { 76 | "*.{ts,sol}": "yarn lint" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lba/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true, 9 | "rootDir": ".", 10 | }, 11 | "include": [ 12 | "./tasks", 13 | "./typechain-types", 14 | "env.ts" 15 | ], 16 | "files": [ 17 | "./hardhat.config.ts" 18 | ] 19 | } --------------------------------------------------------------------------------