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

[SC-1037] VelodromeV2 #109

Merged
merged 11 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- run: yarn test:ci
env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
RPC_AUTH_HEADER: ${{ secrets.RPC_AUTH_HEADER }}
OPTIMISTIC_RPC_URL: ${{ secrets.OPTIMISTIC_RPC_URL }}

coverage:
runs-on: ubuntu-latest
Expand All @@ -31,7 +31,7 @@ jobs:
- run: yarn coverage
env:
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
RPC_AUTH_HEADER: ${{ secrets.RPC_AUTH_HEADER }}
OPTIMISTIC_RPC_URL: ${{ secrets.OPTIMISTIC_RPC_URL }}
- uses: codecov/codecov-action@v3

single-price-example:
Expand Down
88 changes: 88 additions & 0 deletions contracts/oracles/VelodromeV2Oracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: MIT
// solhint-disable one-contract-per-file

pragma solidity 0.8.23;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../interfaces/IOracle.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "../libraries/OraclePrices.sol";

interface IVelodramoV2Router {
function getReserves(address tokenA, address tokenB, bool stable, address _factory) external view returns (uint256 reserveA, uint256 reserveB);
}

interface IVelodramoV2Registry {
function poolFactories() external view returns (address[] memory);
}


contract VelodromeV2Oracle is IOracle {
using OraclePrices for OraclePrices.Data;
using Math for uint256;

IERC20 private constant _NONE = IERC20(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF);

IVelodramoV2Router public immutable ROUTER;
IVelodramoV2Registry public immutable REGISTRY;

constructor(IVelodramoV2Router _router, IVelodramoV2Registry _registry) {
ROUTER = _router;
REGISTRY = _registry;
}

function getRate(IERC20 srcToken, IERC20 dstToken, IERC20 connector, uint256 thresholdFilter) external view override returns (uint256 rate, uint256 weight) {
zZoMROT marked this conversation as resolved.
Show resolved Hide resolved
address[] memory factories = REGISTRY.poolFactories();
uint256 factoriesLength = factories.length;
OraclePrices.Data memory ratesAndWeights = OraclePrices.init(2 * factoriesLength);
uint256 b0;
uint256 b1;
if (connector == _NONE) {
for (uint256 i = 0; i < factoriesLength; i++) {
zZoMROT marked this conversation as resolved.
Show resolved Hide resolved
(b0, b1) = _getReserves(srcToken, dstToken, true, factories[i]);
if (b0 > 0) {
ratesAndWeights.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt()));
}
(b0, b1) = _getReserves(srcToken, dstToken, false, factories[i]);
if (b0 > 0) {
ratesAndWeights.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt()));
}
}
(rate, weight) = ratesAndWeights.getRateAndWeight(thresholdFilter);
} else {
OraclePrices.Data memory ratesAndWeightsC0 = OraclePrices.init(2 * factoriesLength);
for (uint256 i = 0; i < factoriesLength; i++) {
(b0, b1) = _getReserves(srcToken, connector, true, factories[i]);
if (b0 > 0) {
ratesAndWeightsC0.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt()));
}
(b0, b1) = _getReserves(srcToken, connector, false, factories[i]);
if (b0 > 0) {
ratesAndWeightsC0.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt()));
}
}
(uint256 rateC0, uint256 weightC0) = ratesAndWeightsC0.getRateAndWeight(thresholdFilter);

OraclePrices.Data memory ratesAndWeightsC1 = OraclePrices.init(2 * factoriesLength);
for (uint256 i = 0; i < factoriesLength; i++) {
(b0, b1) = _getReserves(connector, dstToken, true, factories[i]);
if (b0 > 0) {
ratesAndWeightsC1.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt()));
}
(b0, b1) = _getReserves(connector, dstToken, false, factories[i]);
if (b0 > 0) {
ratesAndWeightsC1.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt()));
}
}
(uint256 rateC1, uint256 weightC1) = ratesAndWeightsC1.getRateAndWeight(thresholdFilter);
rate = rateC0 * rateC1 / 1e18;
weight = Math.min(weightC0, weightC1);
}
}

