Skip to content

Commit

Permalink
Merge branch 'staging' into s/forge-format
Browse files Browse the repository at this point in the history
  • Loading branch information
magnetto90 committed Jan 18, 2024
2 parents 6c0723c + db84c44 commit eb5fb39
Show file tree
Hide file tree
Showing 72 changed files with 1,072 additions and 2,500 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/test-python.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: Create env file
- name: Create .env
run: |
touch .env
echo WEB3_INFURA_PROJECT_ID=${{ secrets.WEB3_INFURA_PROJECT_ID }} >> .env
echo ETHERSCAN_TOKEN=${{ secrets.ETHERSCAN_TOKEN }} >> .env
echo export ARBISCAN_TOKEN=${{ secrets.ARBISCAN_TOKEN }} >> .env
cat .env
- name: Cache Compiler Installations
uses: actions/cache@v2
Expand All @@ -57,5 +57,9 @@ jobs:
- name: Compile Code
run: brownie compile --size

- name: Configure Arbitrum
run: |
brownie networks add Development arbitrum-main-fork name="Ganache-CLI (Aribtrum-Mainnet Fork)" host=http://127.0.0.1 cmd=ganache-cli accounts=10 evm_version=istanbul fork=arbitrum-main mnemonic=brownie port=8545
brownie networks modify arbitrum-main host="https://arbitrum-mainnet.infura.io/v3/\$WEB3_INFURA_PROJECT_ID" provider=infura
- name: Run Tests
run: brownie test -vv -s --gas
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ reports/

# Foundry
cache
forge-out
forge-out

# Echidna
crytic-export
coverage-echidna
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/Openzeppelin/openzeppelin-contracts
[submodule "lib/v3-core"]
path = lib/v3-core
url = https://github.com/uniswap/v3-core
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/chainlink"]
path = lib/chainlink
url = https://github.com/smartcontractkit/chainlink
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

V1 core smart contracts


## Requirements

To run the project you need:
Expand All @@ -19,28 +18,40 @@ To run the project you need:
```
# required environment variables
export WEB3_INFURA_PROJECT_ID=<INFURA_TOKEN>
export ETHERSCAN_TOKEN=<ETHERSCAN_TOKEN>
export ARBISCAN_TOKEN=<ETHERSCAN_TOKEN>
```

```
# add Arbitrum Fork
brownie networks add Development arbitrum-main-fork name="Ganache-CLI (Aribtrum-Mainnet Fork)" host=http://127.0.0.1 cmd=ganache-cli accounts=10 evm_version=istanbul fork=arbitrum-main mnemonic=brownie port=8545
```

```
# modify network configuration to use API key
brownie networks modify arbitrum-main host="https://arbitrum-mainnet.infura.io/v3/\$WEB3_INFURA_PROJECT_ID" provider=infura
```

To generate the required tokens, see

