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-1269] Patch SolidlyOracle #188

Merged
merged 12 commits into from
Sep 19, 2024
9 changes: 0 additions & 9 deletions contracts/interfaces/ISolidlyFactory.sol

This file was deleted.

61 changes: 53 additions & 8 deletions contracts/oracles/SolidlyOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity 0.8.23;

import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import "./OracleBase.sol";
import "../interfaces/IOracle.sol";
import "../interfaces/IUniswapV2Pair.sol";
Expand All @@ -23,21 +24,34 @@ contract SolidlyOracle is IOracle {
}

function getRate(IERC20 srcToken, IERC20 dstToken, IERC20 connector, uint256 thresholdFilter) external view override returns (uint256 rate, uint256 weight) {
uint256 srcDecimals = IERC20Metadata(address(srcToken)).decimals();
uint256 dstDecimals = IERC20Metadata(address(dstToken)).decimals();
if (connector == _NONE) {
(rate, weight) = _getWeightedRate(srcToken, dstToken, thresholdFilter);
(rate, weight) = _getWeightedRate(srcToken, dstToken, srcDecimals, dstDecimals, thresholdFilter);
} else {
(uint256 rateC0, uint256 weightC0) = _getWeightedRate(srcToken, connector, thresholdFilter);
(uint256 rateC1, uint256 weightC1) = _getWeightedRate(connector, dstToken, thresholdFilter);
uint256 connectorDecimals = IERC20Metadata(address(connector)).decimals();
(uint256 rateC0, uint256 weightC0) = _getWeightedRate(srcToken, connector, srcDecimals, connectorDecimals, thresholdFilter);
(uint256 rateC1, uint256 weightC1) = _getWeightedRate(connector, dstToken, connectorDecimals, dstDecimals, thresholdFilter);
rate = rateC0 * rateC1 / 1e18;
weight = Math.min(weightC0, weightC1);
}
}

function _getWeightedRate(IERC20 srcToken, IERC20 dstToken, uint256 thresholdFilter) internal view returns (uint256 rate, uint256 weight) {
function _getWeightedRate(IERC20 srcToken, IERC20 dstToken, uint256 srcDecimals, uint256 dstDecimals, uint256 thresholdFilter) internal view returns (uint256 rate, uint256 weight) {
OraclePrices.Data memory ratesAndWeights = OraclePrices.init(2);
(uint256 b0, uint256 b1) = _getBalances(srcToken, dstToken, true);
if (b0 > 0) {
ratesAndWeights.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt()));
uint256 _x = (b0 * 1e18) / 10 ** srcDecimals; // b0 converted to 1e18 decimals format
uint256 _y = (b1 * 1e18) / 10 ** dstDecimals; // b1 converted to 1e18 decimals format
uint256 _a = (_x * _y) / 1e18;
uint256 _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18);
uint256 xy = (_a * _b) / 1e18;

