diff --git a/contracts/interfaces/ISolidlyFactory.sol b/contracts/interfaces/ISolidlyFactory.sol deleted file mode 100644 index e2a4d8b6..00000000 --- a/contracts/interfaces/ISolidlyFactory.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -interface ISolidlyFactory { - function getPair(IERC20 tokenA, IERC20 tokenB, bool stable) external view returns (address pair); -} diff --git a/contracts/oracles/SolidlyOracle.sol b/contracts/oracles/SolidlyOracle.sol index c0758437..1cb56fdf 100644 --- a/contracts/oracles/SolidlyOracle.sol +++ b/contracts/oracles/SolidlyOracle.sol @@ -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"; @@ -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) { @@ -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, @@ -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); } } } diff --git a/contracts/oracles/SolidlyOracleNoCreate2.sol b/contracts/oracles/SolidlyOracleNoCreate2.sol deleted file mode 100644 index 89269468..00000000 --- a/contracts/oracles/SolidlyOracleNoCreate2.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "@openzeppelin/contracts/utils/math/Math.sol"; -import "./OracleBase.sol"; -import "../interfaces/ISolidlyFactory.sol"; -import "../interfaces/IOracle.sol"; -import "../interfaces/IUniswapV2Pair.sol"; -import "../libraries/OraclePrices.sol"; - -contract SolidlyOracleNoCreate2 is IOracle { - using OraclePrices for OraclePrices.Data; - using Math for uint256; - - ISolidlyFactory public immutable FACTORY; - - IERC20 private constant _NONE = IERC20(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF); - - constructor(ISolidlyFactory _factory) { - FACTORY = _factory; - } - - function getRate(IERC20 srcToken, IERC20 dstToken, IERC20 connector, uint256 thresholdFilter) external view override returns (uint256 rate, uint256 weight) { - if(connector != _NONE) revert ConnectorShouldBeNone(); - OraclePrices.Data memory ratesAndWeights = OraclePrices.init(2); - (uint256 b0, uint256 b1) = _getBalances(srcToken, dstToken, true); - ratesAndWeights.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt())); - (b0, b1) = _getBalances(srcToken, dstToken, false); - ratesAndWeights.append(OraclePrices.OraclePrice(Math.mulDiv(b1, 1e18, b0), (b0 * b1).sqrt())); - return ratesAndWeights.getRateAndWeight(thresholdFilter); - } - - function _getBalances(IERC20 srcToken, IERC20 dstToken, bool stable) internal view returns (uint256 srcBalance, uint256 dstBalance) { - (IERC20 token0, IERC20 token1) = srcToken < dstToken ? (srcToken, dstToken) : (dstToken, srcToken); - (bool success, bytes memory data) = FACTORY.getPair(token0, token1, stable).staticcall(abi.encodeWithSelector(IUniswapV2Pair.getReserves.selector)); - 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); - } - } -} diff --git a/contracts/oracles/SolidlyOracleZksync.sol b/contracts/oracles/SolidlyOracleZksync.sol new file mode 100644 index 00000000..393c032c --- /dev/null +++ b/contracts/oracles/SolidlyOracleZksync.sol @@ -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("")) + ))))); + } +} diff --git a/test/helpers.js b/test/helpers.js index b414c266..999100a6 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -63,6 +63,7 @@ const tokens = { WETH: '0x4200000000000000000000000000000000000006', DAI: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', axlUSDC: '0xEB466342C4d449BC9f53A865D5Cb90586f405215', + rETH: '0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c', }, optimistic: { WETH: '0x4200000000000000000000000000000000000006', diff --git a/test/oracles/SolidlyOracle.js b/test/oracles/SolidlyOracle.js index f29bd973..503df163 100644 --- a/test/oracles/SolidlyOracle.js +++ b/test/oracles/SolidlyOracle.js @@ -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 () { @@ -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 () {