Skip to content

Commit

Permalink
feat: implemented decimals in usd oracle
Browse files Browse the repository at this point in the history
* test: implemented decimals in oracle struct

* test: individual struct for usd oracle

* fix: rm kp3r decimals

* feat: added decimals to quote calculation

* fix: unit tests

* fix: fixture tests

* fix: testnet fixture tests

* fix: convention naming

---------

Co-authored-by: 0xGorilla <[email protected]>
  • Loading branch information
wei3erHase and 0xGorilla authored Feb 20, 2023
1 parent 70c8619 commit b18e294
Show file tree
Hide file tree
Showing 12 changed files with 129 additions and 67 deletions.
4 changes: 2 additions & 2 deletions deploy/2-sidechain/203_keep3r_helper_and_sidechain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DeployFunction } from 'hardhat-deploy/types';
import { HardhatRuntimeEnvironment } from 'hardhat/types';

const deployFunction: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployer, governor, kp3rV1, weth, kp3rWethOracle, wethUsdOracle } = await hre.getNamedAccounts();
const { deployer, governor, kp3rV1, weth, kp3rWethOracle, wethUsdOracle, usdDecimals } = await hre.getNamedAccounts();
const { kp3rV1: mainnetKp3rV1 } = await hre.companionNetworks['mainnet'].getNamedAccounts();

