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

Optimism integration tests for collateral accounts and trigger orders #480

Open
wants to merge 24 commits into
base: v2.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
693b58b
corrected test filenames
EdNoepel Oct 14, 2024
6522ff8
the easy part
EdNoepel Oct 14, 2024
78d4fce
reworked incentivized integration tests
EdNoepel Oct 14, 2024
7a8e925
refactored integration test setup
EdNoepel Oct 14, 2024
8de71a5
commiting wip to switch to another task
EdNoepel Oct 15, 2024
7b5b183
refactored account tests and chain-specific runners
EdNoepel Oct 16, 2024
5f94ede
fixed issue with controller's kept oracle
EdNoepel Oct 16, 2024
853092f
made gas config chain-specific, balanced optimism gas params
EdNoepel Oct 17, 2024
abb290f
integration tests now run on both forks
EdNoepel Oct 17, 2024
6adb68a
resolved issue with gas limit
EdNoepel Oct 17, 2024
cd4692e
reverted unintentional chicken
EdNoepel Oct 17, 2024
9ddd473
refactored arbitrum-specific stuff out of manager integration test
EdNoepel Oct 18, 2024
dde610f
optimism manager implementation and tests
EdNoepel Oct 18, 2024
4655136
first attempt to run base test in CI
EdNoepel Oct 18, 2024
ec66112
Merge branch 'ed/optimism' of https://github.com/equilibria-xyz/peren…
EdNoepel Oct 18, 2024
41df754
post-rebase cleanup
EdNoepel Oct 18, 2024
bdc881c
adjust CI
EdNoepel Oct 18, 2024
d6b935e
commented-out new thing
EdNoepel Oct 18, 2024
f8eafdc
uncommented without extra env
EdNoepel Oct 18, 2024
8e0c7b5
enable base tests in ci
EdNoepel Oct 18, 2024
7bd71de
reenable other ci jobs
EdNoepel Oct 19, 2024
6b3656d
tidy
EdNoepel Oct 21, 2024
637598a
moar linter fixes
EdNoepel Oct 21, 2024
c26c839
adjust whitespace
EdNoepel Oct 22, 2024
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
18 changes: 16 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ jobs:
run: yarn --frozen-lockfile
- name: Compile
run: yarn workspaces run compile # compile all packages
- name: Run tests
- name: Run tests with coverage on Arbitrum
env:
MOCHA_REPORTER: dot
MOCHA_RETRY_COUNT: 2
Expand All @@ -206,6 +206,13 @@ jobs:
with:
name: account_integration_test_coverage
path: ./packages/perennial-account/coverage/lcov.info
- name: Run tests on Base
env:
MOCHA_REPORTER: dot
MOCHA_RETRY_COUNT: 2
BASE_NODE_URL: ${{ secrets.BASE_NODE_URL }}
run: |
yarn workspace @perennial/account run test:integrationBase

# [ORACLE]
oracle-unit-test:
Expand Down Expand Up @@ -346,7 +353,7 @@ jobs:
run: yarn --frozen-lockfile
- name: Compile
run: yarn workspaces run compile # compile all packages
- name: Run tests
- name: Run tests with coverage on Arbitrum
env:
MOCHA_REPORTER: dot
MOCHA_RETRY_COUNT: 2
Expand All @@ -358,6 +365,13 @@ jobs:
with:
name: order_integration_test_coverage
path: ./packages/perennial-order/coverage/lcov.info
- name: Run tests on Base
env:
MOCHA_REPORTER: dot
MOCHA_RETRY_COUNT: 2
BASE_NODE_URL: ${{ secrets.BASE_NODE_URL }}
run: |
yarn workspace @perennial/order run test:integrationBase

