-
Notifications
You must be signed in to change notification settings - Fork 533
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 65e62b6
Showing
223 changed files
with
42,436 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
root = true | ||
|
||
[*] | ||
indent_style = space | ||
indent_size = 2 | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = true | ||
|
||
[*.sol] | ||
indent_size = 4 | ||
|
||
[*.md] | ||
trim_trailing_whitespace = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
node_modules/ | ||
typechain/ | ||
env.json | ||
.tmp-addresses*.json | ||
data/gmxMigration | ||
flattened | ||
|
||
#Hardhat files | ||
cache | ||
artifacts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2016-2020 zOS Global Limited | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining | ||
a copy of this software and associated documentation files (the | ||
"Software"), to deal in the Software without restriction, including | ||
without limitation the rights to use, copy, modify, merge, publish, | ||
distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so, subject to | ||
the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included | ||
in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | ||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | ||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | ||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# Gambit Contracts | ||
Contracts for the GMT Token and GMT Treasury. | ||
|
||
## Install Dependencies | ||
If npx is not installed yet: | ||
`npm install -g npx` | ||
|
||
Install packages: | ||
`npm i` | ||
|
||
## Compile Contracts | ||
`npx hardhat compile` | ||
|
||
## Run Tests | ||
`npx hardhat test` | ||
|
||
## Vault | ||
The Vault contract handles buying USDG, selling USDG, swapping, increasing positions, decreasing positions and liquidations. | ||
Overview: https://gambit.gitbook.io/gambit/ | ||
|
||
### Buying USDG | ||
- USDG can be bought with any whitelisted token | ||
- The oracle price is used to determine the amount of USDG that should be minted to the receiver, with 1 USDG being valued at 1 USD | ||
- Fees are collected based on `swapFeeBasisPoints` | ||
- `usdgAmounts` is increased to track the USDG debt of the token | ||
- `poolAmounts` is increased to track the amount of tokens that can be used for swaps or borrowed for margin trading | ||
|
||
### Selling USDG | ||
- USDG can be sold for any whitelisted token | ||
- The oracle price is used to determine the amount of tokens that should be sent to the receiver | ||
- For non-stableTokens, the amount of tokens sent out is additionally capped by the redemption collateral | ||
- To calculate the redemption collateral: | ||
- Convert the value in `guaranteedUsd[token]` from USD to tokens | ||
- Add `poolAmounts[token]` | ||
- Subtract `reservedAmounts[token]` | ||
- The reason for this calculation is because traders can open long positions by borrowing non-stable whitelisted tokens, when these tokens are borrowed the USD value in `guaranteedUsd[token]` is guaranteed until the positions are closed or liquidated | ||
- `reservedAmounts[token]` tracks the amount of tokens in the pool reserved for open positions | ||
- The redemption amount is capped by: `(USDG sold) / (USDG debt) * (redemption collateral) * (redemptionBasisPoints[token]) / BASIS_POINTS_DIVISOR` | ||
- redemptionBasisPoints can be adjusted to allow a larger or smaller amount of redemption | ||
- Fees are collected based on `swapFeeBasisPoints` | ||
- `usdgAmounts` is decreased to reduce the USDG debt of the token | ||
- `poolAmounts` is decreased to reflect the reduction in available collateral for redemption | ||
|
||
### Swap | ||
- Any whitelisted tokens can be swapped for one another | ||
- The oracle prices are used to determine the amount of tokens that should be sent to the receiver | ||
- USDG debt is transferred from the _tokenOut to the _tokenIn | ||
- Fees are collected based on `swapFeeBasisPoints` | ||
- `poolAmounts` are updated to reflect the change in tokens | ||
|
||
### IncreasePosition | ||
- Traders can long and short whitelisted tokens | ||
- For longs, the collateral token must be the same as the index token (the token being longed) | ||
- For shorts, the collateral token must be a stableToken and the index token must not be a stableToken | ||
- For both longs and shorts, the token borrowed from the pool is based on the collateral token | ||
- Fees are collected based on `marginFeeBasisPoints` and funding rates | ||
- Funding rates are calculated based on the `fundingRateFactor` and utilisation of the pool for the token being borrowed | ||
- `reservedAmounts[token]` is increased to ensure there are sufficient tokens to pay profits on the position | ||
- For longs: | ||
- `guaranteedUsd[token]` is updated based on the difference between the position size and the collateral | ||
- `poolAmounts[token]` is increased by the collateral received and considered as part of the pool | ||
- For shorts: | ||
- `guaranteedUsd[token]` is not updated as the collateral token is a stableToken, and no USD amount is additionally guaranteed | ||
- `poolAmounts[token]` is not increased as the collateral is not considered as part of the pool | ||
|
||
### DecreasePosition | ||
- `reservedAmounts[token]` is decreased proportional to the decrease in position size | ||
- For longs: | ||
- The `guaranteedUsd[token]` is updated based on the new difference between the position size and the collateral | ||
- `poolAmounts[token]` is decreased by the amount of USD sent out, since the position's collateral and the position's size are treated as a part of the pool | ||
- For shorts: | ||
- `poolAmounts[token]` is decreased if there are realised profits for the position | ||
- `poolAmounts[token]` is increased if there are realised losses for the position | ||
|
||
### LiquidatePosition | ||
- Any user can liquidate a position if the remaining collateral after losses is lower than `liquidationFeeUsd` or if the `maxLeverage` is exceeded | ||
- `reservedAmounts[token]` is decreased since it is no longer needed for the position | ||
- For longs: | ||
- `guaranteedUsd[token]` is decreased based on the different between the position size and the collateral | ||
- For shorts: | ||
- `poolAmounts[token]` is increased to reflect the additional collateral from the position's losses | ||
|
||
## Front-Running Protection | ||
Oracle results can be known before the result is finalised on-chain. | ||
|
||
This means that a trader could observe oracle results in the mempool or otherwise and enter a favourable position before the result is finalised. | ||
|
||
Over time, this could lead to losses in assets for the system. | ||
|
||
To guard against this attack vector, the last three oracle results are sampled to determine prices. | ||
|
||
This reduces the attack surface as minor fluctuations cannot be exploited for profits. | ||
|
||
Additionally, a `minProfitBasisPoints` configuration is allowed per token. | ||
|
||
If the oracle is updated on every 0.5% price movement, the `minProfitBasisPoints` for the token could be set to 0.75%. | ||
This means that if the profit on a position is less than 0.75%, then the profit would be considered to be 0. | ||
|
||
For buying USDG, selling USDG and swaps, a fee of 0.3% would make trades across a 0.5% price movement unprofitable. | ||
|
||
## Governance | ||
Governance will be set to a timelock contract which would require actions to be broadcasted 5 days in advance before they can be executed. | ||
|
||
This timelock contract will be upgraded to a DAO based contract once the system is stable. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
The major issues raised in the ABDK Audit have been resolved. | ||
|
||
1. CVF-42, CVF-51: The mentioned functions have been removed, the intention of these functions was to support future features. We have decided to keep the supported features compact, when new features are needed the new code will be sent for another audit | ||
|
||
2. CVF-87: The function is meant to be re-callable, token whitelisting is currently controlled by a Timelock contract with a delay of 5 days: https://bscscan.com/address/0x330EeF6b9B1ea6EDd620C825c9919DC8b611d5d5 | ||
|
||
3. CVF-90: The returned value was not affecting any behaviour, but for correctness it has been fixed: https://github.com/xvi10/gambit-contracts/blob/master/contracts/core/Vault.sol#L310 | ||
|
||
4. CVF-130: The spread between prices is assumed to be small, it is large only if the prices are volatile, in which case, a larger spread is desirable to protect the assets in the system |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.6.12; | ||
|
||
contract Governable { | ||
address public gov; | ||
|
||
constructor() public { | ||
gov = msg.sender; | ||
} | ||
|
||
modifier onlyGov() { | ||
require(msg.sender == gov, "Governable: forbidden"); | ||
_; | ||
} | ||
|
||
function setGov(address _gov) external onlyGov { | ||
gov = _gov; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
//SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.6.12; | ||
|
||
import "../libraries/math/SafeMath.sol"; | ||
import "../libraries/token/IERC20.sol"; | ||
import "../libraries/token/ERC721/IERC721.sol"; | ||
import "../libraries/utils/ReentrancyGuard.sol"; | ||
|
||
import "../peripherals/interfaces/ITimelock.sol"; | ||
|
||
contract TokenManager is ReentrancyGuard { | ||
using SafeMath for uint256; | ||
|
||
bool public isInitialized; | ||
|
||
uint256 public actionsNonce; | ||
uint256 public minAuthorizations; | ||
|
||
address public admin; | ||
|
||
address[] public signers; | ||
mapping (address => bool) public isSigner; | ||
mapping (bytes32 => bool) public pendingActions; | ||
mapping (address => mapping (bytes32 => bool)) public signedActions; | ||
|
||
event SignalApprove(address token, address spender, uint256 amount, bytes32 action, uint256 nonce); | ||
event SignalApproveNFT(address token, address spender, uint256 tokenId, bytes32 action, uint256 nonce); | ||
event SignalApproveNFTs(address token, address spender, uint256[] tokenIds, bytes32 action, uint256 nonce); | ||
event SignalSetAdmin(address target, address admin, bytes32 action, uint256 nonce); | ||
event SignalPendingAction(bytes32 action, uint256 nonce); | ||
event SignAction(bytes32 action, uint256 nonce); | ||
event ClearAction(bytes32 action, uint256 nonce); | ||
|
||
constructor(uint256 _minAuthorizations) public { | ||
admin = msg.sender; | ||
minAuthorizations = _minAuthorizations; | ||
} | ||
|
||
modifier onlyAdmin() { | ||
require(msg.sender == admin, "TokenManager: forbidden"); | ||
_; | ||
} | ||
|
||
modifier onlySigner() { | ||
require(isSigner[msg.sender], "TokenManager: forbidden"); | ||
_; | ||
} | ||
|
||
function initialize(address[] memory _signers) public virtual onlyAdmin { | ||
require(!isInitialized, "TokenManager: already initialized"); | ||
isInitialized = true; | ||
|
||
signers = _signers; | ||
for (uint256 i = 0; i < _signers.length; i++) { | ||
address signer = _signers[i]; | ||
isSigner[signer] = true; | ||
} | ||
} | ||
|
||
function signersLength() public view returns (uint256) { | ||
return signers.length; | ||
} | ||
|
||
function signalApprove(address _token, address _spender, uint256 _amount) external nonReentrant onlyAdmin { | ||
actionsNonce++; | ||
uint256 nonce = actionsNonce; | ||
bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount, nonce)); | ||
_setPendingAction(action, nonce); | ||
emit SignalApprove(_token, _spender, _amount, action, nonce); | ||
} | ||
|
||
function signApprove(address _token, address _spender, uint256 _amount, uint256 _nonce) external nonReentrant onlySigner { | ||
bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount, _nonce)); | ||
_validateAction(action); | ||
require(!signedActions[msg.sender][action], "TokenManager: already signed"); | ||
signedActions[msg.sender][action] = true; | ||
emit SignAction(action, _nonce); | ||
} | ||
|
||
function approve(address _token, address _spender, uint256 _amount, uint256 _nonce) external nonReentrant onlyAdmin { | ||
bytes32 action = keccak256(abi.encodePacked("approve", _token, _spender, _amount, _nonce)); | ||
_validateAction(action); | ||
_validateAuthorization(action); | ||
|
||
IERC20(_token).approve(_spender, _amount); | ||
_clearAction(action, _nonce); | ||
} | ||
|
||
function signalApproveNFT(address _token, address _spender, uint256 _tokenId) external nonReentrant onlyAdmin { | ||
actionsNonce++; | ||
uint256 nonce = actionsNonce; | ||
bytes32 action = keccak256(abi.encodePacked("approveNFT", _token, _spender, _tokenId, nonce)); | ||
_setPendingAction(action, nonce); | ||
emit SignalApproveNFT(_token, _spender, _tokenId, action, nonce); | ||
} | ||
|
||
function signApproveNFT(address _token, address _spender, uint256 _tokenId, uint256 _nonce) external nonReentrant onlySigner { | ||
bytes32 action = keccak256(abi.encodePacked("approveNFT", _token, _spender, _tokenId, _nonce)); | ||
_validateAction(action); | ||
require(!signedActions[msg.sender][action], "TokenManager: already signed"); | ||
signedActions[msg.sender][action] = true; | ||
emit SignAction(action, _nonce); | ||
} | ||
|
||
function approveNFT(address _token, address _spender, uint256 _tokenId, uint256 _nonce) external nonReentrant onlyAdmin { | ||
bytes32 action = keccak256(abi.encodePacked("approveNFT", _token, _spender, _tokenId, _nonce)); | ||
_validateAction(action); | ||
_validateAuthorization(action); | ||
|
||
IERC721(_token).approve(_spender, _tokenId); | ||
_clearAction(action, _nonce); | ||
} | ||
|
||
function signalApproveNFTs(address _token, address _spender, uint256[] memory _tokenIds) external nonReentrant onlyAdmin { | ||
actionsNonce++; | ||
uint256 nonce = actionsNonce; | ||
bytes32 action = keccak256(abi.encodePacked("approveNFTs", _token, _spender, _tokenIds, nonce)); | ||
_setPendingAction(action, nonce); | ||
emit SignalApproveNFTs(_token, _spender, _tokenIds, action, nonce); | ||
} | ||
|
||
function signApproveNFTs(address _token, address _spender, uint256[] memory _tokenIds, uint256 _nonce) external nonReentrant onlySigner { | ||
bytes32 action = keccak256(abi.encodePacked("approveNFTs", _token, _spender, _tokenIds, _nonce)); | ||
_validateAction(action); | ||
require(!signedActions[msg.sender][action], "TokenManager: already signed"); | ||
signedActions[msg.sender][action] = true; | ||
emit SignAction(action, _nonce); | ||
} | ||
|
||
function approveNFTs(address _token, address _spender, uint256[] memory _tokenIds, uint256 _nonce) external nonReentrant onlyAdmin { | ||
bytes32 action = keccak256(abi.encodePacked("approveNFTs", _token, _spender, _tokenIds, _nonce)); | ||
_validateAction(action); | ||
_validateAuthorization(action); | ||
|
||
for (uint256 i = 0 ; i < _tokenIds.length; i++) { | ||
IERC721(_token).approve(_spender, _tokenIds[i]); | ||
} | ||
_clearAction(action, _nonce); | ||
} | ||
|
||
function signalSetAdmin(address _target, address _admin) external nonReentrant onlySigner { | ||
actionsNonce++; | ||
uint256 nonce = actionsNonce; | ||
bytes32 action = keccak256(abi.encodePacked("setAdmin", _target, _admin, nonce)); | ||
_setPendingAction(action, nonce); | ||
signedActions[msg.sender][action] = true; | ||
emit SignalSetAdmin(_target, _admin, action, nonce); | ||
} | ||
|
||
function signSetAdmin(address _target, address _admin, uint256 _nonce) external nonReentrant onlySigner { | ||
bytes32 action = keccak256(abi.encodePacked("setAdmin", _target, _admin, _nonce)); | ||
_validateAction(action); | ||
require(!signedActions[msg.sender][action], "TokenManager: already signed"); | ||
signedActions[msg.sender][action] = true; | ||
emit SignAction(action, _nonce); | ||
} | ||
|
||
function setAdmin(address _target, address _admin, uint256 _nonce) external nonReentrant onlySigner { | ||
bytes32 action = keccak256(abi.encodePacked("setAdmin", _target, _admin, _nonce)); | ||
_validateAction(action); | ||
_validateAuthorization(action); | ||
|
||
ITimelock(_target).setAdmin(_admin); | ||
_clearAction(action, _nonce); | ||
} | ||
|
||
function _setPendingAction(bytes32 _action, uint256 _nonce) private { | ||
pendingActions[_action] = true; | ||
emit SignalPendingAction(_action, _nonce); | ||
} | ||
|
||
function _validateAction(bytes32 _action) private view { | ||
require(pendingActions[_action], "TokenManager: action not signalled"); | ||
} | ||
|
||
function _validateAuthorization(bytes32 _action) private view { | ||
uint256 count = 0; | ||
for (uint256 i = 0; i < signers.length; i++) { | ||
address signer = signers[i]; | ||
if (signedActions[signer][_action]) { | ||
count++; | ||
} | ||
} | ||
|
||
if (count == 0) { | ||
revert("TokenManager: action not authorized"); | ||
} | ||
require(count >= minAuthorizations, "TokenManager: insufficient authorization"); | ||
} | ||
|
||
function _clearAction(bytes32 _action, uint256 _nonce) private { | ||
require(pendingActions[_action], "TokenManager: invalid _action"); | ||
delete pendingActions[_action]; | ||
emit ClearAction(_action, _nonce); | ||
} | ||
} |
Oops, something went wrong.