Skip to content

Commit

Permalink
Merge pull request #170 from 1inch/curve/rate-provider
Browse files Browse the repository at this point in the history
[SC-1127] Update CurveOracle | Curve Rate Provider
  • Loading branch information
zZoMROT authored Aug 8, 2024
2 parents 9b388c2 + 99f16c7 commit 74fdd1c
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 104 deletions.
16 changes: 16 additions & 0 deletions contracts/interfaces/ICurveProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,23 @@
pragma solidity 0.8.23;

// solhint-disable func-name-mixedcase
// solhint-disable var-name-mixedcase

interface ICurveProvider {
function get_address (uint256 _id) external view returns (address);
}

interface ICurveRateProvider {
struct Quote {
uint256 source_token_index;
uint256 dest_token_index;
bool is_underlying;
uint256 amount_out;
address pool;
uint256 source_token_pool_balance;
uint256 dest_token_pool_balance;
uint8 pool_type; // 0 for stableswap, 1 for cryptoswap, 2 for LLAMMA.
}

function get_quotes (address source_token, address destination_token, uint256 amount_in) external view returns (ICurveRateProvider.Quote[] memory quote);
}
47 changes: 47 additions & 0 deletions contracts/oracles/CurveOracleCRP.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

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

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

IERC20 private constant _NONE = IERC20(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF);
IERC20 private constant _ETH = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
uint256 private constant _RATE_PROVIDER_ID = 18;

ICurveRateProvider public immutable CURVE_RATE_PROVIDER;
uint256 public immutable MAX_POOLS;

constructor(ICurveProvider curveProvider, uint256 maxPools) {
CURVE_RATE_PROVIDER = ICurveRateProvider(curveProvider.get_address(_RATE_PROVIDER_ID));
MAX_POOLS = maxPools;
}

function getRate(IERC20 srcToken, IERC20 dstToken, IERC20 connector, uint256 thresholdFilter) external view override returns (uint256 rate, uint256 weight) {
if(connector != _NONE) revert ConnectorShouldBeNone();
uint256 amountIn;
if (srcToken == _ETH) {
amountIn = 10**18;
} else {
amountIn = 10**IERC20Metadata(IERC20Metadata(address(srcToken))).decimals();
}
ICurveRateProvider.Quote[] memory quotes = CURVE_RATE_PROVIDER.get_quotes(address(srcToken), address(dstToken), amountIn);

OraclePrices.Data memory ratesAndWeights = OraclePrices.init(quotes.length);
for (uint256 i = 0; i < quotes.length && ratesAndWeights.size < MAX_POOLS; i++) {
if (quotes[i].amount_out > 0) {
rate = quotes[i].amount_out * 1e18 / amountIn;
weight = (quotes[i].source_token_pool_balance * quotes[i].dest_token_pool_balance).sqrt();
ratesAndWeights.append(OraclePrices.OraclePrice(rate, weight));
}
}
return ratesAndWeights.getRateAndWeight(thresholdFilter);
}
}
233 changes: 129 additions & 104 deletions test/oracles/CurveOracle.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,148 +11,173 @@ const {
} = require('../helpers.js');

describe('CurveOracle', function () {
async function initContracts () {
const curveOracle = await deployContract('CurveOracle', [Curve.provider, Curve.maxPools]);
async function deployUniswapV3 () {
const uniswapV3Oracle = await deployContract('UniswapV3LikeOracle', [UniswapV3.factory, UniswapV3.initcodeHash, UniswapV3.fees]);
return { uniswapV3Oracle };
}

async function deployCurveOracle () {
const { uniswapV3Oracle } = await deployUniswapV3();
const curveOracle = await deployContract('CurveOracle', [Curve.provider, Curve.maxPools]);
return { curveOracle, uniswapV3Oracle };
}

it('USDT -> WBTC', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(initContracts);
await testRate(tokens.USDT, tokens.WBTC, tokens.NONE, curveOracle, uniswapV3Oracle);
});
async function deployCurveOracleCRP () {
const { uniswapV3Oracle } = await deployUniswapV3();
const curveOracle = await deployContract('CurveOracleCRP', [Curve.provider, Curve.maxPools]);
return { curveOracle, uniswapV3Oracle };
}

it('WBTC -> USDT', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(initContracts);
await testRate(tokens.WBTC, tokens.USDT, tokens.NONE, curveOracle, uniswapV3Oracle);
});
async function initContractsForCurveOrcle () {
const { uniswapV3Oracle } = await deployUniswapV3();
const { curveOracle } = await deployCurveOracle();
return { curveOracle, uniswapV3Oracle };
}

it('WBTC -> WETH', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(initContracts);
await testRate(tokens.WBTC, tokens.WETH, tokens.NONE, curveOracle, uniswapV3Oracle);
});
async function initContractsForCurveOrcleCRP () {
const { uniswapV3Oracle } = await deployUniswapV3();
const { curveOracle } = await deployCurveOracleCRP();
return { curveOracle, uniswapV3Oracle };
}

