Skip to content

Commit

Permalink
Merge pull request #128 from simplyoptimistic/refactor/optimize-velod…
Browse files Browse the repository at this point in the history
…rome-oracle

Optimized v2 oracle implementation [SC-1132]
  • Loading branch information
zZoMROT authored Apr 17, 2024
2 parents 93bc4af + 4b4df7b commit 12fd543
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 7 deletions.
51 changes: 47 additions & 4 deletions contracts/oracles/VelodromeV2Oracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ interface IVelodramoV2Registry {
function poolFactories() external view returns (address[] memory);
}

interface IVelodromeV2Pool {
function getReserves() external view returns (uint256 _reserve0, uint256 _reserve1, uint256 _timestampLast);
}

contract VelodromeV2Oracle is IOracle {
using OraclePrices for OraclePrices.Data;
Expand All @@ -26,9 +29,20 @@ contract VelodromeV2Oracle is IOracle {
IVelodramoV2Router public immutable ROUTER;
IVelodramoV2Registry public immutable REGISTRY;

constructor(IVelodramoV2Router _router, IVelodramoV2Registry _registry) {
struct FactoryDescription {
address factory;
bytes32 initcodeHash;
}
address public immutable CL_FACTORY_ADDRESS; // Slipstream used as separate Oracle
address public immutable POOL_FACTORY_ADDRESS;
bytes32 public immutable POOL_FACTORY_INITCODE_HASH;

constructor(IVelodramoV2Router _router, IVelodramoV2Registry _registry, FactoryDescription memory _poolFactory, address _skipFactory) {
ROUTER = _router;
REGISTRY = _registry;
POOL_FACTORY_ADDRESS = _poolFactory.factory;
POOL_FACTORY_INITCODE_HASH = _poolFactory.initcodeHash;
CL_FACTORY_ADDRESS = _skipFactory;
}

function getRate(IERC20 srcToken, IERC20 dstToken, IERC20 connector, uint256 thresholdFilter) external view override returns (uint256 rate, uint256 weight) {
Expand All @@ -49,6 +63,9 @@ contract VelodromeV2Oracle is IOracle {
uint256 b0;
uint256 b1;
for (uint256 i = 0; i < factoriesLength; i++) {
if (factories[i] == CL_FACTORY_ADDRESS) {
continue;
}
(b0, b1) = _getReserves(srcToken, dstToken, true, factories[i]);
if (b0 > 0) {
ratesAndWeights.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt()));
Expand All @@ -62,8 +79,34 @@ contract VelodromeV2Oracle is IOracle {
}

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
if (factory == POOL_FACTORY_ADDRESS) {
(IERC20 token0, IERC20 token1) = srcToken < dstToken ? (srcToken, dstToken) : (dstToken, srcToken);
address pool = _getPool(token0, token1, stable, POOL_FACTORY_ADDRESS, POOL_FACTORY_INITCODE_HASH);

(bool success, bytes memory data) = pool.staticcall(abi.encodeWithSelector(IVelodromeV2Pool.getReserves.selector, pool));
if (success && data.length >= 64) {
(reserveSrc, reserveDst) = abi.decode(data, (uint256, uint256));
if (srcToken == token1) {
(reserveSrc, reserveDst) = (reserveDst, reserveSrc);
}
}
} else {
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
}
}

function _getPool(IERC20 token0, IERC20 token1, bool stable, address factory, bytes32 initcodeHash) private pure returns (address) {
return address(uint160(uint256(
keccak256(
abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1, stable)),
initcodeHash
)
)
)));
}
}
4 changes: 4 additions & 0 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ const deployParams = {
VelodromeV2: { // optimistic network
router: '0xa062aE8A9c5e11aaA026fc2670B0D65cCc8B2858',
registry: '0xF4c67CdEAaB8360370F41514d06e32CcD8aA1d7B',
poolFactory: {
factory: '0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a',
initcodeHash: '0xc0629f1c7daa09624e54d4f711ba99922a844907cce02997176399e4cc7e8fcf',
},
},
Slipstream: { // optimistic network
factory: '0x548118C7E0B865C2CfA94D15EC86B666468ac758',
Expand Down
58 changes: 55 additions & 3 deletions test/oracles/VelodromeV2Oracle.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
const { network } = require('hardhat');
const { network, ethers } = require('hardhat');
const { deployContract } = require('@1inch/solidity-utils');
const { resetHardhatNetworkFork } = require('@1inch/solidity-utils/hardhat-setup');
const {
tokens,
deployParams: { VelodromeV2, UniswapV3 },
deployParams: { Slipstream, VelodromeV2, UniswapV3 },
defaultValues: { thresholdFilter },
testRate,
measureGas,
Expand All @@ -20,7 +20,7 @@ describe('VelodromeV2Oracle', function () {
});

async function initContracts () {
const velodromeV2Oracle = await deployContract('VelodromeV2Oracle', [VelodromeV2.router, VelodromeV2.registry]);
const velodromeV2Oracle = await deployContract('VelodromeV2Oracle', [VelodromeV2.router, VelodromeV2.registry, VelodromeV2.poolFactory, Slipstream.factory]);
const uniswapV3Oracle = await deployContract('UniswapV3LikeOracle', [UniswapV3.factory, UniswapV3.initcodeHash, UniswapV3.fees]);
return { velodromeV2Oracle, uniswapV3Oracle };
}
Expand All @@ -41,6 +41,58 @@ describe('VelodromeV2Oracle', function () {
});

describe('Measure gas', function () {
async function initContractsForOffchainOracle () {
const { velodromeV2Oracle } = await loadFixture(initContracts);

const [wallet] = await ethers.getSigners();
const velodromeV2OraclePrevVersion = await ethers.getContractAt('VelodromeV2Oracle', '0x41674e58F339fE1caB03CA8DF095D46B998E6125');
const multiWrapper = await deployContract('MultiWrapper', [[], wallet.address]);
const offchainOracle = await deployContract('OffchainOracle', [
multiWrapper.target,
[velodromeV2OraclePrevVersion],
['0'],
[tokens.NONE],
tokens.optimistic.WETH,
wallet.address,
]);
const offchainOracleNew = await deployContract('OffchainOracle', [
multiWrapper.target,
[velodromeV2Oracle],
['0'],
[tokens.NONE],
tokens.optimistic.WETH,
wallet.address,
]);
return { offchainOracle, offchainOracleNew };
}

it('OffchainOracle with 1 connector', async function () {
const { offchainOracle, offchainOracleNew } = await loadFixture(initContractsForOffchainOracle);
await measureGas(
await offchainOracle.getFunction('getRateWithThreshold').send(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.NONE, thresholdFilter),
'offchainOracle (1 connector) WETH -> USDC',
);
await measureGas(
await offchainOracleNew.getFunction('getRateWithThreshold').send(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.NONE, thresholdFilter),
'offchainOracleNew (1 connector) WETH -> USDC',
);
});

it('OffchainOracle with 2 connectors', async function () {
const { offchainOracle, offchainOracleNew } = await loadFixture(initContractsForOffchainOracle);
await offchainOracle.addConnector(tokens.optimistic.OP);
await offchainOracleNew.addConnector(tokens.optimistic.OP);

await measureGas(
await offchainOracle.getFunction('getRateWithThreshold').send(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.NONE, thresholdFilter),
'offchainOracle (2 connectors) WETH -> USDC',
);
await measureGas(
await offchainOracleNew.getFunction('getRateWithThreshold').send(tokens.optimistic.WETH, tokens.optimistic.USDC, tokens.NONE, thresholdFilter),
'offchainOracleNew (2 connector) WETH -> USDC',
);
});

it('WETH -> USDC', async function () {
const { velodromeV2Oracle, uniswapV3Oracle } = await loadFixture(initContracts);
await measureGas(
Expand Down

0 comments on commit 12fd543

Please sign in to comment.