Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Arbitrum's sequencer healthy check. #180

Merged
merged 5 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion contracts/OverlayV1Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "./interfaces/IOverlayV1Factory.sol";
import "./interfaces/IOverlayV1Market.sol";
import "./interfaces/IOverlayV1Token.sol";
import "./interfaces/feeds/IOverlayV1FeedFactory.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

import "./libraries/Risk.sol";

Expand Down Expand Up @@ -65,6 +66,10 @@ contract OverlayV1Factory is IOverlayV1Factory {
// market deployer
IOverlayV1Deployer public immutable deployer;

// sequencer oracle
AggregatorV3Interface public sequencerOracle;
uint256 public gracePeriod;

// fee related quantities
address public feeRecipient;

Expand All @@ -83,6 +88,10 @@ contract OverlayV1Factory is IOverlayV1Factory {
event FeedFactoryRemoved(address indexed user, address feedFactory);
event FeeRecipientUpdated(address indexed user, address recipient);

// events for sequencer oracle
event SequencerOracleUpdated(address indexed newOracle);
event GracePeriodUpdated(uint256 newGracePeriod);

// governor modifier for governance sensitive functions
modifier onlyGovernor() {
require(ov.hasRole(GOVERNOR_ROLE, msg.sender), "OVV1: !governor");
Expand All @@ -101,7 +110,12 @@ contract OverlayV1Factory is IOverlayV1Factory {
_;
}

constructor(address _ov, address _feeRecipient) {
constructor(
address _ov,
address _feeRecipient,
address _sequencerOracle,
uint256 _gracePeriod
) {
// set ov
ov = IOverlayV1Token(_ov);

Expand All @@ -110,6 +124,10 @@ contract OverlayV1Factory is IOverlayV1Factory {

// create a new deployer to use when deploying markets
deployer = new OverlayV1Deployer(_ov);

// set the sequencer oracle
sequencerOracle = AggregatorV3Interface(_sequencerOracle);
gracePeriod = _gracePeriod;
}

/// @dev adds a supported feed factory
Expand Down Expand Up @@ -214,4 +232,23 @@ contract OverlayV1Factory is IOverlayV1Factory {
market.shutdown();
emit EmergencyShutdown(msg.sender, address(market));
}

/**
* @notice Checks the sequencer oracle is healthy: is up and grace period passed.
* @return True if the SequencerOracle is up and the grace period passed, false otherwise
*/
function isUpAndGracePeriodPassed() external view returns (bool) {
(, int256 answer,, uint256 lastUpdateTimestamp,) = sequencerOracle.latestRoundData();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we are using the lastUpdatedTimestamp field (timestamp of when the round was updated), which is what AAVE is doing as well.

However, in the Chainlink docs, the startedAt (timestamp of when the round started) timestamp is used instead.

I think both implementations should be correct.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From ArbitrumSequencerUptimeFeed.sol - latestRoundData() we see that both variables are actually the same.
Conceptually, we should use the one that's higher.

return answer == 0 && block.timestamp - lastUpdateTimestamp > gracePeriod;
}

function setSequencerOracle(address newSequencerOracle) public onlyGovernor {
sequencerOracle = AggregatorV3Interface(newSequencerOracle);
emit SequencerOracleUpdated(newSequencerOracle);
}

function setGracePeriod(uint256 newGracePeriod) public onlyGovernor {
gracePeriod = newGracePeriod;
emit GracePeriodUpdated(newGracePeriod);
}
}
4 changes: 4 additions & 0 deletions contracts/OverlayV1Market.sol
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,10 @@ contract OverlayV1Market is IOverlayV1Market, Pausable {
// pay funding for time elasped since last interaction w market
_payFunding();

// check Arbitrum sequencer is up and grace period has
// passed before fetching new data from feed
require(IOverlayV1Factory(factory).isUpAndGracePeriodPassed(), "OVV1:!sequencer");

// fetch new oracle data from feed
// applies sanity check in case of data manipulation
Oracle.Data memory data = IOverlayV1Feed(feed).latest();
Expand Down
3 changes: 3 additions & 0 deletions contracts/interfaces/IOverlayV1Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,7 @@ interface IOverlayV1Factory {

// fee repository setter
function setFeeRecipient(address _feeRecipient) external;

// Arbitrum sequencer check
function isUpAndGracePeriodPassed() external view returns (bool);
}
3 changes: 2 additions & 1 deletion tests/OverlayV1Market.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ contract MarketTest is Test {
address immutable USER = makeAddr("user");
address constant FEED_FACTORY = 0x92ee7A26Dbc18E9C0157831d79C2906A02fD1FAe;
address constant FEED = 0x46B4143CAf2fE2965349FCa53730e83f91247E2C;
address constant SEQUENCER_ORACLE = 0xFdB631F5EE196F0ed6FAa767959853A9F217697D;

OverlayV1Token ov;
OverlayV1Factory factory;
Expand All @@ -31,7 +32,7 @@ contract MarketTest is Test {
function setUp() public {
vm.createSelectFork(vm.envString("RPC"), 169_490_320);
ov = new OverlayV1Token();
factory = new OverlayV1Factory(address(ov), FEE_RECIPIENT);
factory = new OverlayV1Factory(address(ov), FEE_RECIPIENT, SEQUENCER_ORACLE, 0);

ov.grantRole(ADMIN, address(factory));
ov.grantRole(ADMIN, GOVERNOR);
Expand Down
106 changes: 106 additions & 0 deletions tests/Sequencer.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;

import {Test, console2} from "forge-std/Test.sol";
import {OverlayV1Market} from "contracts/OverlayV1Market.sol";
import {OverlayV1Factory} from "contracts/OverlayV1Factory.sol";
import {OverlayV1Token} from "contracts/OverlayV1Token.sol";
import {OverlayV1Deployer} from "contracts/OverlayV1Deployer.sol";
import {Position} from "contracts/libraries/Position.sol";
import {AggregatorMock} from "contracts/mocks/AggregatorMock.sol";

contract SequencerTest is Test {
bytes32 constant ADMIN = 0x00;
bytes32 constant MINTER_ROLE = keccak256("MINTER");
bytes32 constant BURNER_ROLE = keccak256("BURNER");
bytes32 constant GOVERNOR_ROLE = keccak256("GOVERNOR");
bytes32 constant GUARDIAN_ROLE = keccak256("GUARDIAN");
bytes32 constant PAUSER_ROLE = keccak256("PAUSER");

address immutable GOVERNOR = makeAddr("governor");
address immutable FEE_RECIPIENT = makeAddr("fee-recipient");
address immutable PAUSER = makeAddr("pauser");
address immutable USER = makeAddr("user");
address constant FEED_FACTORY = 0x92ee7A26Dbc18E9C0157831d79C2906A02fD1FAe;
address constant FEED = 0x46B4143CAf2fE2965349FCa53730e83f91247E2C;
AggregatorMock sequencer_oracle;

OverlayV1Token ov;
OverlayV1Factory factory;
OverlayV1Market market;
OverlayV1Deployer deployer;

function setUp() public {
vm.createSelectFork(vm.envString("RPC"), 169_490_320);
ov = new OverlayV1Token();
sequencer_oracle = new AggregatorMock();
factory =
new OverlayV1Factory(address(ov), FEE_RECIPIENT, address(sequencer_oracle), 30 minutes);

ov.grantRole(ADMIN, address(factory));
ov.grantRole(ADMIN, GOVERNOR);
ov.grantRole(MINTER_ROLE, GOVERNOR);
ov.grantRole(GOVERNOR_ROLE, GOVERNOR);
ov.grantRole(PAUSER_ROLE, PAUSER);

uint256[15] memory params;
params[0] = 115740740740;
params[1] = 750000000000000000;
params[2] = 2475000000000000;
params[3] = 5000000000000000000;
params[4] = 20000000000000000000000;
params[5] = 10000000000000000000;
params[6] = 2592000;
params[7] = 1666666666666666666666;
params[8] = 40000000000000000;
params[9] = 50000000000000000;
params[10] = 50000000000000000;
params[11] = 750000000000000;
params[12] = 100000000000000;
params[13] = 87000000000000;
params[14] = 250;

vm.startPrank(GOVERNOR);
factory.addFeedFactory(FEED_FACTORY);

market = OverlayV1Market(factory.deployMarket(FEED_FACTORY, FEED, params));

ov.mint(USER, 100e18);
}

function testSequencerDown() public {
sequencer_oracle.setData(1, 1); //Sequencer Down
vm.startPrank(USER);
ov.approve(address(market), type(uint256).max);
vm.expectRevert("OVV1:!sequencer");
market.build(1e18, 1e18, true, type(uint256).max);

vm.stopPrank();
sequencer_oracle.setData(2, 0); //Sequencer Up
skip(15 minutes);

vm.startPrank(USER);
vm.expectRevert(); // Grace period not over
market.build(1e18, 1e18, true, type(uint256).max);

skip(45 minutes);
market.build(1e18, 1e18, true, type(uint256).max);

vm.stopPrank();
sequencer_oracle.setData(3, 1); //Sequencer Down
skip(60 minutes);

vm.startPrank(USER);
vm.expectRevert();
market.unwind(0, 1e18, 0);

vm.stopPrank();
sequencer_oracle.setData(4, 0);
skip(60 minutes);

vm.startPrank(USER);
market.unwind(0, 1e18, 0);

assertLt(ov.balanceOf(USER), 100e18);
}
}
14 changes: 11 additions & 3 deletions tests/factories/market/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@
from brownie import (
OverlayV1Factory, OverlayV1Market,
OverlayV1Deployer, OverlayV1Token, OverlayV1FeedFactoryMock,
web3
web3, Contract
)


@pytest.fixture(scope="module")
def sequencer_aggregator():
# Arbitrum One sequencer aggregator
yield Contract.from_explorer("0xFdB631F5EE196F0ed6FAa767959853A9F217697D")


@pytest.fixture(scope="module")
def gov(accounts):
yield accounts[0]
Expand Down Expand Up @@ -143,12 +149,14 @@ def feed_three(feed_factory):

@pytest.fixture(scope="module")
def create_factory(gov, guardian, fee_recipient, request, ov, governor_role,
guardian_role, feed_factory, feed_three):
guardian_role, feed_factory, feed_three,
sequencer_aggregator):

def create_factory(tok=ov, recipient=fee_recipient, feeds=feed_factory,
feed=feed_three):
# create the market factory
factory = gov.deploy(OverlayV1Factory, tok, recipient)
factory = gov.deploy(OverlayV1Factory, tok, recipient,
sequencer_aggregator.address, 0)

# grant market factory token admin role
tok.grantRole(tok.DEFAULT_ADMIN_ROLE(), factory, {"from": gov})
Expand Down
6 changes: 5 additions & 1 deletion tests/invariants/MarketEchidna.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {OverlayV1Factory} from "../../contracts/OverlayV1Factory.sol";
import {OverlayV1Market} from "../../contracts/OverlayV1Market.sol";
import {OverlayV1Token} from "../../contracts/OverlayV1Token.sol";
import {OverlayV1FeedFactoryMock} from "../../contracts/mocks/OverlayV1FeedFactoryMock.sol";
import {AggregatorMock} from "../../contracts/mocks/AggregatorMock.sol";
import {GOVERNOR_ROLE, MINTER_ROLE} from "../../contracts/interfaces/IOverlayV1Token.sol";
import {TestUtils} from "./TestUtils.sol";

Expand All @@ -28,6 +29,7 @@ contract MarketEchidna {
OverlayV1Factory factory;
OverlayV1Market market;
OverlayV1Token ovl;
AggregatorMock sequencer_oracle;

// make these constant to match Echidna config
address public constant ALICE = address(0x1000000000000000000000000000000000000000);
Expand All @@ -37,9 +39,11 @@ contract MarketEchidna {
uint256 constant CAP_NOTIONAL = 8e23;

constructor() {
// Add sequencer Mock
sequencer_oracle = new AggregatorMock();
// create contracts to be tested
ovl = new OverlayV1Token();
factory = new OverlayV1Factory(address(ovl), address(0x111));
factory = new OverlayV1Factory(address(ovl), address(0x111), address(sequencer_oracle), 0);
// market will be later deployed by factory

// ovl config
Expand Down
6 changes: 5 additions & 1 deletion tests/invariants/MarketFoundry.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {OverlayV1Factory} from "../../contracts/OverlayV1Factory.sol";
import {OverlayV1Market} from "../../contracts/OverlayV1Market.sol";
import {OverlayV1Token} from "../../contracts/OverlayV1Token.sol";
import {OverlayV1FeedFactoryMock} from "../../contracts/mocks/OverlayV1FeedFactoryMock.sol";
import {AggregatorMock} from "../../contracts/mocks/AggregatorMock.sol";
import {GOVERNOR_ROLE, MINTER_ROLE} from "../../contracts/interfaces/IOverlayV1Token.sol";
import {TestUtils} from "./TestUtils.sol";

Expand All @@ -19,6 +20,7 @@ contract MarketFoundry is Test {
OverlayV1Factory factory;
OverlayV1Market market;
OverlayV1Token ovl;
AggregatorMock sequencer_oracle;

// make these constant to match Echidna config
address public constant ALICE = address(0x1000000000000000000000000000000000000000);
Expand All @@ -28,13 +30,15 @@ contract MarketFoundry is Test {
uint256 constant CAP_NOTIONAL = 8e23;

function setUp() public virtual {
// Add sequencer Mock
sequencer_oracle = new AggregatorMock();
// foundry-specific sender setup
targetSender(ALICE);
targetSender(BOB);

// create contracts to be tested
ovl = new OverlayV1Token();
factory = new OverlayV1Factory(address(ovl), address(0x111));
factory = new OverlayV1Factory(address(ovl), address(0x111), address(sequencer_oracle), 0);
// market will be later deployed by factory

// ovl config
Expand Down
12 changes: 10 additions & 2 deletions tests/markets/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
)


@pytest.fixture(scope="module")
def sequencer_aggregator():
# Arbitrum One sequencer aggregator
yield Contract.from_explorer("0xFdB631F5EE196F0ed6FAa767959853A9F217697D")


@pytest.fixture(scope="module")
def gov(accounts):
yield accounts[0]
Expand Down Expand Up @@ -163,10 +169,12 @@ def mock_feed(create_mock_feed):

@pytest.fixture(scope="module")
def create_factory(gov, guardian, fee_recipient, request, ov, governor_role,
guardian_role, feed_factory, mock_feed_factory):
guardian_role, feed_factory, mock_feed_factory,
sequencer_aggregator):
def create_factory(tok=ov, recipient=fee_recipient):
# create the market factory
factory = gov.deploy(OverlayV1Factory, tok, recipient)
factory = gov.deploy(OverlayV1Factory, tok, recipient,
sequencer_aggregator.address, 0)

# grant market factory token admin role
tok.grantRole(tok.DEFAULT_ADMIN_ROLE(), factory, {"from": gov})
Expand Down
Loading