- `ETHERSCAN_TOKEN`: Creating an API key in [Etherscan's API docs](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics)
- `ARBISCAN_TOKEN`: Creating an API key in [Arbiscan's API docs](https://docs.arbiscan.io/getting-started/viewing-api-usage-statistics)
- `WEB3_INFURA_PROJECT_ID`: Getting Started in [Infura's API docs](https://infura.io/docs)


## Diagram

![diagram](./docs/assets/diagram.svg)


## Modules

V1 core relies on three modules:

- [Markets Module](#markets-module)
- [Feeds Module](#feeds-module)
- [OV Module](#ov-module)

- [v1-core](#v1-core)
- [Requirements](#requirements)
- [Diagram](#diagram)
- [Modules](#modules)
- [Markets Module](#markets-module)
- [Feeds Module](#feeds-module)
- [OV Module](#ov-module)
- [Deployment Process](#deployment-process)

### Markets Module

Expand Down Expand Up @@ -76,7 +87,6 @@ For each market contract, there is an associated feed contract that delivers the

All markets are implemented by the contract `OverlayV1Market.sol`, regardless of the underlying feed type.


### Feeds Module

The feed contract ingests the data stream directly from the oracle provider and formats the data in a format consumable by any market contract. The feed contract is limited to a single core external view function
Expand Down Expand Up @@ -105,6 +115,7 @@ library Oracle {
}
}
```

from the [`Oracle.sol`](./contracts/libraries/Oracle.sol) library. `Oracle.Data` is consumed by each deployment of `OverlayV1Market.sol` for traders to take positions on the market of interest.

For each oracle provider supported, there should be a specific implementation of a feed contract that inherits from `OverlayV1Feed.sol` (e.g. [`OverlayV1UniswapV3Feed.sol`](./contracts/feeds/uniswapv3/OverlayV1UniswapV3Feed.sol) for Uniswap V3 pools).
Expand All @@ -116,7 +127,6 @@ The OV module consists of an ERC20 token with permissioned mint and burn functio

[`OverlayV1Factory.sol`](./contracts/OverlayV1Factory.sol) grants these mint and burn permissions on a call to `deployMarket()`. Because of this, the factory contract must have admin privileges on the OV token prior to deploying markets.


## Deployment Process

The process to add a new market is as follows:
Expand Down
6 changes: 3 additions & 3 deletions brownie-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# use Ganache's forked mainnet mode as the default network
networks:
default: mainnet-fork
default: arbitrum-main-fork

# automatically fetch contract sources from Etherscan
autofetch_sources: True
Expand All @@ -11,7 +11,7 @@ dotenv: .env
# require OpenZepplin, Uniswap Contracts
dependencies:
- OpenZeppelin/[email protected]
- Uniswap/[email protected]
- smartcontractkit/[email protected]

# path remapping to support imports from GitHub/NPM
compiler:
Expand All @@ -21,4 +21,4 @@ compiler:
runs: 800
remappings:
- "@openzeppelin=OpenZeppelin/[email protected]"
- "@uniswap/v3-core=Uniswap/[email protected]"
- "@chainlink=smartcontractkit/[email protected]"
8 changes: 4 additions & 4 deletions contracts/OverlayV1Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract OverlayV1Factory is IOverlayV1Factory {
1e14, // MIN_TRADING_FEE_RATE = 0.01% (1 bps)
0.000_001e18, // MIN_MINIMUM_COLLATERAL = 1e-6 OV
0.01e14, // MIN_PRICE_DRIFT_UPPER_LIMIT = 0.01 bps/s
0 // MIN_AVERAGE_BLOCK_TIME = 0s
1 // MIN_AVERAGE_BLOCK_TIME = 1s
];
uint256[15] public PARAMS_MAX = [
0.04e14, // MAX_K = ~ 1000 bps / 8 hr
Expand Down Expand Up @@ -160,22 +160,22 @@ contract OverlayV1Factory is IOverlayV1Factory {
}

/// @notice checks market doesn't exist on feed and feed is from a supported factory
function _checkFeed(address feedFactory, address feed) private {
function _checkFeed(address feedFactory, address feed) private view {
require(getMarket[feed] == address(0), "OVV1: market already exists");
require(isFeedFactory[feedFactory], "OVV1: feed factory not supported");
require(IOverlayV1FeedFactory(feedFactory).isFeed(feed), "OVV1: feed does not exist");
}

/// @notice Checks all risk params are within acceptable bounds
function _checkRiskParams(uint256[15] calldata params) private {
function _checkRiskParams(uint256[15] calldata params) private view{
uint256 length = params.length;
for (uint256 i = 0; i < length; i++) {
_checkRiskParam(Risk.Parameters(i), params[i]);
}
}

/// @notice Checks risk param is within acceptable bounds
function _checkRiskParam(Risk.Parameters name, uint256 value) private {
function _checkRiskParam(Risk.Parameters name, uint256 value) private view{
uint256 minValue = PARAMS_MIN.get(name);
uint256 maxValue = PARAMS_MAX.get(name);
require(value >= minValue && value <= maxValue, "OVV1: param out of bounds");
Expand Down
56 changes: 25 additions & 31 deletions contracts/OverlayV1Market.sol
Original file line number Diff line number Diff line change
Expand Up @@ -343,18 +343,7 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {

// subtract unwound open interest from the side's aggregate oi value
// and decrease number of oi shares issued
// NOTE: use subFloor to avoid reverts with oi rounding issues
if (pos.isLong) {
oiLong = oiLong.subFloor(
pos.oiCurrent(fraction, oiTotalOnSide, oiTotalSharesOnSide)
);
oiLongShares -= pos.oiSharesCurrent(fraction);
} else {
oiShort = oiShort.subFloor(
pos.oiCurrent(fraction, oiTotalOnSide, oiTotalSharesOnSide)
);
oiShortShares -= pos.oiSharesCurrent(fraction);
}
_reduceOIAndOIShares(pos, fraction);

// register the amount to be minted/burned
// capPayoff prevents overflow reverts with int256 cast
Expand Down Expand Up @@ -445,18 +434,7 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {

// subtract liquidated open interest from the side's aggregate oi value
// and decrease number of oi shares issued
// NOTE: use subFloor to avoid reverts with oi rounding issues
if (pos.isLong) {
oiLong = oiLong.subFloor(
pos.oiCurrent(fraction, oiTotalOnSide, oiTotalSharesOnSide)
);
oiLongShares -= pos.oiSharesCurrent(fraction);
} else {
oiShort = oiShort.subFloor(
pos.oiCurrent(fraction, oiTotalOnSide, oiTotalSharesOnSide)
);
oiShortShares -= pos.oiSharesCurrent(fraction);
}
_reduceOIAndOIShares(pos, fraction);

// register the amount to be burned
_registerMintOrBurn(int256(value) - int256(cost) - int256(marginToBurn));
Expand Down Expand Up @@ -535,7 +513,7 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {
) public view returns (uint256, uint256) {
uint256 oiTotal = oiOverweight + oiUnderweight;
uint256 oiImbalance = oiOverweight - oiUnderweight;
uint256 oiInvariant = oiUnderweight.mulUp(oiOverweight);
uint256 oiInvariant = oiUnderweight * oiOverweight;

// If no OI or imbalance, no funding occurs. Handles div by zero case below
if (oiTotal == 0 || oiImbalance == 0) {
Expand Down Expand Up @@ -574,7 +552,7 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {
// potential overflow reverts
oiOverweight = (oiTotal + oiImbalance) / 2;
if (oiOverweight != 0) {
oiUnderweight = oiInvariant.divUp(oiOverweight);
oiUnderweight = oiInvariant == 0 ? 0 : (oiInvariant - 1) / oiOverweight + 1; // round up
}
return (oiOverweight, oiUnderweight);
}
Expand Down Expand Up @@ -633,14 +611,14 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {
}

/// @dev bound on notional cap to mitigate front-running attack
/// @dev bound = lmbda * reserveInOvl
/// @dev bound = lmbda * reserveInOv
function frontRunBound(Oracle.Data memory data) public view returns (uint256) {
uint256 lmbda = params.get(Risk.Parameters.Lmbda);
return lmbda.mulDown(data.reserveOverMicroWindow);
}

/// @dev bound on notional cap to mitigate back-running attack
/// @dev bound = macroWindowInBlocks * reserveInOvl * 2 * delta
/// @dev bound = macroWindowInBlocks * reserveInOv * 2 * delta
function backRunBound(Oracle.Data memory data) public view returns (uint256) {
uint256 averageBlockTime = params.get(Risk.Parameters.AverageBlockTime);
uint256 window = (data.macroWindow * ONE) / averageBlockTime;
Expand All @@ -650,7 +628,7 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {

/// @dev Returns the open interest in number of contracts for a given notional
/// @dev Uses _midFromFeed(data) price to calculate oi: OI = Q / P
function oiFromNotional(uint256 notional, uint256 midPrice) public view returns (uint256) {
function oiFromNotional(uint256 notional, uint256 midPrice) public pure returns (uint256) {
return notional.divDown(midPrice);
}

Expand Down Expand Up @@ -682,7 +660,7 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {

/// @dev mid price without impact/spread given oracle data and recent volume
/// @dev used for gas savings to avoid accessing storage for delta, lmbda
function _midFromFeed(Oracle.Data memory data) private view returns (uint256 mid_) {
function _midFromFeed(Oracle.Data memory data) private pure returns (uint256 mid_) {
mid_ = Math.average(data.priceOverMicroWindow, data.priceOverMacroWindow);
}

Expand Down Expand Up @@ -749,6 +727,22 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {
return minted;
}

/// @notice subtract open interest from the side's aggregate oi value
/// @notice and decrease number of oi shares issued
function _reduceOIAndOIShares(
Position.Info memory pos,
uint256 fraction
) internal {
// NOTE: use subFloor to avoid reverts with oi rounding issues
if (pos.isLong) {
oiLong = oiLong.subFloor(pos.oiCurrent(fraction, oiLong, oiLongShares));
oiLongShares -= pos.oiSharesCurrent(fraction);
} else {
oiShort = oiShort.subFloor(pos.oiCurrent(fraction, oiShort, oiShortShares));
oiShortShares -= pos.oiSharesCurrent(fraction);
}
}

/// @notice Updates the market for funding changes to open interest
/// @notice since last time market was interacted with
function _payFunding() private {
Expand Down Expand Up @@ -825,7 +819,7 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {
}

/// @notice Checks the governance per-market risk parameter is valid
function _checkRiskParam(Risk.Parameters name, uint256 value) private {
function _checkRiskParam(Risk.Parameters name, uint256 value) private view {
// checks delta won't cause position to be immediately
// liquidatable given current leverage cap (capLeverage),
// liquidation fee rate (liquidationFeeRate), and
Expand Down
18 changes: 4 additions & 14 deletions contracts/OverlayV1Token.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,20 @@
pragma solidity 0.8.10;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

import "./interfaces/IOverlayV1Token.sol";

contract OverlayV1Token is IOverlayV1Token, AccessControlEnumerable, ERC20("Overlay", "OV") {
contract OverlayV1Token is IOverlayV1Token, AccessControl, ERC20("Overlay", "OV") {
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}

modifier onlyMinter() {
require(hasRole(MINTER_ROLE, msg.sender), "ERC20: !minter");
_;
}

modifier onlyBurner() {
require(hasRole(BURNER_ROLE, msg.sender), "ERC20: !burner");
_;
}

function mint(address _recipient, uint256 _amount) external onlyMinter {
function mint(address _recipient, uint256 _amount) external onlyRole(MINTER_ROLE) {
_mint(_recipient, _amount);
}

function burn(uint256 _amount) external onlyBurner {
function burn(uint256 _amount) external onlyRole(BURNER_ROLE) {
_burn(msg.sender, _amount);
}
}
Loading

0 comments on commit eb5fb39

Please sign in to comment.