it('USDT -> USDC', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(initContracts);
await testRate(tokens.USDT, tokens.USDC, tokens.NONE, curveOracle, uniswapV3Oracle);
});
async function deployOffchainOraclesToCheckRuins (curveOracle) {
const [deployer] = await ethers.getSigners();

const uniswapV2LikeOracle = await deployContract('UniswapV2LikeOracle', [UniswapV2.factory, UniswapV2.initcodeHash]);
const uniswapOracle = await deployContract('UniswapOracle', [Uniswap.factory]);
const mooniswapOracle = await deployContract('MooniswapOracle', [tokens.oneInchLP1]);
const wethWrapper = await deployContract('BaseCoinWrapper', [tokens.ETH, tokens.WETH]);
const aaveWrapperV1 = await deployContract('AaveWrapperV1');
const aaveWrapperV2 = await deployContract('AaveWrapperV2', [AaveWrapperV2.lendingPool]);
await aaveWrapperV1.addMarkets([tokens.DAI]);
await aaveWrapperV2.addMarkets([tokens.DAI]);
const multiWrapper = await deployContract('MultiWrapper', [
[wethWrapper, aaveWrapperV1, aaveWrapperV2],
deployer,
]);

const oldOffchainOracle = await deployContract('OffchainOracle', [
multiWrapper,
[uniswapV2LikeOracle, uniswapOracle, mooniswapOracle],
['0', '1', '2'],
[tokens.NONE, tokens.ETH, tokens.WETH, tokens.USDC, tokens.DAI],
tokens.WETH,
deployer.address,
]);

const newOffchainOracle = await deployContract('OffchainOracle', [
multiWrapper,
[uniswapV2LikeOracle, uniswapOracle, mooniswapOracle, curveOracle],
['0', '1', '2', '2'],
[tokens.NONE, tokens.ETH, tokens.USDC, tokens.DAI],
tokens.WETH,
deployer.address,
]);

return { oldOffchainOracle, newOffchainOracle };
}

it('should use correct `get_dy` selector when vyper return redundant bytes', async function () {
const { curveOracle } = await loadFixture(initContracts);
const rate = await curveOracle.getRate(tokens.BEAN, tokens['3CRV'], tokens.NONE, thresholdFilter);
expect(rate.rate).to.gt('0');
});
async function initContractsToCheckRuinsForCurveOracle () {
const { curveOracle } = await deployCurveOracle();
return await deployOffchainOraclesToCheckRuins(curveOracle);
}

describe('Measure gas', function () {
async function initContractsToCheckRuinsForCurveOracleCRP () {
const { curveOracle } = await deployCurveOracleCRP();
return await deployOffchainOraclesToCheckRuins(curveOracle);
}

function shouldReturnCorrectPrices (fixture) {
it('USDT -> WBTC', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(initContracts);
const { curveOracle, uniswapV3Oracle } = await loadFixture(fixture);
await testRate(tokens.USDT, tokens.WBTC, tokens.NONE, curveOracle, uniswapV3Oracle);
});

it('WBTC -> USDT', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(fixture);
await testRate(tokens.WBTC, tokens.USDT, tokens.NONE, curveOracle, uniswapV3Oracle);
});

it('WBTC -> WETH', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(fixture);
await testRate(tokens.WBTC, tokens.WETH, tokens.NONE, curveOracle, uniswapV3Oracle);
});

it('USDT -> USDC', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(fixture);
await testRate(tokens.USDT, tokens.USDC, tokens.NONE, curveOracle, uniswapV3Oracle);
});

it('should use correct `get_dy` selector when vyper return redundant bytes', async function () {
const { curveOracle } = await loadFixture(fixture);
const rate = await curveOracle.getRate(tokens.BEAN, tokens['3CRV'], tokens.NONE, thresholdFilter);
expect(rate.rate).to.gt('0');
});
};

function shouldShowMeasureGas (fixture) {
it('USDT -> WBTC', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(fixture);
await measureGas(await curveOracle.getFunction('getRate').send(tokens.USDT, tokens.WBTC, tokens.NONE, thresholdFilter), 'CurveOracle usdt -> wbtc');
await measureGas(await uniswapV3Oracle.getFunction('getRate').send(tokens.USDT, tokens.WBTC, tokens.NONE, thresholdFilter), 'UniswapV3Oracle usdt -> wbtc');
});

it('WBTC -> USDT', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(initContracts);
const { curveOracle, uniswapV3Oracle } = await loadFixture(fixture);
await measureGas(await curveOracle.getFunction('getRate').send(tokens.WBTC, tokens.USDT, tokens.NONE, thresholdFilter), 'CurveOracle wbtc -> usdt');
await measureGas(await uniswapV3Oracle.getFunction('getRate').send(tokens.WBTC, tokens.USDT, tokens.NONE, thresholdFilter), 'UniswapV3Oracle wbtc -> usdt');
});