(uint256 y, bool error) = _getY(1e18 + _x , xy, _y); // calculation for 1 src token converted to 1e18 decimals format
if (!error) {
uint256 amountOut = b1 - y / (10 ** (18 - dstDecimals));
ratesAndWeights.append(OraclePrices.OraclePrice(amountOut, (b0 * b1).sqrt()));
}
}
(b0, b1) = _getBalances(srcToken, dstToken, false);
if (b0 > 0) {
Expand All @@ -46,8 +60,41 @@ contract SolidlyOracle is IOracle {
(rate, weight) = ratesAndWeights.getRateAndWeight(thresholdFilter);
}

// Helper function to compute 'y' based on the stable swap invariant
function _getY(uint256 x0, uint256 xy, uint256 y0) internal pure returns (uint256 y, bool error) {
y = y0;
for (uint256 i = 0; i < 255; i++) {
uint256 k = _f(x0, y);
if (k < xy) {
uint256 dy = ((xy - k) * 1e18) / _d(x0, y);
if (dy == 0) {
return (y, false);
}
y = y + dy;
} else {
uint256 dy = ((k - xy) * 1e18) / _d(x0, y);
if (dy == 0) {
return (y, false);
}
y = y - dy;
}
}
return (0, true);
}

// Internal functions '_f' and '_d' as per the original code
function _f(uint256 x0, uint256 y) internal pure returns (uint256) {
uint256 _a = (x0 * y) / 1e18;
uint256 _b = ((x0 * x0) / 1e18 + (y * y) / 1e18);
return (_a * _b) / 1e18;
}

function _d(uint256 x0, uint256 y) internal pure returns (uint256) {
return (3 * x0 * ((y * y) / 1e18)) / 1e18 + ((((x0 * x0) / 1e18) * x0) / 1e18);
}

// calculates the CREATE2 address for a pair without making any external calls
function _pairFor(IERC20 tokenA, IERC20 tokenB, bool stable) private view returns (address pair) {
function _pairFor(IERC20 tokenA, IERC20 tokenB, bool stable) internal virtual view returns (address pair) {
pair = address(uint160(uint256(keccak256(abi.encodePacked(
hex"ff",
FACTORY,
Expand All @@ -62,8 +109,6 @@ contract SolidlyOracle is IOracle {
if (success && data.length == 96) {
(srcBalance, dstBalance) = abi.decode(data, (uint256, uint256));
(srcBalance, dstBalance) = srcToken == token0 ? (srcBalance, dstBalance) : (dstBalance, srcBalance);
} else {
(srcBalance, dstBalance) = (1, 0);
}
}
}
44 changes: 0 additions & 44 deletions contracts/oracles/SolidlyOracleNoCreate2.sol

This file was deleted.

22 changes: 22 additions & 0 deletions contracts/oracles/SolidlyOracleZksync.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

import "./SolidlyOracle.sol";

contract SolidlyOracleZksync is SolidlyOracle {
/// @dev keccak256("zksyncCreate2")
bytes32 public constant CREATE2_PREFIX = 0x2020dba91b30cc0006188af794c2fb30dd8520db7e2c088b7fc7c103c00ca494;

constructor(address _factory, bytes32 _initcodeHash) SolidlyOracle(_factory, _initcodeHash) {}

function _pairFor(IERC20 tokenA, IERC20 tokenB, bool stable) internal override view returns (address pair) {
pair = address(uint160(uint256(keccak256(abi.encodePacked(
CREATE2_PREFIX,
FACTORY,
keccak256(abi.encodePacked(tokenA, tokenB, stable)),
INITCODE_HASH,
keccak256(abi.encodePacked(""))
)))));
}
}
1 change: 1 addition & 0 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const tokens = {
WETH: '0x4200000000000000000000000000000000000006',
DAI: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
axlUSDC: '0xEB466342C4d449BC9f53A865D5Cb90586f405215',
rETH: '0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c',
},
optimistic: {
WETH: '0x4200000000000000000000000000000000000006',
Expand Down
21 changes: 19 additions & 2 deletions test/oracles/SolidlyOracle.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ describe('SolidlyOracle', function () {
});

async function initContracts () {
const velocimeterV2Oracle = await deployContract('SolidlyOracle', [VelocimeterV2.factory, VelocimeterV2.initcodeHash]);
const uniswapV3Oracle = await deployContract('UniswapV3LikeOracle', [UniswapV3Base.factory, UniswapV3Base.initcodeHash, UniswapV3Base.fees]);
return { velocimeterV2Oracle, uniswapV3Oracle };
return { uniswapV3Oracle };
}

async function deployVelocimeterV2 () {
Expand Down Expand Up @@ -56,6 +55,24 @@ describe('SolidlyOracle', function () {
const { oracle, uniswapV3Oracle } = await loadFixture(fixture);
await testRate(tokens.base.DAI, tokens.base.WETH, tokens.NONE, oracle, uniswapV3Oracle, 0.1);
});

it('rETH -> WETH', async function () {
const { oracle, uniswapV3Oracle } = await loadFixture(fixture);
// Test only for Aerodrome
if (await oracle.FACTORY() !== Aerodrome.factory) {
this.skip();
}
await testRate(tokens.base.rETH, tokens.base.WETH, tokens.NONE, oracle, uniswapV3Oracle, 0.1);
});

it('WETH -> rETH', async function () {
const { oracle, uniswapV3Oracle } = await loadFixture(fixture);
// Test only for Aerodrome
if (await oracle.FACTORY() !== Aerodrome.factory) {
this.skip();
}
await testRate(tokens.base.WETH, tokens.base.rETH, tokens.NONE, oracle, uniswapV3Oracle, 0.1);
});
}

describe('VelocimeterV2', function () {
Expand Down
Loading