# [VAULT]
vault-unit-test:
Expand Down
2 changes: 1 addition & 1 deletion packages/common/hardhat.default.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export default function defaultConfig({
: undefined,
chainId: getChainId('hardhat'),
allowUnlimitedContractSize: true,
blockGasLimit: 32000000,
blockGasLimit: 32_000_000,
mining: NODE_INTERVAL_MINING
? {
interval: NODE_INTERVAL_MINING,
Expand Down
6 changes: 3 additions & 3 deletions packages/perennial-account/contracts/Controller.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import { Withdrawal, WithdrawalLib } from "./types/Withdrawal.sol";
/// without keeper compensation. No message relaying facilities are provided.
contract Controller is Factory, IController {
// used for deterministic address creation through create2
bytes32 constant SALT = keccak256("Perennial V2 Collateral Accounts");
bytes32 public constant SALT = keccak256("Perennial V2 Collateral Accounts");

uint256 constant MAX_GROUPS_PER_OWNER = 8;
uint256 constant MAX_MARKETS_PER_GROUP = 4;
uint256 public constant MAX_GROUPS_PER_OWNER = 8;
uint256 public constant MAX_MARKETS_PER_GROUP = 4;

/// @dev USDC stablecoin address
Token6 public immutable USDC; // solhint-disable-line var-name-mixedcase
Expand Down
43 changes: 43 additions & 0 deletions packages/perennial-account/contracts/Controller_Optimism.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.24;

import { Kept_Optimism, Kept } from "@equilibria/root/attribute/Kept/Kept_Optimism.sol";
import { UFixed18 } from "@equilibria/root/number/types/UFixed18.sol";
import { IVerifierBase } from "@equilibria/root/verifier/interfaces/IVerifierBase.sol";
import { IMarketFactory } from "@perennial/core/contracts/interfaces/IMarketFactory.sol";
import { Controller_Incentivized } from "./Controller_Incentivized.sol";

/// @title Controller_Optimism
/// @notice Controller which compensates keepers for handling or relaying messages on Optimism L2.
contract Controller_Optimism is Controller_Incentivized, Kept_Optimism {
/// @dev Creates instance of Controller which compensates keepers
/// @param implementation Pristine collateral account contract
/// @param marketFactory Market Factory contract
/// @param nonceManager Verifier contract to which nonce and group cancellations are relayed
constructor(
address implementation,
IMarketFactory marketFactory,
IVerifierBase nonceManager
) Controller_Incentivized(implementation, marketFactory, nonceManager) {}

/// @dev Use the Kept_Optimism implementation for calculating the dynamic fee
function _calldataFee(
bytes memory applicableCalldata,
UFixed18 multiplierCalldata,
uint256 bufferCalldata
) internal view override(Kept_Optimism, Kept) returns (UFixed18) {
return Kept_Optimism._calldataFee(applicableCalldata, multiplierCalldata, bufferCalldata);
}

/// @dev Transfers funds from collateral account to controller, and limits compensation
/// to the user-defined maxFee in the Action message
/// @param amount Calculated keeper fee
/// @param data Encoded address of collateral account and UFixed6 user-specified maximum fee
/// @return raisedKeeperFee Amount pulled from controller to keeper
function _raiseKeeperFee(
UFixed18 amount,
bytes memory data
) internal override(Controller_Incentivized, Kept) returns (UFixed18 raisedKeeperFee) {
return Controller_Incentivized._raiseKeeperFee(amount, data);
}
}
1 change: 1 addition & 0 deletions packages/perennial-account/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"gasReport": "REPORT_GAS=true OPTIMIZER_ENABLED=true yarn test:integration",
"test": "hardhat test test/unit/*",
"test:integration": "FORK_ENABLED=true FORK_NETWORK=arbitrum FORK_BLOCK_NUMBER=233560862 hardhat test test/integration/*",
"test:integrationBase": "FORK_ENABLED=true FORK_NETWORK=base FORK_BLOCK_NUMBER=21067741 hardhat test test/integration/*",
"coverage": "hardhat coverage --testfiles 'test/unit/*'",
"coverage:integration": "FORK_ENABLED=true FORK_NETWORK=arbitrum FORK_BLOCK_NUMBER=233560862 hardhat coverage --testfiles 'test/integration/*'",
"lint": "eslint --fix --ext '.ts,.js' ./ && solhint 'contracts/**/*.sol' --fix",
Expand Down
167 changes: 14 additions & 153 deletions packages/perennial-account/test/helpers/arbitrumHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
import { expect } from 'chai'
import { BigNumber, CallOverrides, constants, utils } from 'ethers'
import {
IKeeperOracle,
IOracleFactory,
KeeperOracle,
KeeperOracle__factory,
Oracle,
Oracle__factory,
PythFactory,
PythFactory__factory,
} from '@perennial/oracle/types/generated'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { createMarket, deployController, deployOracleFactory, deployProtocolForOracle } from './setupHelpers'
import { IOracleFactory, PythFactory } from '@perennial/oracle/types/generated'
import { createFactories, deployController } from './setupHelpers'
import {
Account__factory,
AccountVerifier__factory,
AggregatorV3Interface,
Controller,
Controller_Arbitrum,
Controller_Arbitrum__factory,
IEmptySetReserve,
IEmptySetReserve__factory,
IERC20Metadata,
IERC20Metadata__factory,
IMarket,
IMarketFactory,
IOracleProvider,
GasOracle__factory,
} from '../../types/generated'
import { impersonate } from '../../../common/testutil'
import { IVerifier } from '@perennial/core/types/generated'

const PYTH_ADDRESS = '0xff1a0f4744e8582DF1aE09D5611b887B6a12925C'
const PYTH_ETH_USD_PRICE_FEED = '0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace'
const PYTH_BTC_USD_PRICE_FEED = '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43'
const CHAINLINK_ETH_USD_FEED = '0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612'

const DSU_ADDRESS = '0x52C64b8998eB7C80b6F526E99E29ABdcC86B841b' // Digital Standard Unit, an 18-decimal token
Expand All @@ -41,101 +29,13 @@ const USDC_ADDRESS = '0xaf88d065e77c8cC2239327C5EDb3A432268e5831' // Arbitrum na
const USDC_HOLDER = '0x2df1c51e09aecf9cacb7bc98cb1742757f163df7' // Hyperliquid deposit bridge has 414mm USDC at height 233560862

// deploys protocol
export async function createFactories(
export async function createFactoriesForChain(
owner: SignerWithAddress,
): Promise<[IOracleFactory, IMarketFactory, PythFactory]> {
// Deploy the oracle factory, which markets created by the market factory will query
const oracleFactory = await deployOracleFactory(owner)
// Deploy the market factory and authorize it with the oracle factory
const marketFactory = await deployProtocolForOracle(owner, oracleFactory)

// Deploy a Pyth keeper oracle factory, which we'll need to meddle with prices

const commitmentGasOracle = await new GasOracle__factory(owner).deploy(
CHAINLINK_ETH_USD_FEED,
8,
1_000_000,
utils.parseEther('1.02'),
1_000_000,
0,
0,
0,
)
const settlementGasOracle = await new GasOracle__factory(owner).deploy(
CHAINLINK_ETH_USD_FEED,
8,
200_000,
utils.parseEther('1.02'),
500_000,
0,
0,
0,
)
const keeperOracleImpl = await new KeeperOracle__factory(owner).deploy(60)
const pythOracleFactory = await new PythFactory__factory(owner).deploy(
PYTH_ADDRESS,
commitmentGasOracle.address,
settlementGasOracle.address,
keeperOracleImpl.address,
)
await pythOracleFactory.initialize(oracleFactory.address)
await pythOracleFactory.updateParameter(1, 0, 4, 10)
await oracleFactory.register(pythOracleFactory.address)

return [oracleFactory, marketFactory, pythOracleFactory]
}

// creates an ETH market using a locally deployed factory and oracle
export async function createMarketETH(
owner: SignerWithAddress,
oracleFactory: IOracleFactory,
pythOracleFactory: PythFactory,
marketFactory: IMarketFactory,
dsu: IERC20Metadata,
overrides?: CallOverrides,
): Promise<[IMarket, IOracleProvider, IKeeperOracle]> {
// Create oracles needed to support the market
const [keeperOracle, oracle] = await createPythOracle(
owner,
oracleFactory,
pythOracleFactory,
PYTH_ETH_USD_PRICE_FEED,
'ETH-USD',
overrides,
)
// Create the market in which user or collateral account may interact
const market = await createMarket(owner, marketFactory, dsu, oracle, undefined, undefined, overrides ?? {})
await keeperOracle.register(oracle.address)
await oracle.register(market.address)
return [market, oracle, keeperOracle]
}

// creates a BTC market using a locally deployed factory and oracle
export async function createMarketBTC(
owner: SignerWithAddress,
oracleFactory: IOracleFactory,
pythOracleFactory: PythFactory,
marketFactory: IMarketFactory,
dsu: IERC20Metadata,
overrides?: CallOverrides,
): Promise<[IMarket, IOracleProvider, IKeeperOracle]> {
// Create oracles needed to support the market
const [keeperOracle, oracle] = await createPythOracle(
owner,
oracleFactory,
pythOracleFactory,
PYTH_BTC_USD_PRICE_FEED,
'BTC-USD',
overrides,
)
// Create the market in which user or collateral account may interact
const market = await createMarket(owner, marketFactory, dsu, oracle, undefined, undefined, overrides ?? {})
await keeperOracle.register(oracle.address)
await oracle.register(market.address)
return [market, oracle, keeperOracle]
): Promise<[IOracleFactory, IMarketFactory, PythFactory, AggregatorV3Interface]> {
return createFactories(owner, PYTH_ADDRESS, CHAINLINK_ETH_USD_FEED)
}

// connects to Arbitrum stablecoins and deploys a controller configured for them
// connects to Arbitrum stablecoins and deploys a non-incentivized controller configured for them
export async function deployAndInitializeController(
owner: SignerWithAddress,
marketFactory: IMarketFactory,
Expand All @@ -152,15 +52,14 @@ export async function deployAndInitializeController(
export async function deployControllerArbitrum(
owner: SignerWithAddress,
marketFactory: IMarketFactory,
nonceManager: IVerifier,
overrides?: CallOverrides,
): Promise<Controller_Arbitrum> {
const accountImpl = await new Account__factory(owner).deploy(USDC_ADDRESS, DSU_ADDRESS, DSU_RESERVE)
accountImpl.initialize(constants.AddressZero)
const controller = await new Controller_Arbitrum__factory(owner).deploy(
accountImpl.address,
marketFactory.address,
nonceManager.address,
await marketFactory.verifier(),
overrides ?? {},
)
return controller
Expand Down Expand Up @@ -190,50 +89,12 @@ export async function fundWalletUSDC(
await usdc.transfer(wallet.address, amount, overrides ?? {})
}

export function getDSUReserve(owner: SignerWithAddress): IEmptySetReserve {
return IEmptySetReserve__factory.connect(DSU_RESERVE, owner)
}

export async function getStablecoins(owner: SignerWithAddress): Promise<[IERC20Metadata, IERC20Metadata]> {
const dsu = IERC20Metadata__factory.connect(DSU_ADDRESS, owner)
const usdc = IERC20Metadata__factory.connect(USDC_ADDRESS, owner)
return [dsu, usdc]
}

export async function returnUSDC(wallet: SignerWithAddress): Promise<undefined> {
const usdc = IERC20Metadata__factory.connect(USDC_ADDRESS, wallet)
await usdc.transfer(USDC_HOLDER, await usdc.balanceOf(wallet.address))
}

export async function returnDSU(wallet: SignerWithAddress): Promise<undefined> {
const dsu = IERC20Metadata__factory.connect(DSU_ADDRESS, wallet)
await dsu.transfer(DSU_HOLDER, await dsu.balanceOf(wallet.address))
}

async function createPythOracle(
owner: SignerWithAddress,
oracleFactory: IOracleFactory,
pythOracleFactory: PythFactory,
pythFeedId: string,
name: string,
overrides?: CallOverrides,
): Promise<[KeeperOracle, Oracle]> {
// Create the keeper oracle, which tests may use to meddle with prices
const keeperOracle = KeeperOracle__factory.connect(
await pythOracleFactory.callStatic.create(pythFeedId, pythFeedId, {
provider: constants.AddressZero,
decimals: 0,
}),
owner,
)
await pythOracleFactory.create(
pythFeedId,
pythFeedId,
{ provider: constants.AddressZero, decimals: 0 },
overrides ?? {},
)

// Create the oracle, which markets created by the market factory will query
const oracle = Oracle__factory.connect(
await oracleFactory.callStatic.create(pythFeedId, pythOracleFactory.address, name),
owner,
)
await oracleFactory.create(pythFeedId, pythOracleFactory.address, name, overrides ?? {})
return [keeperOracle, oracle]
}
Loading
Loading