it('WBTC -> WETH', async function () {
const { curveOracle, uniswapV3Oracle } = await loadFixture(initContracts);
const { curveOracle, uniswapV3Oracle } = await loadFixture(fixture);
await measureGas(await curveOracle.getFunction('getRate').send(tokens.WBTC, tokens.WETH, tokens.NONE, thresholdFilter), 'CurveOracle wbtc -> weth');
await measureGas(await uniswapV3Oracle.getFunction('getRate').send(tokens.WBTC, tokens.WETH, tokens.NONE, thresholdFilter), 'UniswapV3Oracle wbtc -> weth');
});
});

describe('CurveOracle doesn\'t ruin rates', function () {
async function initContracts () {
const [deployer] = await ethers.getSigners();

const uniswapV2LikeOracle = await deployContract('UniswapV2LikeOracle', [UniswapV2.factory, UniswapV2.initcodeHash]);
const curveOracle = await deployContract('CurveOracle', [Curve.provider, Curve.maxPools]);
const uniswapOracle = await deployContract('UniswapOracle', [Uniswap.factory]);
const mooniswapOracle = await deployContract('MooniswapOracle', [tokens.oneInchLP1]);
const wethWrapper = await deployContract('BaseCoinWrapper', [tokens.ETH, tokens.WETH]);
const aaveWrapperV1 = await deployContract('AaveWrapperV1');
const aaveWrapperV2 = await deployContract('AaveWrapperV2', [AaveWrapperV2.lendingPool]);
await aaveWrapperV1.addMarkets([tokens.DAI]);
await aaveWrapperV2.addMarkets([tokens.DAI]);
const multiWrapper = await deployContract('MultiWrapper', [
[
wethWrapper,
aaveWrapperV1,
aaveWrapperV2,
],
deployer,
]);

const oldOffchainOracle = await deployContract('OffchainOracle', [
multiWrapper,
[
uniswapV2LikeOracle,
uniswapOracle,
mooniswapOracle,
],
[
'0',
'1',
'2',
],
[
tokens.NONE,
tokens.ETH,
tokens.WETH,
tokens.USDC,
tokens.DAI,
],
tokens.WETH,
deployer.address,
]);

const newOffchainOracle = await deployContract('OffchainOracle', [
multiWrapper,
[
uniswapV2LikeOracle,
uniswapOracle,
mooniswapOracle,
curveOracle,
],
[
'0',
'1',
'2',
'2',
],
[
tokens.NONE,
tokens.ETH,
tokens.USDC,
tokens.DAI,
],
tokens.WETH,
deployer.address,
]);

return { oldOffchainOracle, newOffchainOracle };
}
};

function shouldNotRuintRate (fixture) {
it('WBTC -> WETH', async function () {
const { oldOffchainOracle, newOffchainOracle } = await loadFixture(initContracts);
const { oldOffchainOracle, newOffchainOracle } = await loadFixture(fixture);
await testRateOffchainOracle(tokens.WBTC, tokens.WETH, oldOffchainOracle, newOffchainOracle);
});

it('WETH -> WBTC', async function () {
const { oldOffchainOracle, newOffchainOracle } = await loadFixture(initContracts);
const { oldOffchainOracle, newOffchainOracle } = await loadFixture(fixture);
await testRateOffchainOracle(tokens.WETH, tokens.WBTC, oldOffchainOracle, newOffchainOracle);
});

it('WBTC -> USDT', async function () {
const { oldOffchainOracle, newOffchainOracle } = await loadFixture(initContracts);
const { oldOffchainOracle, newOffchainOracle } = await loadFixture(fixture);
await testRateOffchainOracle(tokens.WBTC, tokens.USDT, oldOffchainOracle, newOffchainOracle);
});

it('USDT -> WBTC', async function () {
const { oldOffchainOracle, newOffchainOracle } = await loadFixture(initContracts);
const { oldOffchainOracle, newOffchainOracle } = await loadFixture(fixture);
await testRateOffchainOracle(tokens.USDT, tokens.WBTC, oldOffchainOracle, newOffchainOracle);
});
};

describe('CurveRateProvider logic implemetation', function () {
shouldReturnCorrectPrices(initContractsForCurveOrcle);

describe('Measure gas', function () {
shouldShowMeasureGas(initContractsForCurveOrcle);
});

describe('CurveOracle doesn\'t ruin rates', function () {
shouldNotRuintRate(initContractsToCheckRuinsForCurveOracle);
});
});

describe('CurveRateProvider', function () {
shouldReturnCorrectPrices(initContractsForCurveOrcleCRP);

describe('Measure gas', function () {
shouldShowMeasureGas(initContractsForCurveOrcleCRP);
});

describe('CurveOracle doesn\'t ruin rates', function () {
shouldNotRuintRate(initContractsToCheckRuinsForCurveOracleCRP);
});
});
});

0 comments on commit 74fdd1c

Please sign in to comment.