const keep3rEscrow = await hre.deployments.get('Keep3rEscrow');
Expand All @@ -11,7 +11,7 @@ const deployFunction: DeployFunction = async function (hre: HardhatRuntimeEnviro
const currentNonce: number = await hre.ethers.provider.getTransactionCount(deployer);
const keeperV2Address: string = hre.ethers.utils.getContractAddress({ from: deployer, nonce: currentNonce + 1 });

const keep3rHelperArgs = [keeperV2Address, governor, mainnetKp3rV1, weth, kp3rWethOracle, wethUsdOracle];
const keep3rHelperArgs = [keeperV2Address, governor, mainnetKp3rV1, weth, kp3rWethOracle, wethUsdOracle, usdDecimals];

const keep3rHelper = await hre.deployments.deploy('Keep3rHelperSidechain', {
from: deployer,
Expand Down
4 changes: 2 additions & 2 deletions deploy/3-sidechain-test/301_keep3r_helper_and_sidechain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DeployFunction } from 'hardhat-deploy/types';
import { HardhatRuntimeEnvironment } from 'hardhat/types';

const deployFunction: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployer, governor, kp3rV1, kp3rWethOracle, wethUsdOracle } = await hre.getNamedAccounts();
const { deployer, governor, kp3rV1, kp3rWethOracle, wethUsdOracle, usdDecimals } = await hre.getNamedAccounts();
const { kp3rV1: mainnetKp3rV1, weth: mainnetWeth } = await hre.companionNetworks['mainnet'].getNamedAccounts();

const keep3rEscrow = await hre.deployments.get('Keep3rEscrow');
Expand All @@ -11,7 +11,7 @@ const deployFunction: DeployFunction = async function (hre: HardhatRuntimeEnviro
const currentNonce: number = await hre.ethers.provider.getTransactionCount(deployer);
const keeperV2Address: string = hre.ethers.utils.getContractAddress({ from: deployer, nonce: currentNonce + 1 });

const keep3rHelperArgs = [keeperV2Address, governor, mainnetKp3rV1, mainnetWeth, kp3rWethOracle, wethUsdOracle];
const keep3rHelperArgs = [keeperV2Address, governor, mainnetKp3rV1, mainnetWeth, kp3rWethOracle, wethUsdOracle, usdDecimals];

const keep3rHelper = await hre.deployments.deploy('Keep3rHelperSidechain', {
from: deployer,
Expand Down
2 changes: 1 addition & 1 deletion solidity/contracts/Keep3rHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ contract Keep3rHelper is IKeep3rHelper, Keep3rHelperParameters {

(int56[] memory _tickCumulatives, ) = IUniswapV3Pool(kp3rWethPool.poolAddress).observe(_secondsAgos);
int56 _difference = _tickCumulatives[0] - _tickCumulatives[1];
_amountOut = getQuoteAtTick(uint128(_eth), kp3rWethPool.isTKNToken0 ? _difference : -_difference, quoteTwapTime);
_amountOut = getQuoteAtTick(uint128(_eth), kp3rWethPool.isKP3RToken0 ? _difference : -_difference, quoteTwapTime);
}

/// @inheritdoc IKeep3rHelper
Expand Down
19 changes: 9 additions & 10 deletions solidity/contracts/Keep3rHelperParameters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ contract Keep3rHelperParameters is IKeep3rHelperParameters, IBaseErrors, Governa
address public override keep3rV2;

/// @inheritdoc IKeep3rHelperParameters
IKeep3rHelperParameters.TokenOraclePool public override kp3rWethPool;
IKeep3rHelperParameters.Kp3rWethOraclePool public override kp3rWethPool;

constructor(
address _kp3r,
Expand All @@ -57,8 +57,9 @@ contract Keep3rHelperParameters is IKeep3rHelperParameters, IBaseErrors, Governa
keep3rV2 = _keep3rV2;

// Immutable variables [KP3R] cannot be read during contract creation time [_setKp3rWethPool]
kp3rWethPool = _validateOraclePool(_kp3rWethPool, _kp3r);
emit Kp3rWethPoolChange(kp3rWethPool.poolAddress, kp3rWethPool.isTKNToken0);
bool _isKP3RToken0 = _validateOraclePool(_kp3rWethPool, KP3R);
kp3rWethPool = Kp3rWethOraclePool(_kp3rWethPool, _isKP3RToken0);
emit Kp3rWethPoolChange(_kp3rWethPool, _isKP3RToken0);
}

/// @inheritdoc IKeep3rHelperParameters
Expand Down Expand Up @@ -123,15 +124,13 @@ contract Keep3rHelperParameters is IKeep3rHelperParameters, IBaseErrors, Governa
/// @notice Sets KP3R-WETH pool
/// @param _poolAddress The address of the KP3R-WETH pool
function _setKp3rWethPool(address _poolAddress) internal {
kp3rWethPool = _validateOraclePool(_poolAddress, KP3R);
emit Kp3rWethPoolChange(kp3rWethPool.poolAddress, kp3rWethPool.isTKNToken0);
bool _isKP3RToken0 = _validateOraclePool(_poolAddress, KP3R);
kp3rWethPool = Kp3rWethOraclePool(_poolAddress, _isKP3RToken0);
emit Kp3rWethPoolChange(_poolAddress, _isKP3RToken0);
}

function _validateOraclePool(address _poolAddress, address _token) internal view virtual returns (TokenOraclePool memory _oraclePool) {
bool _isTKNToken0 = IUniswapV3Pool(_poolAddress).token0() == _token;

function _validateOraclePool(address _poolAddress, address _token) internal view virtual returns (bool _isTKNToken0) {
_isTKNToken0 = IUniswapV3Pool(_poolAddress).token0() == _token;
if (!_isTKNToken0 && IUniswapV3Pool(_poolAddress).token1() != _token) revert InvalidOraclePool();

return TokenOraclePool(_poolAddress, _isTKNToken0);
}
}
29 changes: 20 additions & 9 deletions solidity/contracts/sidechain/Keep3rHelperSidechain.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ contract Keep3rHelperSidechain is IKeep3rHelperSidechain, Keep3rHelper {
/// @inheritdoc IKeep3rHelperSidechain
mapping(address => address) public override oracle;
/// @inheritdoc IKeep3rHelperSidechain
IKeep3rHelperParameters.TokenOraclePool public override wethUSDPool;
IKeep3rHelperSidechain.WethUsdOraclePool public override wethUSDPool;

/// @notice Ethereum mainnet WETH address used for quoting references
address public immutable override WETH;

/// @dev Amount of decimals in which USD is quoted within the contract
uint256 constant _USD_BASE_DECIMALS = 18;

/// @param _keep3rV2 Address of sidechain Keep3r implementation
/// @param _governor Address of governor
/// @param _kp3rWethOracle Address of oracle used for KP3R/WETH quote
Expand All @@ -42,10 +45,16 @@ contract Keep3rHelperSidechain is IKeep3rHelperSidechain, Keep3rHelper {
address _kp3r,
address _weth,
address _kp3rWethOracle,
address _wethUsdOracle
address _wethUsdOracle,
uint8 _usdDecimals
) Keep3rHelper(_kp3r, _keep3rV2, _governor, _kp3rWethOracle) {
WETH = _weth;
wethUSDPool = _validateOraclePool(_wethUsdOracle, _weth);

// Immutable variables [KP3R] cannot be read during contract creation time [_setKp3rWethPool]
bool _isWETHToken0 = _validateOraclePool(_wethUsdOracle, WETH);
wethUSDPool = WethUsdOraclePool(_wethUsdOracle, _isWETHToken0, _usdDecimals);
emit WethUSDPoolChange(wethUSDPool.poolAddress, wethUSDPool.isWETHToken0, _usdDecimals);

_setQuoteTwapTime(1 days);
workExtraGas = 0;
}
Expand All @@ -68,17 +77,18 @@ contract Keep3rHelperSidechain is IKeep3rHelperSidechain, Keep3rHelper {
function quoteUsdToEth(uint256 _usd) public view virtual override returns (uint256 _amountOut) {
uint32[] memory _secondsAgos = new uint32[](2);
_secondsAgos[1] = quoteTwapTime;
_usd = _usd / 10**(_USD_BASE_DECIMALS - wethUSDPool.usdDecimals);

/// @dev Oracle is compatible with IUniswapV3Pool
(int56[] memory _tickCumulatives, ) = IUniswapV3Pool(wethUSDPool.poolAddress).observe(_secondsAgos);
int56 _difference = _tickCumulatives[0] - _tickCumulatives[1];
_amountOut = getQuoteAtTick(uint128(_usd), wethUSDPool.isTKNToken0 ? _difference : -_difference, quoteTwapTime);
_amountOut = getQuoteAtTick(uint128(_usd), wethUSDPool.isWETHToken0 ? _difference : -_difference, quoteTwapTime);
}

/// @inheritdoc IKeep3rHelperSidechain
function setWethUsdPool(address _poolAddress) external override onlyGovernor {
function setWethUsdPool(address _poolAddress, uint8 _usdDecimals) external override onlyGovernor {
if (_poolAddress == address(0)) revert ZeroAddress();
_setWethUsdPool(_poolAddress);
_setWethUsdPool(_poolAddress, _usdDecimals);
}

/// @inheritdoc IKeep3rHelper
Expand All @@ -98,9 +108,10 @@ contract Keep3rHelperSidechain is IKeep3rHelperSidechain, Keep3rHelper {
_extraGas = workExtraGas;
}

function _setWethUsdPool(address _poolAddress) internal {
wethUSDPool = _validateOraclePool(_poolAddress, WETH);
emit WethUSDPoolChange(wethUSDPool.poolAddress, wethUSDPool.isTKNToken0);
function _setWethUsdPool(address _poolAddress, uint8 _usdDecimals) internal {
bool _isWETHToken0 = _validateOraclePool(_poolAddress, WETH);
wethUSDPool = WethUsdOraclePool(_poolAddress, _isWETHToken0, _usdDecimals);
emit WethUSDPoolChange(wethUSDPool.poolAddress, wethUSDPool.isWETHToken0, _usdDecimals);
}

/// @dev Sidechain jobs are quoted by USD/gasUnit, baseFee is set to 1
Expand Down
8 changes: 4 additions & 4 deletions solidity/interfaces/IKeep3rHelperParameters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ interface IKeep3rHelperParameters {

/// @dev KP3R-WETH Pool address and isKP3RToken0
/// @dev Created in order to save gas by avoiding calls to pool's token0 method
struct TokenOraclePool {
struct Kp3rWethOraclePool {
address poolAddress;
bool isTKNToken0;
bool isKP3RToken0;
}

// Errors
Expand Down Expand Up @@ -70,8 +70,8 @@ interface IKeep3rHelperParameters {

/// @notice KP3R-WETH pool that is being used as oracle
/// @return poolAddress Address of the pool
/// @return isTKNToken0 True if calling the token0 method of the pool returns the KP3R token address
function kp3rWethPool() external view returns (address poolAddress, bool isTKNToken0);
/// @return isKP3RToken0 True if calling the token0 method of the pool returns the KP3R token address
function kp3rWethPool() external view returns (address poolAddress, bool isKP3RToken0);

/// @notice The minimum multiplier used to calculate the amount of gas paid to the Keeper for the gas used to perform a job
/// For example: if the quoted gas used is 1000, then the minimum amount to be paid will be 1000 * minBoost / BOOST_BASE
Expand Down
28 changes: 24 additions & 4 deletions solidity/interfaces/sidechain/IKeep3rHelperSidechain.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ import '../IKeep3rHelper.sol';
/// @title Keep3rHelperSidechain contract
/// @notice Contains all the helper functions for sidechain keep3r implementations
interface IKeep3rHelperSidechain is IKeep3rHelper {
// Structs

/// @dev WETH-USD Pool address, isWETHToken0 and usdDecimals
/// @dev Created in order to quote any kind of USD tokens
struct WethUsdOraclePool {
address poolAddress;
bool isWETHToken0;
uint8 usdDecimals;
}

// Events

/// @notice The oracle for a liquidity has been saved
Expand All @@ -16,7 +26,8 @@ interface IKeep3rHelperSidechain is IKeep3rHelper {
/// @notice Emitted when the WETH USD pool is changed
/// @param _address Address of the new WETH USD pool
/// @param _isWETHToken0 True if calling the token0 method of the pool returns the WETH token address
event WethUSDPoolChange(address _address, bool _isWETHToken0);
/// @param _usdDecimals The amount of decimals of the USD token paired with ETH
event WethUSDPoolChange(address _address, bool _isWETHToken0, uint8 _usdDecimals);

/// Variables

Expand All @@ -30,8 +41,16 @@ interface IKeep3rHelperSidechain is IKeep3rHelper {

/// @notice WETH-USD pool that is being used as oracle
/// @return poolAddress Address of the pool
/// @return isTKNToken0 True if calling the token0 method of the pool returns the WETH token address
function wethUSDPool() external view returns (address poolAddress, bool isTKNToken0);
/// @return isWETHToken0 True if calling the token0 method of the pool returns the WETH token address
/// @return usdDecimals The amount of decimals of the USD token paired with ETH
function wethUSDPool()
external
view
returns (
address poolAddress,
bool isWETHToken0,
uint8 usdDecimals
);

/// @notice Quotes USD to ETH
/// @dev Used to know how much ETH should be paid to keepers before converting it from ETH to KP3R
Expand All @@ -49,6 +68,7 @@ interface IKeep3rHelperSidechain is IKeep3rHelper {

/// @notice Sets an oracle for querying WETH/USD quote
/// @param _poolAddress The address of the pool used as oracle
/// @param _usdDecimals The amount of decimals of the USD token paired with ETH
/// @dev The oracle must contain WETH as either token0 or token1
function setWethUsdPool(address _poolAddress) external;
function setWethUsdPool(address _poolAddress, uint8 _usdDecimals) external;
}
1 change: 1 addition & 0 deletions test/e2e/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const RICH_KP3R_WETH_POOL_ADDRESS = '0x2269522ad48aeb971b25042471a44acc8c
export const KP3R_WETH_POOL_ADDRESS = '0xaf988afF99d3d0cb870812C325C588D8D8CB7De8';
export const KP3R_WETH_V3_POOL_ADDRESS = '0x11B7a6bc0259ed6Cf9DB8F499988F9eCc7167bf5';
export const WETH_DAI_V3_POOL_ADDRESS = '0xc2e9f25be6257c210d7adf0d4cd6e3e881ba25f8';
export const WETH_USDC_V3_POOL_ADDRESS = '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640';
export const UNISWAP_V2_ROUTER_02_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D';
export const UNISWAP_V2_FACTORY_ADDRESS = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f';
export const KP3R_V1_ADDRESS = '0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44';
Expand Down
15 changes: 12 additions & 3 deletions test/e2e/keep3r-multichain.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ import chai, { expect } from 'chai';
import { solidity } from 'ethereum-waffle';
import { BigNumber } from 'ethers';
import { ethers } from 'hardhat';
import { KP3R_V1_ADDRESS, KP3R_WETH_V3_POOL_ADDRESS, PAIR_MANAGER_ADDRESS, WETH_ADDRESS, WETH_DAI_V3_POOL_ADDRESS } from './common';
import {
KP3R_V1_ADDRESS,
KP3R_WETH_V3_POOL_ADDRESS,
PAIR_MANAGER_ADDRESS,
WETH_ADDRESS,
WETH_DAI_V3_POOL_ADDRESS,
WETH_USDC_V3_POOL_ADDRESS,
} from './common';

const kp3rWhaleAddress = '0xa0f75491720835b36edc92d06ddc468d201e9b73';

Expand All @@ -31,7 +38,7 @@ const DAY = 86400;
const ONE = bn.toUnit(1);
const BONDS = bn.toUnit(10);
const ESCROW_AMOUNT = bn.toUnit(100);
const DELTA = bn.toUnit(0.001).toNumber();
const DELTA = bn.toUnit(0.001).toNumber(); // in KP3Rs (18 decimals)

describe('Keep3r Sidechain @skip-on-coverage', () => {
let deployer: SignerWithAddress;
Expand Down Expand Up @@ -94,7 +101,9 @@ describe('Keep3r Sidechain @skip-on-coverage', () => {
KP3R_V1_ADDRESS,
WETH_ADDRESS,
KP3R_WETH_V3_POOL_ADDRESS, // uses KP3R-WETH pool as oracle
WETH_DAI_V3_POOL_ADDRESS // uses WETH-DAI pool as oracle
// helper uses WETH-USDC and test uses WETH-DAI to verify
WETH_USDC_V3_POOL_ADDRESS, // uses WETH-USDC pool as oracle
6 // USDC has 6 decimals
);

const kp3rSidechainFactory = (await ethers.getContractFactory('Keep3rSidechain')) as Keep3rSidechain__factory;
Expand Down
24 changes: 12 additions & 12 deletions test/unit/Keep3rHelperParameters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ describe('Keep3rHelperParameters', () => {
expect(assignedAddress).to.equal(pool.address);
});

it('should set kp3rWethPool isTKNToken0 to true if KP3R is token0', async () => {
it('should set kp3rWethPool isKP3RToken0 to true if KP3R is token0', async () => {
parameters = await parametersFactory.deploy(KP3R_V1_ADDRESS, randomKeep3rV2Address, governor.address, pool.address);
const isTKNToken0 = (await parameters.callStatic.kp3rWethPool()).isTKNToken0;
expect(isTKNToken0).to.be.true;
const isKP3RToken0 = (await parameters.callStatic.kp3rWethPool()).isKP3RToken0;
expect(isKP3RToken0).to.be.true;
});

it('should set kp3rWethPool isTKNToken0 to false if KP3R is not token0', async () => {
it('should set kp3rWethPool isKP3RToken0 to false if KP3R is not token0', async () => {
pool.token0.returns(generateRandomAddress());
pool.token1.returns(KP3R_V1_ADDRESS);
parameters = await parametersFactory.deploy(KP3R_V1_ADDRESS, randomKeep3rV2Address, governor.address, pool.address);
const isTKNToken0 = (await parameters.callStatic.kp3rWethPool()).isTKNToken0;
expect(isTKNToken0).to.be.false;
const isKP3RToken0 = (await parameters.callStatic.kp3rWethPool()).isKP3RToken0;
expect(isKP3RToken0).to.be.false;
});
});

Expand All @@ -79,19 +79,19 @@ describe('Keep3rHelperParameters', () => {
await expect(parameters.connect(governor).setKp3rWethPool(ZERO_ADDRESS)).to.be.revertedWith('ZeroAddress()');
});

it('should set kp3rWethPool isTKNToken0 to true if KP3R is token0', async () => {
it('should set kp3rWethPool isKP3RToken0 to true if KP3R is token0', async () => {
await parameters.connect(governor).setKp3rWethPool(otherPool.address);
const isTKNToken0 = (await parameters.callStatic.kp3rWethPool()).isTKNToken0;
expect(isTKNToken0).to.be.true;
const isKP3RToken0 = (await parameters.callStatic.kp3rWethPool()).isKP3RToken0;
expect(isKP3RToken0).to.be.true;
});

it('should set kp3rWethPool isTKNToken0 to false if KP3R is not token0', async () => {
it('should set kp3rWethPool isKP3RToken0 to false if KP3R is not token0', async () => {
otherPool.token0.returns(generateRandomAddress());
otherPool.token1.returns(KP3R_V1_ADDRESS);

await parameters.connect(governor).setKp3rWethPool(otherPool.address);
const isTKNToken0 = (await parameters.callStatic.kp3rWethPool()).isTKNToken0;
expect(isTKNToken0).to.be.false;
const isKP3RToken0 = (await parameters.callStatic.kp3rWethPool()).isKP3RToken0;
expect(isKP3RToken0).to.be.false;
});

it('should revert if pool does not contain KP3R as token0 nor token1', async () => {
Expand Down
Loading

0 comments on commit b18e294

Please sign in to comment.