function _getReserves(IERC20 srcToken, IERC20 dstToken, bool stable, address factory) internal view returns (uint256 reserveSrc, uint256 reserveDst) {
try ROUTER.getReserves(address(srcToken), address(dstToken), stable, factory) returns (uint256 reserveSrc_, uint256 reserveDst_) {
(reserveSrc, reserveDst) = (reserveSrc_, reserveDst_);
} catch {} // solhint-disable-line no-empty-blocks
}
}
1 change: 0 additions & 1 deletion hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ require('hardhat-gas-reporter');
require('hardhat-tracer');
require('solidity-coverage');

require('dotenv').config();
const { Networks, getNetwork } = require('@1inch/solidity-utils/hardhat-setup');

const { networks, etherscan } = (new Networks(true, 'mainnet', true)).registerAll();
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@openzeppelin/contracts": "5.0.1"
},
"devDependencies": {
"@1inch/solidity-utils": "3.5.5",
"@1inch/solidity-utils": "3.5.6",
"@matterlabs/hardhat-zksync-deploy": "1.1.1",
"@matterlabs/hardhat-zksync-solc": "1.0.5",
"@matterlabs/hardhat-zksync-verify": "1.2.1",
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-token.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { ethers, deployments } = require('hardhat');
const { tokens } = require('../test/helpers');

const usdPrice = (ethPrice, srcTokenDecimals) => { return parseFloat(ethPrice * 10 ** srcTokenDecimals / 1e18 / 1e18 * parseFloat(process.env.SCRIPT_ETH_PRICE)).toFixed(2); };
const usdPrice = (ethPrice, srcTokenDecimals) => { return parseFloat(parseFloat(ethPrice) * 10 ** srcTokenDecimals / 1e18 / 1e18 * parseFloat(process.env.SCRIPT_ETH_PRICE)).toFixed(2); };

async function main () {
if (!process.env.SCRIPT_ETH_PRICE) {
Expand Down
33 changes: 33 additions & 0 deletions test/helpers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
const { network } = require('hardhat');
const { Networks } = require('@1inch/solidity-utils/hardhat-setup');

const defaultValues = {
thresholdFilter: 10,
};
Expand Down Expand Up @@ -55,6 +58,11 @@ const tokens = {
axlUSDC: '0xEB466342C4d449BC9f53A865D5Cb90586f405215',
axlUSDT: '0x7f5373AE26c3E8FfC4c77b7255DF7eC1A9aF52a6',
},
optimistic: {
WETH: '0x4200000000000000000000000000000000000006',
USDC: '0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
OP: '0x4200000000000000000000000000000000000042',
},
};

const contracts = {
Expand Down Expand Up @@ -137,11 +145,36 @@ const deployParams = {
factory: '0xe21aac7f113bd5dc2389e4d8a8db854a87fd6951',
initcodeHash: '0x0ccd005ee58d5fb11632ef5c2e0866256b240965c62c8e990c0f84a97f311879',
},
VelodromeV2: { // optimistic network
router: '0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858',
registry: '0xF4c67CdEAaB8360370F41514d06e32CcD8aA1d7B',
},
};

const resetHardhatNetworkFork = async function (networkName) {
if (networkName.toLowerCase() === 'hardhat') {
await network.provider.request({ // reset to local network
method: 'hardhat_reset',
params: [],
});
} else {
const { url, authKeyHttpHeader } = (new Networks())._parseRpcEnv(process.env[`${networkName.toUpperCase()}_RPC_URL`]);
await network.provider.request({ // reset to networkName fork
method: 'hardhat_reset',
params: [{
forking: {
jsonRpcUrl: url,
httpHeaders: authKeyHttpHeader ? { 'auth-key': authKeyHttpHeader } : undefined,
},
}],
});
}
};

module.exports = {
defaultValues,
tokens,
contracts,
deployParams,
resetHardhatNetworkFork,
};
88 changes: 88 additions & 0 deletions test/oracles/VelodromeV2Oracle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { assertRoughlyEqualValues, deployContract } = require('@1inch/solidity-utils');
const {
tokens,
deployParams: { VelodromeV2, UniswapV3 },
defaultValues: { thresholdFilter },
resetHardhatNetworkFork,
} = require('../helpers.js');

describe('VelodromeV2Oracle', function () {
before(async function () {
await resetHardhatNetworkFork('optimistic');
});

after(async function () {
await resetHardhatNetworkFork('mainnet');
});

async function initContracts () {
const velodromeV2Oracle = await deployContract('VelodromeV2Oracle', [VelodromeV2.router, VelodromeV2.registry]);
const uniswapV3Oracle = await deployContract('UniswapV3LikeOracle', [UniswapV3.factory, UniswapV3.initcodeHash, UniswapV3.fees]);
return { velodromeV2Oracle, uniswapV3Oracle };
}

it('WETH -> USDC', async function () {
const { velodromeV2Oracle, uniswapV3Oracle } = await loadFixture(initContracts);
await testRate(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.NONE, velodromeV2Oracle, uniswapV3Oracle);
});

it('USDC -> WETH', async function () {
const { velodromeV2Oracle, uniswapV3Oracle } = await loadFixture(initContracts);
await testRate(tokens.optimistic.USDC, tokens.optimistic.WETH, tokens.NONE, velodromeV2Oracle, uniswapV3Oracle);
});

it('WETH -> OP -> USDC', async function () {
const { velodromeV2Oracle, uniswapV3Oracle } = await loadFixture(initContracts);
await testRate(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.optimistic.OP, velodromeV2Oracle, uniswapV3Oracle);
});

const testRate = async function (srcToken, dstToken, connector, velodromeV2Oracle, uniswapV3Oracle) {
const velodromeV2Result = await velodromeV2Oracle.getRate(srcToken, dstToken, connector, thresholdFilter);
const v3Result = await uniswapV3Oracle.getRate(srcToken, dstToken, connector, thresholdFilter);
assertRoughlyEqualValues(v3Result.rate, velodromeV2Result.rate, 0.05);
};

describe('Measure gas', function () {
it('WETH -> USDC', async function () {
const { velodromeV2Oracle, uniswapV3Oracle } = await loadFixture(initContracts);
await measureGas(
await velodromeV2Oracle.getFunction('getRate').send(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.NONE, thresholdFilter),
'VelodromeV2Oracle WETH -> USDC',
);
await measureGas(
await uniswapV3Oracle.getFunction('getRate').send(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.NONE, thresholdFilter),
'UniswapV3Oracle WETH -> USDC',
);
});

it('USDC -> WETH', async function () {
const { velodromeV2Oracle, uniswapV3Oracle } = await loadFixture(initContracts);
await measureGas(
await velodromeV2Oracle.getFunction('getRate').send(tokens.optimistic.USDC, tokens.optimistic.WETH, tokens.NONE, thresholdFilter),
'VelodromeV2Oracle USDC -> WETH',
);
await measureGas(
await uniswapV3Oracle.getFunction('getRate').send(tokens.optimistic.USDC, tokens.optimistic.WETH, tokens.NONE, thresholdFilter),
'UniswapV3Oracle USDC -> WETH',
);
});

it('WETH -> OP -> USDC', async function () {
const { velodromeV2Oracle, uniswapV3Oracle } = await loadFixture(initContracts);
await measureGas(
await velodromeV2Oracle.getFunction('getRate').send(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.optimistic.OP, thresholdFilter),
'VelodromeV2Oracle WETH -> OP -> USDC',
);
await measureGas(
await uniswapV3Oracle.getFunction('getRate').send(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.optimistic.OP, thresholdFilter),
'UniswapV3Oracle WETH -> OP -> USDC',
);
});

async function measureGas (tx, comment) {
const receipt = await tx.wait();
console.log('gasUsed', comment, receipt.gasUsed.toString());
}
});
});
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
# yarn lockfile v1


"@1inch/[email protected].5":
version "3.5.5"
resolved "https://registry.yarnpkg.com/@1inch/solidity-utils/-/solidity-utils-3.5.5.tgz#14353aa36270e4213927c7de31088953aa277a25"
integrity sha512-uX9fzLLxQIvXB4gIczTZ11C2tRrqswJsXV7WNhdPn5CZnCrp8gyBGnuxLjEbY1jZK+NPs2AtZB7FD+/+0ZN+Kg==
"@1inch/[email protected].6":
version "3.5.6"
resolved "https://registry.yarnpkg.com/@1inch/solidity-utils/-/solidity-utils-3.5.6.tgz#4f5ed93e0c2246078b1d34faf0cbc2aa6bb79b3c"
integrity sha512-HK0dzmLsoyaL/2yWWha3SR1ZkkZzsxcEomDHofHyxryn4HKFF6MdnXOpspnx2PG038VF1XLKr11u5E43V2VOww==
dependencies:
"@metamask/eth-sig-util" "7.0.1"
"@nomicfoundation/hardhat-ethers" "3.0.5"
Expand Down
Loading